using System;
using System.Collections;
using System.Collections.Specialized;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
namespace edu.jhu.QueryStats {
	public struct OpKeys{
		public const string 
			ARG="Argument",
			AVG_ROW_SIZE="AvgRowSize",
			BKMK="Bookmark Lookup",
			BKMK_TARGET="Bookmark Target",
			CI_SCAN="Clustered Index Scan",
			CI_SEEK = "Clustered Index Seek",
			COST="Cost",
			DEF_VAL="DefinedValues",
			E_CPU="EstimateCPU",
			E_EXECS="EstimateExecutions",
			E_IO="EstimateIO",
			E_ROWS="EstimateRows",
			FILTER="Filter",
			I_JOIN="Inner Join",
			I_SCAN="Index Scan",
			I_SEEK="Index Seek",
			L_OP="LogicalOp",
			NODE_ID="NodeId",
			OP="Operation",
			P_OP="PhysicalOp",
			PARENT="Parent",
			SQL_EST="SqlEstimate",
			STMT_TEXT="StmtText",
			SUB_COST="TotalSubtreeCost",
			TIME="Time",
			TYPE="Type";
	}
	public class Operation{
		public Hashtable Data;
		public ArrayList Children;
		public int X, Y,Row,Col;
		public float IO,CPU;
		public bool SubTreeDrawn;
		public string Title;
		public Operation(){
			Data = new Hashtable();
			Children = new ArrayList();
			SubTreeDrawn = false;
		}
	}
	public class PlanMakerException:Exception{public PlanMakerException(string s):base(s){}}
	public class PlanMaker{
		public TimeSpan TotalTime;
		public double TotalCost;
        public int PlanCount;
		private const RegexOptions DEF_REG_OPT = RegexOptions.IgnoreCase|RegexOptions.Compiled|RegexOptions.Multiline|
			RegexOptions.ExplicitCapture;
		private DataTable dt;
		private Hashtable Operations;
        private ArrayList PlanOperations;
		private StringCollection suppresed,first2Show;
		private double TotalIO,TotalCPU;
		private string CPU = OpKeys.E_CPU;
		private string IO = OpKeys.E_IO;
        private Point[] Sizes;
        private StringCollection Queries = new StringCollection();
		
        private Bitmap[] bm;
		public string BG_COLOR = ConfigurationSettings.AppSettings["bg_color"];
		public string FG_COLOR = ConfigurationSettings.AppSettings["fg_color"];
			
