using System;
using System.Collections;
using System.Collections.Specialized;
using System.Data;
using System.IO;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Text;

using ms.aq.cview.util;
using ms.aq.cview.query;

namespace ms.aq.cview.draw {

    public class Clotter {
        public const int MINIMUM_BAR_WIDTH = 12;
        public const float FULL = Int16.MinValue;
        public static Color BCKGRND = Color.FromArgb(235, 235, 235);
        public static Color FGRND = Color.FromArgb(0, 0, 0);
        //public static Color BCKGRND = Color.White;
        //public static Color FGRND = Color.Black;
        Bitmap bMap;
        Graphics g;
        public DBNumber XMin, XMax, XDif, YMin, YMax, YDif;
        public int Width, Height, TopMargin, RightMargin, BottomMargin, LeftMargin, MajorTicks, MinorTicks, InnerWidth, InnerHeight;
        public int TickWidth, MinorTickWidth, BlockSize;

        private string dollup(string s) {
            int di = s.LastIndexOf('.') + 1;
            return s.Substring(di, s.Length - di).Trim('&').Trim('[').Trim(']');
        }

        public Clotter(DataSet minMax, SelectionSets ss,
            int width, int height, int top_m, int right_m, int bottom_m, int left_m, int major_ticks,
            int tick_width, int minor_ticks, int minor_tick_width, int block_size) {
            this.Width = width; this.Height = height; this.TopMargin = top_m; this.BottomMargin = bottom_m;
            this.LeftMargin = left_m; this.RightMargin = right_m;
            this.MajorTicks = major_ticks; this.MinorTicks = minor_ticks;
            this.TickWidth = tick_width;
            this.MinorTickWidth = minor_tick_width;
            this.BlockSize = block_size;

            bMap = new Bitmap(width, height);
            g = Graphics.FromImage(bMap);
            g.SmoothingMode = SmoothingMode.Default;
            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
            setBounds(minMax, ss);
            ArrayList al = drawCanvas();
        }
        void setBounds(DataSet minMax, SelectionSets ss) {
            StringCollection range = ss.GetCollection(PrefixDecoder.P1_RANGE_PREFIX);
            if (range.Count == 0) throw new Exception("Cannot plot, no range selected.");
            ICollection ic = ss.GetPrefixes(PrefixDecoder.P1_SERIES_PREFIX);
            if (ic.Count == 0) throw new Exception("Cannot plot, no series added");

            string col_nb = common.debracket(range[0]);
            GetBoundsFromMinMaxTable(col_nb, minMax.Tables[0], out XMin, out XMax, out XDif);
            if (XDif.CompareTo(0) == 0) {
                XMin = XMin.Sub(1);
                XMax = XMax.Add(1);
                XDif = DBNumber.Dif(XMax, XMin);
            }
            string key, col;

            DBNumber cmin, cmax, cdif;
            foreach (object o in ic) {//detemine overall series bounds
                key = (string)o;
                try {
                    col = ss.GetCollection(key)[0];
                }
                catch (ArgumentOutOfRangeException) { continue; }
                col_nb = common.debracket(col);
                GetBoundsFromMinMaxTable(col_nb, minMax.Tables[0], out cmin, out cmax, out cdif);
                if (YMax == null || cmax.GT(YMax)) YMax = cmax;
                if (YMin == null || cmin.LT(YMin)) YMin = cmin;
            }
            YDif = DBNumber.Dif(YMax, YMin);
            if (YDif.CompareTo(0) == 0) {
                YMin = YMin.Sub(1);
                YMax = YMax.Add(1);
                YDif = DBNumber.Dif(YMax, YMin);
            }

        }

        public static void GetBoundsFromMinMaxTable(string col_nb, DataTable minMaxSet, out DBNumber min, out DBNumber max, out DBNumber dif) {
            min = DBNumberFactory.Object2DBNumber(minMaxSet.Rows[0][col_nb + "_MIN"]);
            max = DBNumberFactory.Object2DBNumber(minMaxSet.Rows[0][col_nb + "_MAX"]);
            dif = DBNumber.Dif(max, min);
        }
        public void Save(Stream s) {
            g.Flush();
            bMap.Save(s, ImageFormat.Png);
        }
        /*
         * returns a list of unlabeled points on the x axis
         */
        private ArrayList drawCanvas() {
            g.FillRectangle(new SolidBrush(BCKGRND),
                0, 0, Width, Height);
            ArrayList rst = drawTicks();
            StringFormat drawFormat = new StringFormat();
            drawFormat.Alignment = StringAlignment.Center;
            Font titleFont = new Font("Arial", 14, FontStyle.Bold);
            return rst;
        }

