using System;
using System.Collections.Generic;
using System.Text;

using System.Windows.Forms;
using System.Drawing;

using Chart;

namespace Chart
{
    /// <summary>
    /// Performer of chart
    /// </summary>
    public class ChartPerformer
    {
        /// <summary>
        /// Shift delta
        /// </summary>
        const int DELTA = 5;

        /// <summary>
        /// Control to draw
        /// </summary>
        protected Control control = null;




        /// <summary>
        /// internal insets 
        /// </summary>
        protected int[,] internalInsets = new int[,] { { 0, 0 }, { 0, 0 } };

        /// <summary>
        /// Array of series
        /// </summary>
        protected List<ISeries> series = new List<ISeries>();


        protected Dictionary<ISeries, ISeriesPainter> painters = new Dictionary<ISeries, ISeriesPainter>();

        /// <summary>
        /// Size of canvas
        /// </summary>
        protected int[] size = new int[2];

        /// <summary>
        /// Physical size
        /// </summary>
        protected double[,] dSize = new double[2, 2];

        /// <summary>
        /// Insets
        /// </summary>
        protected int[,] insets;

        /// <summary>
        /// Temporary buffer
        /// </summary>
        protected Image iTemp;

        /// <summary>
        /// Buffer
        /// </summary>
        protected Image image;

        /// <summary>
        /// Auxiliary variable
        /// </summary>
        protected int xp;

        /// <summary>
        /// Auxiliary variable
        /// </summary>
        protected int yp;

        /// <summary>
        /// Auxiliary variable
        /// </summary>
        protected int xc;

        /// <summary>
        /// Auxiliary variable
        /// </summary>
        protected int yc;

        /// <summary>
        /// Rectangle for drawing
        /// </summary>
        protected Rectangle r = new Rectangle();

        /// <summary>
        /// Painter of coordinates
        /// </summary>
        protected ICoordPainter coordPainter = null;

        /// <summary>
        /// The "is mouse moved" sign
        /// </summary>
        protected bool isMoved;

        /// <summary>
        /// Pen for coordinates drawing
        /// </summary>
        protected Pen linePen;

        /// <summary>
        /// Source rectangle
        /// </summary>
        protected Rectangle sR;

        /// <summary>
        /// Descination rectangle
        /// </summary>
        protected Rectangle dR;

        /// <summary>
        /// The invert x - axis sign
        /// </summary>
        protected bool invertX = false;


        /// <summary>
        /// The invert y - axis sign
        /// </summary>
        protected bool invertY = false;

        /// <summary>
        /// Background color
        /// </summary>
        protected Color backColor;

        /// <summary>
        /// Step along x axis
        /// </summary>
        protected int stepX = 0;