		public const int 
			NODE_HEIGHT=50,NODE_WIDTH=120,//size of the node
			NODE_XSPACE=30,NODE_YSPACE=20,//space between nodes, space between nodes is space*2
			LEFT_M=10,RIGHT_M=10,//margins
			TOP_M=10,BOT_M=10,
			STEP_M=9,//space between vertical lines, only applies when sibcount > 2
			A_W=6,A_H=3,//height/width for the arrows
			TITLE_HEIGHT=25,
			NODE_CURVE=15,
			MAX_TITLE_LENGTH=60;
		public const double 
			EST_CELL_WEIGHT = .00001,
			RESULT_ROW_WEIGHT = 1,
			MATH_WEIGHT = 2;
		private Hashtable InitWeights(){
			Hashtable ht = new Hashtable();
			ht.Add(OpKeys.BKMK,(double)1);
			ht.Add(OpKeys.CI_SCAN,(double)1);
			ht.Add(OpKeys.I_SCAN,(double)1);
			ht.Add(OpKeys.CI_SEEK,(double)1);
			ht.Add(OpKeys.I_SEEK,(double)1);
			ht.Add(OpKeys.I_JOIN,(double)1);
			return ht;
		}
		private Hashtable InitCounts(){
			Hashtable tcounts = new Hashtable(); 
			tcounts.Add("speclineall",(double)17467522);
			tcounts.Add("photoobjall",(double)138202232);
			tcounts.Add("specobjall",(double)367360);
			//tcounts.Add("specobjall",(double)367360);
			return tcounts;
		}
		public PlanMaker(DataTable dt_){
			loadSuppressed();
			
			dt = dt_;
			LoadData();
			ColorConverter cc = new ColorConverter();
            
			//Operation root = (Operation)Operations[1];
            ArrayList roots = GetRoots(Operations);
            PlanCount = roots.Count;
            bm = new Bitmap[roots.Count];
            PlanOperations = new ArrayList(roots.Count);
            Sizes = new Point[roots.Count];
            for(int i=0;i<bm.Length;i++) {
                int Width = 5000, Height = 5000;
                PlanOperations.Add(new ArrayList());
                bm[i] = new Bitmap(Width, Height);
                Graphics g = Graphics.FromImage(bm[i]);
                g.FillRectangle(new SolidBrush(HexStringToColor(BG_COLOR)),
                    0, 0, (float)Width, (float)Height);
                //g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
                g.SmoothingMode = SmoothingMode.AntiAlias;

                Operation root = (Operation)roots[i];
                Queries.Add(root.Data[OpKeys.STMT_TEXT].ToString());
                //throw new Exception(root.Data[OpKeys.STMT_TEXT] + ", " + root.Data[OpKeys.NODE_ID]);
                calcCosts(root);
                getTotalCost(root);
                try {
                    root.Row = 0;
                    root.Col = 0;
                }
                catch (NullReferenceException nre) {
                    throw new PlanMakerException("Invalid table; cannot create plan.");
                }
                //throw new Exception(root.Title+", "+root.Data[OpKeys.NODE_ID]+", "+ PlanCount);
                Hashtable cHeights = new Hashtable();
                DrawNodes(i, g, root, cHeights);
                Height = ((maxHeight(cHeights)) * (NODE_HEIGHT + NODE_YSPACE)) - NODE_YSPACE + TOP_M + BOT_M;
                Width = (maxWidth(cHeights) * (NODE_WIDTH + NODE_XSPACE)) + LEFT_M + RIGHT_M - NODE_XSPACE;               
                bm[i] = Crop(bm[i], Width, Height);
                Sizes[i] = new Point(Width, Height);
            }
            SanitizeQueries();
		}
        const string word = "[^\\ \\t\\r\\n\\)\\(]+";
        const string dot = "\\.";
        Regex regex = new Regex("(?<1>"+word+dot+word+dot+word+")"+dot+word, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
        private void SanitizeQueries() {
            for (int i = 0; i < Queries.Count; i++) {
                Match m = regex.Match(Queries[i]);
                while (m.Success) {
                    if(m.Result("$1").ToUpper().IndexOf("MYDB_") > -1)
                        Queries[i] = Queries[i].Replace(m.Result("$1"), "MyDB");
                    else
                        Queries[i] = Queries[i].Replace(m.Result("$1"), "[CONTEXT]");
                    m = m.NextMatch();
                }
            }
        }
        private ArrayList GetRoots(Hashtable Operations) {
            ArrayList rst = new ArrayList();
            foreach(object o in Operations.Keys) {
                Operation a = (Operation)Operations[o];
                Operation parent = getParent(a);
                if (parent == null) rst.Insert(0,a);
            }
            return rst;
        }
		private Bitmap Crop(Bitmap src, int width, int height){
			Bitmap cropped = new Bitmap(width,height);			 
			Graphics gCrop = Graphics.FromImage(cropped);
			gCrop.DrawImage(src, new Rectangle(-1,-1,src.Width,src.Height),0,0,src.Width,src.Height,
				GraphicsUnit.Pixel);
			return cropped;
		}
		public Stream WritePlanImage(int planNum){
			MemoryStream ms = new MemoryStream();
			bm[planNum].Save(ms,ImageFormat.Png);
			ms.Position = 0;
			return ms;
		}
		public void SaveImage(string path, int i){
			bm[i].Save(path,ImageFormat.Png);
		}
		public Hashtable GetOperations(){
			return Operations;
		}
		private int getQueryHeight(string qry){
			int newlines = 1,position=0,tmp=0;
			for(;;){
				tmp = qry.IndexOf("\n",position);
				if(tmp == -1)break;
				position=tmp+1;
				++newlines;
			}
			newlines= (int)newlines*13;
			return newlines < 301?newlines:300;
		}
		public string GetHtml(string imagelink,string qry){
			StringWriter sw = new StringWriter();
			sw.WriteLine("<html><head>");
			sw.WriteLine("<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">");
			sw.WriteLine("<script language=\"javascript\" src=\"../scripts/qspop.js\"></script>");
			sw.WriteLine("<script language=\"javascript\" src=\"pmscript.js\"></script>");
            sw.WriteLine("<link rel=\"shortcut icon\" href=\"../favicon.ico\"/>");
			sw.WriteLine("<link href=\"../cjobs.css\" rel=\"stylesheet\"></link>");
			sw.WriteLine("</head><body>");
			sw.WriteLine("<div id=\"header\">");
			sw.WriteLine("<p><i>Mouseover nodes for more info</i></p>");
			sw.WriteLine("<p><span class=\"atitle\">&nbsp;Estimated Costs: </span><span style=\"background:green;color:white\">&nbsp;Very Low&nbsp;</span>");
			sw.WriteLine("<span style=\"background:yellow;color:black\">&nbsp;Low&nbsp;</span>");
			sw.WriteLine("<span style=\"background:orange;color:black\">&nbsp;Moderate&nbsp;</span>");
			sw.WriteLine("<span style=\"background:red;color:white\">&nbsp;High&nbsp;</span></p>");
			Color[] colors = getCostColors(TotalCost);
			sw.WriteLine("</div>");
			const string nprefix = "qs_node_";
            for (int i = 0; i < PlanCount; i++) {
                sw.WriteLine("<map name=\"index"+i+"\">");
                ArrayList pops = (ArrayList)PlanOperations[i];
                foreach (object o in pops) {
                    Operation op = (Operation)o;
                    sw.WriteLine("<area coords=\"" +
                        op.X + "," + op.Y + "," + (op.X + NODE_WIDTH) + "," + (op.Y + NODE_HEIGHT) + "\"" +
                        " shape=\"rect\"" +
                        " onmouseover=\"showqsnode('" + nprefix + op.Data[OpKeys.NODE_ID] + "', this.event);alignMap();\"" +
                        " onmouseout=\"hideqsnode('" + nprefix + op.Data[OpKeys.NODE_ID] + "', this.event);\"" +
                        " />");
                }
                sw.WriteLine("<area shape=default nohref/>");
                sw.WriteLine("</map>");
            }
			
            for (int i = 0; i < PlanCount; i++) {
                //add noise to the image string to defeat caching attempts
                string gibberish = "&fakeParam=" + Math.Abs(this.GetHashCode() + i + DateTime.Now.GetHashCode());
                sw.WriteLine("<hr/><div><pre>"+Queries[i]+"</pre></div>");
                sw.WriteLine("<div><img width=\""+Sizes[i].X+"\" height=\""+Sizes[i].Y+"\" id=\"map"+i+"\" src=\"" 
                    + imagelink + "?n="+i+gibberish+"\" border=0 usemap=\"#index" + i + "\"></div>");
            }
			foreach(object o in Operations.Keys){
				Operation op = (Operation)Operations[(int)o];
				sw.WriteLine("<div onmouseover=\"donthide\" onmouseout=\"dohide\" id=\""+nprefix+o+
					"\" class=\"ppop\" style=\"position:absolute;top:0;left:0;z-index:100;visibility:hidden\">");
				sw.WriteLine("<table cellpadding=\"0\" cellspacing=\"1\">");
				foreach(string s in first2Show)
					if(op.Data.Contains(s) && op.Data[s].ToString().Length > 0)
						sw.WriteLine("<tr><th>"+s+"</th><td>"+htmlify(op.Data[s].ToString())+
							"</td></tr>");
				foreach(object dk in op.Data.Keys){
					if(suppresed.Contains((string)dk))continue;
					if(first2Show.Contains((string)dk))continue;
					if(op.Data[dk].ToString().Length > 0)
						sw.WriteLine("<tr><th>"+dk+"</th><td>"+htmlify(op.Data[dk].ToString())+
							"</td></tr>");
				}
				sw.WriteLine("</table>");
				sw.WriteLine("</div>");
			}
			sw.WriteLine("<script language=\"javascript\" src=\"scripts/wz_tooltip.js\"></script>");
			sw.Write("</body></html>");
			sw.Close();
			return sw.ToString();
		}
		private string htmlify(string s){
			if(s==null)return "";
			return s.Replace(">","&gt;").Replace("<","&lt;").Replace("\n","<br>").Replace("[","").
				Replace("]","");
		}
		private void LoadData(){
			Operations = new Hashtable();
			ArrayList tmpLvlCount = new ArrayList();
			for(int r=0;r<dt.Rows.Count;r++){
				Operation op = new Operation();
				foreach(DataColumn dc in dt.Columns)
					op.Data.Add(dc.ColumnName,dt.Rows[r][dc]);                
				try{op.CPU=(float)op.Data[CPU];}catch{}
				try{op.IO=(float)op.Data[IO];}catch{}
				TotalCPU+=op.CPU;
				TotalIO+=op.IO;
				Operations.Add((int)op.Data[OpKeys.NODE_ID],op);
				int parent = (int)op.Data[OpKeys.PARENT];
				if(parent != 0)
					((Operation)Operations[parent]).Children.Add(op);
				if(!tmpLvlCount.Contains(parent))tmpLvlCount.Add(parent);
			}
			tmpLvlCount.Clear();
		}
		private void calcCosts(Operation op){
			processOp(op);
			op.Data.Add(OpKeys.COST,getNodeCost(op));
			foreach(object o in op.Children)
				calcCosts((Operation)o);
		}
        private double valueOr0(object o){
            if (o == null) return 0;
            try {
                return double.Parse(o.ToString());
            }
            catch {
                return 0;
            }
        }
		private double getNodeCost(Operation op){
			double thisCost = valueOr0(op.Data[OpKeys.SUB_COST]);
			foreach(object o in op.Children)
				thisCost-=valueOr0(((Operation)o).Data[OpKeys.SUB_COST]);
			return thisCost;
		}
		private void getTotalCost(Operation root){
			TotalCost = valueOr0(root.Data[OpKeys.SUB_COST].ToString());
		}
		private void DrawNodes(int i, Graphics g, Operation root,Hashtable colHeights){
			Operation next = getParent(DrawRow(i, g,root,colHeights,maxHeight(colHeights)+1));
			if(next == null)return;//no more nodes to draw
			while(next != null){
				if(next.Children.Count > 1 && !(next.SubTreeDrawn)){
					next.SubTreeDrawn = true;
					for(int x=1;x<next.Children.Count;x++){
						Operation child = (Operation)next.Children[x];
						child.Col = next.Col+1;
						child.Row = maxHeight(colHeights);
						DrawNodes(i, g,child,colHeights);
					}
				}
				next = getParent(next);
			}
		}
		//returns last node drawn
		private Operation DrawRow(int i, Graphics g, Operation op,Hashtable colHeights,int height){
			op.X = getNodeX(op.Col);
			op.Y = getNodeY(op.Row);
            ArrayList planList = (ArrayList)PlanOperations[i];
            planList.Add(op);
			if(!colHeights.ContainsKey(op.Col))colHeights.Add(op.Col,height);
			else colHeights[op.Col] = height;
			drawNode(i, g,op);
				
			Operation parent = getParent(op);
			if(parent != null)Connect(g,op,parent,1);
			if(op.Children.Count > 0){
				Operation nextChild = (Operation)op.Children[0];
				nextChild.Row = op.Row;
				nextChild.Col = op.Col+1;
				return DrawRow(i, g,nextChild,colHeights,height);
			}
			else return op;
		}
		private int maxHeight(Hashtable heights){
			int maxHeight = 0;
			foreach(object o in heights.Keys){
				int h = (int)heights[o];
				if(h>maxHeight) maxHeight = h;
			}
			return maxHeight;
		}
		private int maxWidth(Hashtable heights){
			int maxWidth = 0;
			foreach(object o in heights.Keys){
				int h = (int)o;
				if(h>maxWidth) maxWidth = h;
			}
			return maxWidth+1;
		}
		private Operation getParent(Operation op){
			return Operations[op.Data[OpKeys.PARENT]] as Operation; 
		}
		
		private int getNodeX(int lvl){return (((NODE_WIDTH+NODE_XSPACE)*lvl)+LEFT_M);}
		//private int getNodeY(int sibcount, int sibnum){return(((Height/(sibcount+1))*sibnum)-(NODE_HEIGHT/2));}
		private int getNodeY(int sibnum){
			return (((NODE_HEIGHT+NODE_YSPACE)*(sibnum))+TOP_M);}
		private void Connect(Graphics g, Operation from, Operation to,int sibcount){
			ArrayList points = new ArrayList();
			points.Add(new Point(from.X,from.Y+(NODE_HEIGHT/2)));
			int xoff = sibcount > 2?(sibcount-2)*(A_H+1)*2:0;
			if(from.Y != to.Y){
				points.Add(new Point(from.X-STEP_M-xoff,from.Y+(NODE_HEIGHT/2)));//-|
				points.Add(new Point(from.X-STEP_M-xoff,from.Y+(NODE_HEIGHT/2)));
				points.Add(new Point(from.X-STEP_M-xoff,to.Y+(NODE_HEIGHT/2)+A_H+3));//-|
				points.Add(new Point(from.X-STEP_M-xoff,to.Y+(NODE_HEIGHT/2)+A_H+3));
				points.Add(new Point(to.X+NODE_WIDTH+A_W,to.Y+(NODE_HEIGHT/2)+A_H+3));//-|
				Point[] _arrow = new Point[4];
				_arrow[0] = new Point(to.X+NODE_WIDTH,		to.Y+(NODE_HEIGHT/2)+A_H+3);
				_arrow[1] = new Point(to.X+NODE_WIDTH+A_W,	to.Y+(NODE_HEIGHT/2)+3);
				_arrow[2] = new Point(to.X+NODE_WIDTH+A_W,	to.Y+(NODE_HEIGHT/2)+A_H+A_H+3);
				_arrow[3] = new Point(to.X+NODE_WIDTH,		to.Y+(NODE_HEIGHT/2)+A_H+3);
				g.FillPolygon(new SolidBrush(HexStringToColor(FG_COLOR)),_arrow);		
			}
			else
				points.Add(new Point(to.X+NODE_WIDTH+A_W,to.Y+(NODE_HEIGHT/2)));
			for(int x=0;x<points.Count;x=x+2)
				g.DrawLine(new Pen(new SolidBrush(HexStringToColor(FG_COLOR))),(Point)points[x],(Point)points[x+1]);
			Point[] arrow = new Point[4];
			arrow[0] = new Point(to.X+NODE_WIDTH,to.Y+(NODE_HEIGHT/2));
			arrow[1] = new Point(to.X+NODE_WIDTH+A_W,to.Y+(NODE_HEIGHT/2)-A_H);
			arrow[2] = new Point(to.X+NODE_WIDTH+A_W,to.Y+(NODE_HEIGHT/2)+A_H);
			arrow[3] = new Point(to.X+NODE_WIDTH,to.Y+(NODE_HEIGHT/2));
			g.FillPolygon(new SolidBrush(HexStringToColor(FG_COLOR)),arrow);		
		}
        /*
         * fills out query fragments that were not in the primary row for the statement.
         * nasty place to put it, but efficient*/
        private void FillQueryRemnants(int i, Operation op) {
            if (op.Data[OpKeys.STMT_TEXT].ToString().IndexOf("|--") == -1)
                if (Queries[i].IndexOf(op.Data[OpKeys.STMT_TEXT] as string) == -1)
                    Queries[i] = Queries[i] + "\n" + op.Data[OpKeys.STMT_TEXT];
        }
		private void drawNode(int i, Graphics g,Operation op){
            FillQueryRemnants(i, op);
			Rectangle rec = new Rectangle(op.X,op.Y,NODE_WIDTH,NODE_HEIGHT);
			ExtendedGraphics eg = new ExtendedGraphics(g);
            try {//REMOVE
                Color[] costColors = getCostColors(op);
                SolidBrush brush = new SolidBrush(costColors[1]);
                eg.FillRoundRectangle(brush, rec.X, rec.Y, rec.Width, rec.Height, NODE_CURVE);

                StringFormat format = new StringFormat();
                brush = new SolidBrush(costColors[0]);
                format.Alignment = StringAlignment.Center;
                format.LineAlignment = StringAlignment.Center;
                Font fb = new Font("Verdana", 7);
                g.DrawString(op.Title, fb, brush, rec, format);
            }
            catch {
                throw new Exception(op+", "+op.Data[OpKeys.NODE_ID]);
            }
		}
		
		private Regex whereR = new Regex("WHERE:\\((?<1>.+)\\)",DEF_REG_OPT);
		private Regex seekR = new Regex("SEEK:\\((?<1>.+)\\)",DEF_REG_OPT);
		private Regex objR = new Regex("OBJECT:\\((?<1>[^\\)]+)\\)",DEF_REG_OPT);
		private void processOp(Operation op){
			string lop = op.Data[OpKeys.L_OP] as string;
			string pop = op.Data[OpKeys.P_OP] as string;
			if(lop != null && lop != "")
				if( lop == pop)	op.Data.Add(OpKeys.OP,lop);
				else op.Data.Add(OpKeys.OP,lop+" / "+pop);
			string title = op.Data[OpKeys.OP] as String;
			string rst = null;
			switch(lop){
				case null: op.Title = op.Data["Type"] as string;break;
				case OpKeys.BKMK:
					op.Title = lop;
					rst = clean(objR.Match(op.Data[OpKeys.ARG] as string).Result("$1")).Split('.')[2];
					op.Title = title+"\n["+rst+"]";
					op.Data.Add(OpKeys.BKMK_TARGET,rst);
					break;
				case OpKeys.FILTER:
					op.Title = title;
					string arg2 = op.Data[OpKeys.ARG] as string;
					op.Data.Add(OpKeys.SQL_EST,whereR.Match(arg2).Result("$1"));
					break;
				case OpKeys.CI_SCAN:
				case OpKeys.I_SCAN:
					string arg1 = op.Data[OpKeys.ARG] as string;
					Match m1 = whereR.Match(arg1);
					if(m1.Success)rst = m1.Result("$1");
					else rst = objR.Match(arg1).Result("$1");
					rst = clean(rst);
					op.Data.Add(OpKeys.SQL_EST,rst);
					op.Title = ttrim(rst);
					break;
				case OpKeys.CI_SEEK:
				case OpKeys.I_SEEK:
					string arg = op.Data[OpKeys.ARG] as string;
					Match m = whereR.Match(arg);
					if(m.Success)rst = m.Result("$1");
					else rst = seekR.Match(arg).Result("$1");
					rst = clean(rst);
					op.Data.Add(OpKeys.SQL_EST,rst);
					op.Title = ttrim(rst);
					break;
				default: op.Title = title;break;
			}
		}
		private string ttrim(string title){
			if(title.Length > MAX_TITLE_LENGTH)
				return title.Substring(0,MAX_TITLE_LENGTH-3)+"...";
			else return title;
		}
		private string clean(string s){
			string s2 = s.Replace("[","").Replace("]","").Replace("="," = ").Replace(">"," > ").Replace("<"," < ").
				Replace("(","").Replace(")","").Replace("Convert","").Trim();
			return s2;
		}
		private string getCostString(Operation op){
			double io = Math.Round((op.IO/TotalIO)*100,1);
			double cpu = Math.Round((op.CPU/TotalCPU)*100,1);
			return "CPU: "+cpu+"%\n"+"IO: "+io+"%";
		}
		private Color[] getCostColors(Operation op){
            try {
                double cost = valueOr0(op.Data[OpKeys.COST].ToString());
                return getCostColors(cost);
            }
            catch {
                return getCostColors(0);
            }
		}
		private Color[] getCostColors(double cost){
			if(cost >= 100)
				return new Color[2]{Color.White,Color.Red};
			else if(cost >= 20) return new Color[2]{Color.Black,Color.Orange};
			else if(cost >= 5) return new Color[2]{Color.Black,Color.Yellow};
			else return new Color[2]{Color.White,Color.Green};
		}
		public Color HexStringToColor(string hexColor) {
			string hc = ExtractHexDigits(hexColor);
			string r = hc.Substring(0, 2);
			string g = hc.Substring(2, 2);
			string b = hc.Substring(4, 2);
			Color color = Color.Empty;
			int ri = Int32.Parse(r, System.Globalization.NumberStyles.HexNumber);
			int gi = Int32.Parse(g, System.Globalization.NumberStyles.HexNumber);
			int bi = Int32.Parse(b, System.Globalization.NumberStyles.HexNumber);
			color = Color.FromArgb(ri, gi, bi);
			return color;
		}
		
		public static string ExtractHexDigits(string input) {
			Regex isHexDigit 
				= new Regex("[abcdefABCDEF\\d]+", RegexOptions.Compiled);
			string newnum = "";
			foreach (char c in input) {
				if (isHexDigit.IsMatch(c.ToString()))
					newnum += c.ToString();
			}
			return newnum;
		}
		private void loadSuppressed(){
			string sup = ConfigurationSettings.AppSettings["suppress"];
			suppresed = new StringCollection();
			suppresed.AddRange(sup.Split(','));
			string first = ConfigurationSettings.AppSettings["first2show"];
			first2Show = new StringCollection();
			first2Show.AddRange(first.Split(','));
		}
	}
}