        private static int toPix(int pixdif, DBNumber val, DBNumber val_dif, DBNumber val_min) {
            DBNumber valb = DBNumber.Dif(val, val_min);
            //Console.WriteLine(val + ", " + val_dif + ", " + val_min + ", " + valb);
            return (int)Math.Floor(DBNumber.DivR(valb, val_dif) * pixdif);
        }
        /*
         * draws tick marks.  returns an arraylist of value for labels it cannot draw without running the 
         * full query, ie xaxis series plot labels.
         */
        private ArrayList drawTicks() {
            Pen pen = new Pen(new SolidBrush(FGRND), 1);
            InnerWidth = Width - LeftMargin - RightMargin;
            InnerHeight = Height - TopMargin - BottomMargin;
            g.DrawRectangle(pen, LeftMargin, TopMargin, InnerWidth, InnerHeight);
            StringFormat drawFormat = new StringFormat();
            drawFormat.Alignment = StringAlignment.Center;
            Font labelFont = new Font("Arial", 8);

            Console.WriteLine("YAXIS:\n\tMIN=" + YMin + ", MAX=" + YMax + ", DIF=" + YDif);
            TickVal tv = getTickVal(YMin, YMax, YDif, MajorTicks);
            Console.WriteLine("\tx0 = " + tv.x0 + ", dx = " + tv.dx + ", n = " + tv.n);

//            throw new Exception(YMin + ", " + YMax + ", " + YDif + "\n" + tv.x0 + ", " + tv.dx + ", " + tv.n);
            YMin = tv.x0;
            YMax = YMin.Add(tv.dx.Mult(tv.n));
            YDif = DBNumber.Dif(YMax, YMin);


            int dash_pos;
            for (int i = 0; i < tv.n; i++) {//y-axis major ticks
                dash_pos = toPix(InnerHeight, tv.x0.Add(tv.dx.Mult(i)), YDif, YMin) + TopMargin;
                g.DrawLine(pen, LeftMargin, dash_pos, LeftMargin + TickWidth, dash_pos);//left 
                g.DrawLine(pen, Width - RightMargin - TickWidth, dash_pos, Width - RightMargin, dash_pos);//right
            }
            for (int i = 0; i < (tv.n * MinorTicks); i++) {//y-axis minor ticks
                //if YDif <= MajorTicks, then this does nothing
                dash_pos = toPix(InnerHeight, tv.x0.Add(tv.dx.Div(MinorTicks).Mult(i)), YDif, YMin) + TopMargin;
                g.DrawLine(pen, LeftMargin, dash_pos, LeftMargin + MinorTickWidth, dash_pos);//left
                g.DrawLine(pen, Width - RightMargin - MinorTickWidth, dash_pos, Width - RightMargin, dash_pos);//left
            }

            string lbl = "";
            int dx = InnerHeight / tv.n;
            Rectangle layout = new Rectangle(0, 0, LeftMargin, dx - 2);
            drawFormat.Alignment = StringAlignment.Far;
            for (int n = 1; n < tv.n; n++) {//left labels                
                layout.Y = Height - BottomMargin - (n * dx) - 5;
                lbl = (tv.x0.Add((tv.dx.Mult(n)))).ToString();
                g.TranslateTransform(layout.X + layout.Width, layout.Y);
                g.RotateTransform(30);
                g.DrawString(lbl, new Font("Arial", 8), new SolidBrush(FGRND),
                    new Rectangle(-layout.Width, 0, layout.Width, layout.Height), drawFormat);
                g.RotateTransform(-30);
                g.TranslateTransform(-(layout.X + layout.Width), -layout.Y);
            }

            Console.WriteLine("XAXIS:\n\tMIN=" + XMin + ", MAX=" + XMax + ", DIF=" + XDif);
            tv = getTickVal(XMin, XMax, XDif, MajorTicks);
            Console.WriteLine("\tx0 = " + tv.x0 + ", dx = " + tv.dx + ", n = " + tv.n);
            //throw new Exception("TTT: " + XMin + ", " + XMax + ", " + XDif+"\n"+tv.x0+", "+tv.dx+", "+tv.n);
            XMin = tv.x0;
            XMax = XMin.Add(tv.dx.Mult(tv.n));
            XDif = DBNumber.Dif(XMax, XMin);


            for (int i = 0; i < tv.n; i++) {//x-axis major ticks
                dash_pos = toPix(InnerWidth, tv.x0.Add(tv.dx.Mult(i)), XDif, XMin) + LeftMargin;
                g.DrawLine(pen, dash_pos, TopMargin + InnerHeight, dash_pos, TopMargin + InnerHeight - TickWidth);//bot
                g.DrawLine(pen, dash_pos, TopMargin, dash_pos, TopMargin + TickWidth);//top
            }
            for (int i = 0; i < (tv.n * MinorTicks); i++) {//x-axis minor ticks
                dash_pos = toPix(InnerWidth, tv.x0.Add(tv.dx.Div(MinorTicks).Mult(i)), XDif, XMin) + LeftMargin;
                g.DrawLine(pen, dash_pos, TopMargin + InnerHeight, dash_pos, TopMargin + InnerHeight - MinorTickWidth);//bot
                g.DrawLine(pen, dash_pos, TopMargin, dash_pos, TopMargin + MinorTickWidth);//top
            }
            dx = InnerWidth / tv.n;
            layout = new Rectangle(0, Height - BottomMargin + 1, (InnerWidth / (tv.n)), BottomMargin);
            pen.Color = FGRND;
            drawFormat.Alignment = StringAlignment.Near;
            for (int n = 1; n < tv.n; n++) {//bottom labels
                layout.X = toPix(InnerWidth, tv.x0.Add((tv.dx.Mult(n))), XDif, XMin) + LeftMargin;
                lbl = ((tv.dx.Mult(n)).Add(tv.x0)).ToString();
                g.TranslateTransform(layout.X, layout.Y);
                g.RotateTransform(30);
                g.DrawString(lbl, new Font("Arial", 8), pen.Brush, 0, 0, drawFormat);
                g.RotateTransform(-30);
                g.TranslateTransform(-layout.X, -layout.Y);
            }
            return null;
        }
        private static TickVal getTickVal(DBNumber min, DBNumber max, DBNumber dif, int ticks) {

            DBNumber x0 = min, dx = DBNumberFactory.Object2DBNumber(-1);
            if (dif is DBInt && ticks > dif) {
                TickVal rst = new TickVal();
                rst.x0 = min;
                rst.dx = new DBInt(1);
                rst.n = (int)dif;
                return rst;
            }
            DBNumber interval = dif.Div(ticks);
            double f = interval;
            long exp10 = 0;
            int n = ticks;
            bool go = true;
            if (interval.CompareTo(0) == 0) {
                TickVal val2 = new TickVal();
                val2.x0 = min - min;
                val2.n = 2;
                val2.dx = min;
                return val2;
            }

            //1 < f < 10
            if (interval.GT(10))
                while (f > 10) {
                    f = f / 10;
                    exp10++;
                }
            else if (interval.LT(1))
                while (f < 1) {
                    f = f * 10;
                    exp10--;
                }
            else if (interval.LT(0)) go = false;
            double tint;
            if (f < 1.5)
                tint = 1;
            else if (f < 3)
                tint = 2;
            else if (f < 5)
                tint = 5;
            else
                tint = 10;
            if (go) {
                dx = tint * Math.Pow(10, exp10);
                if (min is DBInt)
                    tint = (long)min / (long)dx;
                else
                    tint = Math.Round((double)((double)min / (double)dx));
                x0 = dx * tint;
                if (min is DBInt)
                    n = int.Parse(Math.Round((double)(((long)max - (long)x0) / (double)dx)).ToString());
                else if (min is DBFloat)
                    n = int.Parse(Math.Round((double)(((double)max - (double)x0) / (double)dx)).ToString());
            }
            try {
                if (x0.GT(min)) {//check bottom
                    while (x0.GT(min)) {
                        x0 = x0.Sub(dx);
                        ++n;
                    }
                }
                if ((dx.Mult(n).Add(x0)).LT(max)) {//check bottom
                    while ((dx.Mult(n).Add(x0)).LT(max)) {//check bottom
                        ++n;
                    }
                }
                TickVal val = new TickVal();
                //throw new Exception(x0 + ", " + n + ", " + dx);
                val.x0 = x0;
                val.n = n;
                val.dx = dx;
                return val;
            }
            catch (Exception exception) {
                throw new Exception(exception + "\n" + min + ", " + max);
            }
        }
        const float VMIN = .45f, VDIF= .55f;
        void adjustColor(WebColor wc, PlotPoint pp) {
            if (pp.HitDif == 0) {
                wc.setV(1);
                return;
            }
            if (pp.Hits > pp.HitMax || pp.Hits < pp.HitMin) throw new Exception("Hit count out of range: " + pp.HitMin+", "+pp.HitMax+": "+pp.Hits);
            wc.setV(VMIN + ((VDIF * (pp.Hits - pp.HitMin))/pp.HitDif));
        }
        public int DrawPoints(DataSet dspoints, out Serie[] legend) {
            Pen pen = new Pen(new SolidBrush(FGRND), 1);
            int pcount = 0;
            int rcount = 0;
            PlotPoint[] points = null;
            legend = null;
            string rangeLbl;
            int x, y;
            //cl is used to generate different bar colors if xi.axistype is string
            ColorLine cl = new ColorLine();
            foreach (DataRow dr in dspoints.Tables[0].Rows) {
                //while (reader.Read()) {
                if (pcount == 0) points = InitRowPoints(dspoints);
                LoadRowPoints(points, dr, dspoints);
                if (pcount == 0) legend = initLegend(points);
                for (int i = 0; i < points.Length; i++) {
                    if (points[i].Y == Settings.AXI_UNDEF || points[i].X == Settings.AXI_UNDEF)continue;

                    if (BlockSize == 1) {
                        x = (points[i].X * BlockSize) + LeftMargin;
                        y = (points[i].Y * BlockSize) + TopMargin;
                    }
                    else {
                        x = (points[i].X * BlockSize) + LeftMargin;
                        y = (points[i].Y * BlockSize) + TopMargin;
                    }

                    adjustColor(legend[i].WColor, points[i]);
                    pen.Color = legend[i].WColor.GetColor();

                    if (BlockSize == 1)
                        bMap.SetPixel(x, y, legend[i].WColor.GetColor());
                    else
                        g.FillRectangle(pen.Brush, x, y, BlockSize, BlockSize);
                        //g.DrawRectangle(pen, x, y, BlockSize-1, BlockSize-1);  //
                    legend[i].SCount++;
                    ++pcount;
                }
                ++rcount;
            }
            return pcount;
        }