        /// <summary>
        /// Step alogn y axis
        /// </summary>
        protected int stepY = 0;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="control">Control to draw</param>
        /// <param name="insets">Insets</param>
        public ChartPerformer(Control control, int[,] insets)
        {
            this.control = control;
            this.insets = insets;
            control.MouseDown += new MouseEventHandler(onMouseDown);
            control.MouseUp += new MouseEventHandler(onMouseUp);
            control.MouseMove += new MouseEventHandler(onMouseMove);
            control.MouseLeave += new EventHandler(onMouseLeave);
            linePen = new Pen(Color.Black);
            sR = new Rectangle();
            dR = new Rectangle();
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="size">Size</param>
        /// <param name="insets">insets</param>
        public ChartPerformer(int[] size, int[,] insets, Color backColor)
        {
            this.size = size;
            this.insets = insets;
            this.backColor = backColor;
            linePen = new Pen(Color.Black);
            sR = new Rectangle();
            dR = new Rectangle();
            image = new Bitmap(size[0], size[1]);
            iTemp = new Bitmap(size[0], size[1]);
            Image im = image;
            Graphics g = Graphics.FromImage(im);
            Color back = bkgnd;
            g.FillRectangle(new SolidBrush(back), 0, 0, im.Width, im.Height);
            g.Dispose();
            im = iTemp;
            g = Graphics.FromImage(im);
            g.FillRectangle(new SolidBrush(back), 0, 0, im.Width, im.Height);
            g.Dispose();
        }

        /// <summary>
        /// Constuctor
        /// </summary>
        protected ChartPerformer()
        {
        }


        /// <summary>
        /// Adds histogram
        /// </summary>
        /// <param name="histogram">Histogram</param>
        /// <param name="color">Color</param>
 /*       public void AddHistogram(double[,] histogram, Color color)
        {
            histograms.Add(new object[] { histogram, color });
        }*/

        /// <summary>
        /// Access to size
        /// </summary>
        public double this[int i, int j]
        {
            get
            {
                return dSize[i, j];
            }
            set
            {
                dSize[i, j] = value;
            }
        }


        /// <summary>
        /// Step along x axis
        /// </summary>
        public int StepX
        {
            get
            {
                return stepX;
            }
            set
            {
                stepX = value;
            }
        }

        /// <summary>
        /// Step alogn y axis
        /// </summary>
        public int StepY
        {
            get
            {
                return stepY;
            }
            set
            {
                stepY = value;
            }
        }


        /// <summary>
        /// Internal insets
        /// </summary>
        public int[,] InternalInsets
        {
            get
            {
                return internalInsets;
            }
        }

        /// <summary>
        /// The invert x - axis sign
        /// </summary>
        public bool InvertX
        {
            get
            {
                return invertX;
            }
            set
            {
                invertX = value;
            }
        }

        /// <summary>
        /// The invert y - axis sign
        /// </summary>
        public bool InvertY
        {
            get
            {
                return invertY;
            }
            set
            {
                invertY = value;
            }
        }

        /// <summary>
        /// Painter of coordinates
        /// </summary>
        public ICoordPainter Coordinator
        {
            set
            {
                coordPainter = value;
            }
        }

        /// <summary>
        /// Adds series
        /// </summary>
        /// <param name="s">Series to add</param>
        public void AddSeries(ISeries s, Color color)
        {
            series.Add(s);
            painters[s] = new SimpleSeriesPainter(this, color);
        }


        public void AddSeries(ISeries s, ISeriesPainter painter)
        {
            series.Add(s);
            painters[s] = painter;
        }

        /// <summary>
        /// Access to series
        /// </summary>
        public ISeries this[int n]
        {
            get
            {
                return series[n];
            }
        }

        /// <summary>
        /// Count of series
        /// </summary>
        public int Count
        {
            get
            {
                return series.Count;
            }
        }

        /// <summary>
        /// The last series
        /// </summary>
        public ISeries Last
        {
            get
            {
                if (series.Count == 0)
                {
                    return null;
                }
                return this[Count - 1];
            }
        }

        /// <summary>
        /// Removes all series
        /// </summary>
        public void RemoveAll()
        {
            series.Clear();
            //histograms.Clear();
        }

        /// <summary>
        /// Removes a series
        /// </summary>
        /// <param name="s">The series to remove</param>
        public void Remove(ISeries s)
        {
            series.Remove(s);
            if (painters.ContainsKey(s))
            {
                painters.Remove(s);
            }
        }

        /// <summary>
        /// Size calculation
        /// </summary>
        public void CalculateSize()
        {
            for (int i = 0; i < Count; i++)
            {
                ISeries s = this[i];
                double[,] size = s.Size;
                if (i == 0)
                {
                    for (int j = 0; j < 2; j++)
                    {
                        for (int k = 0; k < 2; k++)
                        {
                            dSize[j, k] = size[j, k];
                        }
                    }
                    continue;
                }
                for (int j = 0; j < 2; j++)
                {
                    if (size[0, j] < dSize[0, j])
                    {
                        dSize[0, j] = size[0, j];
                    }
                    if (size[1, j] > dSize[1, j])
                    {
                        dSize[1, j] = size[1, j];
                    }
                }
            }
            //resizeHistograms();
            for (int i = 0; i < 2; i++)
            {
                double[] p = new double[3];
                double a = size[i] + internalInsets[i, 0] + internalInsets[i, 1];
                p[0] = (double)internalInsets[0, i] / a;
                p[1] = (double)size[i] / a;
                p[2] = (double)internalInsets[1, i] / a;
                double min = dSize[0, i];
                double max = dSize[1, i];
                dSize[0, i] = min - p[0] * (max - min);
                dSize[1, i] = max + p[2] * (max - min);
            }
            for (int i = 0; i < 2; i++)
            {
                if (dSize[0, i] == dSize[1, i])
                {
                    if (dSize[1, i] > 0)
                    {
                        dSize[0, i] = 0;
                        dSize[1, i] *= 1.1;
                    }
                    else if (dSize[1, i] < 0)
                    {
                        dSize[1, i] = 0;
                        dSize[0, i] *= 1.1;
                    }
                    else
                    {
                        dSize[0, i] = -1;
                        dSize[1, i] = 1;
                    }
                }
            }
            for (int i = 0; i < 2; i++)
            {
                for (int j = 0; j < 2; j++)
                {
                    if (Double.IsNaN(dSize[i, j]))
                    {
                        throw new Exception("NaN");
                    }
                }
            }
        }

        public void Transform(double[] x, int[] point)
        {
            if (InvertX)
            {
                point[0] = (int)((double)(size[0])
                    * ((x[0] - dSize[1, 0]) / (dSize[0, 0] - dSize[1, 0])));
            }
            else
            {
                point[0] = (int)((double)((size[0])
                    * (x[0] - dSize[0, 0]) / (dSize[1, 0] - dSize[0, 0])));
            }
            if (InvertY)
            {
                point[1] = size[1]
                    - (int)((double)((size[1])
                    * ((x[1] - dSize[1, 1]) / (dSize[0, 1] - dSize[1, 1]))));
            }
            else
            {
                point[1] = size[1] - (int)((double)((size[1])
                    * ((x[1] - dSize[0, 1]) / (dSize[1, 1] - dSize[0, 1]))));
            }
        }


        /// <summary>
        /// Size of canvas
        /// </summary>
        public int[] CanvasSize
        {
            get
            {
                return size;
            }
        }

        /// <summary>
        /// Resizes itself
        /// </summary>
        public void Resize()
        {
            if (control != null)
            {
                size[0] = control.Width - insets[0, 0] - insets[1, 0];
                size[1] = control.Height - insets[0, 1] - insets[1, 1];
                image = new Bitmap(size[0], size[1]);
                iTemp = new Bitmap(size[0], size[1]);
            }
            else
            {
                image = new Bitmap(size[0], size[1]);
                iTemp = new Bitmap(size[0], size[1]);
                Image im = image;
                Graphics g = Graphics.FromImage(im);
                Color back = bkgnd;
                g.FillRectangle(new SolidBrush(back), 0, 0, im.Width, im.Height);
                g.Dispose();
                im = iTemp;
                g = Graphics.FromImage(im);
                g.FillRectangle(new SolidBrush(back), 0, 0, im.Width, im.Height);
                g.Dispose();
            }
            CalculateSize();
        }

        /// <summary>
        /// Bitmap
        /// </summary>
        public Image Image
        {
            get
            {
                Bitmap bitmap = new Bitmap(size[0] + insets[0, 0] + insets[1, 0],
                    size[1] + insets[0, 1] + insets[1, 1]);
                Paint();
                Graphics g = Graphics.FromImage(bitmap);
                g.FillRectangle(new SolidBrush(backColor), 0, 0, bitmap.Width, bitmap.Height);
                g.DrawImage(image, insets[0, 0], insets[0, 1]);
                if (coordPainter != null)
                {
                    coordPainter.DrawCoord(g, insets, dSize, size);
                }
                g.Dispose();
                return bitmap;
            }
        }

        /// <summary>
        /// Paints itself
        /// </summary>
        public void Paint()
        {
            if (image == null)
            {
                return;
            }
            Brush brush = new SolidBrush(bkgnd);
            Graphics gi = Graphics.FromImage(image);
            gi.FillRectangle(brush, 0, 0, image.Width, image.Height);
            for (int i = 0; i < 2; i++)
            {
                for (int j = 0; j < 2; j++)
                {
                    if (Double.IsNaN(dSize[i, j]))
                    {
                        return;
                    }
                }
            }
            if (coordPainter != null)
            {
                coordPainter.DrawCoord(gi, dSize, size);
            }
            for (int i = 0; i < Count; i++)
            {
                ISeries s = this[i];
                ISeriesPainter p = null;
                if (painters.ContainsKey(s))
                {
                    p = painters[s];
                }
                else
                {
                    p = new SimpleSeriesPainter(this, Color.Black);
                    painters[s] = p;
                }
                if (p != null)
                {
                    p.Draw(s, gi);
                }
            }
            //drawHistograms(gi);
            gi.Dispose();
            gi = Graphics.FromImage(iTemp);
            gi.DrawImage(image, 0, 0);
            gi.Dispose();
        }




        /// <summary>
        /// Paints itself
        /// </summary>
        /// <param name="g">Graphics to paint</param>
        public void Paint(Graphics g)
        {
            if (image == null)
            {
                return;
            }
            g.DrawImage(iTemp, insets[0, 0], insets[0, 1]);
            for (int i = 0; i < 2; i++)
            {
                for (int j = 0; j < 2; j++)
                {
                    if (Double.IsNaN(dSize[i, j]))
                    {
                        return;
                    }
                }
            }
            if (coordPainter != null)
            {
                if (control != null)
                {
                    coordPainter.ClearInsets(control, insets);
                    Graphics gc = Graphics.FromHwnd(control.Handle);
                    coordPainter.DrawCoord(gc, insets, dSize, size);
                    gc.Dispose();
                }
            }
        }



        /// <summary>
        /// Performs all refresh operations
        /// </summary>
        public virtual void RefreshAll()
        {
            Resize();
            CalculateSize();
            Paint();
            if (coordPainter != null)
            {
                if (control != null)
                {
                    coordPainter.ClearInsets(control, insets);
                }
            }
            if (control != null)
            {
                Graphics g = Graphics.FromHwnd(control.Handle);
                Paint(g);
            }
        }

        /// <summary>
        /// Refreshs itself
        /// </summary>
        public void Refresh()
        {
            //Resize();
            //CalculateSize();
            Paint();
            if (coordPainter != null)
            {
                if (control != null)
                {
                    coordPainter.ClearInsets(control, insets);
                }
            }
            if (control != null)
            {
                control.Refresh();
            }
        }

        /// <summary>
        /// The temporary buffer
        /// </summary>
        public Image Buffer
        {
            get
            {
                return iTemp;
            }
        }

        /// <summary>
        /// Physical - screen transformation
        /// </summary>
        /// <param name="x">Physical x - coordinate</param>
        /// <param name="y">Physical y - coordinate</param>
        /// <param name="p">Screen coordinates</param>
        public void Transform(double x, double y, int[] p)
        {
            if (invertX)
            {
                p[0] = (int)((double)size[0] * ((x - dSize[1, 0]) / (dSize[0, 0] - dSize[1, 0])));
            }
            else
            {
                p[0] = (int)((double)size[0] * ((x - dSize[0, 0]) / (dSize[1, 0] - dSize[0, 0])));
            }
            if (invertY)
            {
                p[1] = size[1] - (int)((double)size[1] * ((y - dSize[1, 1]) /
                    (dSize[0, 1] - dSize[1, 1])));
            }
            else
            {
                p[1] = size[1] - (int)((double)size[1] * ((y - dSize[0, 1]) /
                    (dSize[1, 1] - dSize[0, 1])));
            }
        }

        /// <summary>
        /// On mouse down event handler
        /// </summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">The event arguments</param>
        protected void onMouseDown(object sender, MouseEventArgs e)
        {
            xp = e.X - insets[0, 0];
            yp = e.Y - insets[0, 1];
            isMoved = true;
        }

        /// <summary>
        /// Draws histogram
        /// </summary>
        /// <param name="o">Histogram object</param>
        /// <param name="g">Graphics</param>
        protected void drawHistogram(object[] o, Graphics g)
        {
            double[,] hist = (double[,])o[0];
            Color color = (Color)o[1];
            double step = hist[1, 0] - hist[0, 0];
            double[] x = new double[2];
            int[] il = new int[2];
            int[] ir = new int[2];
            Pen pen = new Pen(Color.Black);
            Brush brush = new SolidBrush(color);
            for (int i = 0; i < hist.GetLength(0); i++)
            {
                x[0] = hist[i, 0];
                x[1] = hist[i, 1];
                Transform(x, il);
                x[0] += step;
                x[1] = 0;
                Transform(x, ir);
                int w = ir[0] - il[0];
                int h = ir[1] - il[1];
                g.FillRectangle(brush, il[0], il[1], w, h);
                g.DrawRectangle(pen, il[0], il[1], w, h);
            }
        }


        /// <summary>
        /// Draws all histograms
        /// </summary>
        /// <param name="g">Graphics to draw</param>
  /*      protected void drawHistograms(Graphics g)
        {
            foreach (object[] o in histograms)
            {
                drawHistogram(o, g);
            }
        }*/


        /// <summary>
        /// On mouse up event handler
        /// </summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">The event arguments</param>
        protected void onMouseUp(object sender, MouseEventArgs e)
        {
            if ((xp < xc) & (yp < yc))
            {
                double xb = dSize[0, 0];
                double xd = dSize[1, 0] - dSize[0, 0];
                dSize[0, 0] = xb + (double)xp * xd / (double)(size[0]);
                dSize[1, 0] = xb + (double)xc * xd / (double)(size[0]);
                double yb = dSize[1, 1];
                double yd = dSize[1, 1] - dSize[0, 1];
                dSize[0, 1] = yb - (double)yc * yd / (double)(size[1]);
                dSize[1, 1] = yb - (double)yp * yd / (double)(size[1]);
                Refresh();
            }
            else
            {
                RefreshAll();
            }
            Graphics g = Graphics.FromImage(iTemp);
            g.DrawImage(image, 0, 0);
            g.Dispose();
            isMoved = false;
        }

        /// <summary>
        /// On mouse leave event handler
        /// </summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">The event arguments</param>
        protected void onMouseLeave(object sender, EventArgs e)
        {
            if (isMoved)
            {
                RefreshAll();
                isMoved = false;
            }

        }

        /// <summary>
        /// On mouse move event handler
        /// </summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">The event arguments</param>
        protected void onMouseMove(object sender, MouseEventArgs e)
        {
            if (!isMoved)
            {
                return;
            }
            xc = e.X - insets[0, 0];
            yc = e.Y - insets[0, 1];
            int xmin, ymin, xmax, ymax;
            if (xp > xc)
            {
                xmin = xc;
                xmax = xp;
            }
            else
            {
                xmin = xp;
                xmax = xc;
            }
            if (yp > yc)
            {
                ymin = yc;
                ymax = yp;
            }
            else
            {
                ymin = yp;
                ymax = yc;
            }
            Graphics g = Graphics.FromImage(iTemp);
            dR.X = xmin - DELTA;
            dR.Y = ymin - DELTA;
            dR.Width = 2 * DELTA + xmax - xmin;
            dR.Height = 2 * DELTA + ymax - ymin;
            sR.X = xmin - DELTA;
            sR.Y = ymin - DELTA;
            sR.Width = 2 * DELTA + xmax - xmin;
            sR.Height = 2 * DELTA + ymax - ymin;
            g.DrawImage(image, dR, sR, GraphicsUnit.Pixel);
            g.DrawRectangle(linePen, xmin, ymin, xmax - xmin, ymax - ymin);
            g.Dispose();
            g = Graphics.FromHwnd(control.Handle);
            dR.X = xmin - DELTA + insets[0, 0];
            dR.Y = ymin - DELTA + insets[0, 1];
            dR.Width = 2 * DELTA + xmax - xmin;
            dR.Height = 2 * DELTA + ymax - ymin;
            sR.X = xmin - DELTA;
            sR.Y = ymin - DELTA;
            sR.Width = 2 * DELTA + xmax - xmin;
            sR.Height = 2 * DELTA + ymax - ymin;
            g.DrawImage(iTemp, dR, sR, GraphicsUnit.Pixel);
            g.Dispose();
        }

        /// <summary>
        /// Resizes histogram
        /// </summary>
        /// <param name="o">Diagram object</param>
        protected void resizeHistogram(object[] o)
        {
            double[,] hist = o[0] as double[,];
            if (hist[0, 0] < dSize[0, 0])
            {
                dSize[0, 0] = hist[0, 0];
            }
            double a = hist[hist.GetLength(0) - 1, 0] + hist[1, 0] - hist[0, 0];
            if (a > dSize[1, 0])
            {
                dSize[1, 0] = a;
            }
            if (dSize[0, 1] > 0)
            {
                dSize[0, 1] = 0;
            }
            for (int i = 0; i < hist.GetLength(0); i++)
            {
                a = hist[i, 1];
                if (dSize[1, 1] < a)
                {
                    dSize[1, 1] = a;
                }
            }
        }

        /// <summary>
        /// Resizes all histograms
        /// </summary>
 /*       protected void resizeHistograms()
        {
            foreach (object[] o in histograms)
            {
                resizeHistogram(o);
            }
        }*/

        /// <summary>
        /// Background color
        /// </summary>
        protected Color bkgnd
        {
            get
            {
                if (control != null)
                {
                    return control.BackColor;
                }
                return backColor;
            }
        }
    }
}