        private static Serie[] initLegend(PlotPoint[] points) {
            Serie[] rst = new Serie[points.Length];
            int r, g, b;
            ColorLine cl = new ColorLine();
            for (int i = 0; i < rst.Length; i++) {
                cl.newColor(out r, out g, out b);
                rst[i] = new Serie(points[i].SeriesLabel, new WebColor(r, g, b));
            }
            return rst;
        }
        private static RectangleF point2Pixel(RectangleF src, Rectangle dest) {
            return new RectangleF(0, 0, (src.Width / dest.Width) * 2, (src.Height / dest.Height) * 2);
        }
        void LoadRowPoints(PlotPoint[] rst, DataRow dr, DataSet ds) {
            object o;
            for(int i=0;i<rst.Length;i++){
                try {
                    rst[i].X = (int)dr[0];
                } catch (InvalidCastException) {
                    continue;
                }
                o = dr[rst[i].ColumnLabel];
                if (o is DBNull)
                    rst[i].Y = Settings.AXI_UNDEF;
                else {
                    rst[i].Y = (int)o;
                    rst[i].Hits = (int)dr["HITS"];
                }
            }
        }
        PlotPoint[] InitRowPoints(DataSet ds) {
            PlotPoint[] rst = new PlotPoint[(ds.Tables[0].Columns.Count-2)];
            for (int i = 0; i < rst.Length; i++)
                rst[i] = new PlotPoint();           
            for (int i = 1; i < ds.Tables[0].Columns.Count - 1; i++) {
                rst[i - 1].ColumnLabel = ds.Tables[0].Columns[i].ColumnName;
                rst[i - 1].SeriesLabel = ds.Tables[0].Columns[i].ColumnName;
            }
            foreach (DataRow dr in ds.Tables[0].Rows) {
                for (int i = 1; i < ds.Tables[0].Columns.Count - 1; i++) {
                    if (dr[i] is DBNull) continue;//the column associated with hits
                    int val = (int)dr[ds.Tables[0].Columns.Count-1];//the hits column
                    if (rst[i-1].HitMin == Settings.AXI_UNDEF || val < rst[i-1].HitMin)
                        rst[i-1].HitMin = val;
                    if (rst[i-1].HitMax == Settings.AXI_UNDEF || val > rst[i-1].HitMax)
                        rst[i-1].HitMax = val;
                }
            }
            foreach (PlotPoint pp in rst)
                pp.HitDif = Math.Abs(pp.HitMax - pp.HitMin);
            return rst;
        }
    }
}