using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using ChamChat.Models;


namespace ChamChat
{
    public class Graph
    {
        // Rule: Max size of image is limietd by Control.Location as 2^15 = 32768 pix.
        private const float _MaxSizeOfImage = 32768F;

        public Int32 MaxSizeOfImage { get { return (int)Math.Round(_MaxSizeOfImage); } }

        public Graph(Profile Profile, Size SizeOfView, Client Client)
        {
            _Profile = Profile;
            _SizeOfView = SizeOfView;
            _Client = Client;
            _Width = _SizeOfView.Width;
            _Height = _SizeOfView.Height;
            _HrsInView = 24;

            // Pens
            _Pen = new Pen(Color.Black, 3); ;
            _PenTemp = new Pen(Color.Red, 3); ;
            _PenRH = new Pen(Color.Blue, 3); ;
            _PenPreTemp = new Pen(Color.Tomato, 1);
            _PenPreTemp.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
            _PenLoopBox = new Pen(Color.Green, 2);
            _PenLoopBox.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
            
            _PenStepBorder = new Pen(Color.LightGray, 1); ;
            _PenStepBorder.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
            _Font = new Font("Arial", 8);
            _MarginY = (float)Math.Round(_Height * 0.07);
            _MarginX = Math.Min(20,(float)Math.Round(_Width * 0.05));
        }


        private float _MarginY = 20;
        private float _MarginX = 20;
        private Size _SizeOfView;
        private Font _Font;
        private Pen _PenStepBorder;
        private Pen _PenPreTemp;
        private Pen _PenTemp;
        private Pen _PenRH;
        private Pen _Pen;
        private Pen _PenLoopBox;

        private Client _Client; public Client Client { get { return _Client; } }
        private Profile _Profile; public Profile Profile { get { return _Profile; } }
        private Bitmap _Image;

        private double _maxY = double.MinValue;
        private double _minY = double.MaxValue;
        private float _HrsInView = 24; public float HrsInView { get { return _HrsInView; } set { _HrsInView = value; } }
        private double _maxX = 6; // in minutes
        private float _Height; public Int32 Height { get { return (int)Math.Round(_Height); } set { _Height = value; } }
        private float _Width; public Int32 Width { get { return (int)Math.Round(_Width); } }
        private float _maxStepDisplayDuration = 240; public Double MaxStepDisplayDuration { set { _maxStepDisplayDuration = (float)value; } }
        float[,] _StepBorders; // Set while drawing



        private void SetScale()
        {
            _Width = _SizeOfView.Width;
            _minY = Double.MaxValue;
            _maxY = Double.MinValue;
            _maxX = 0;

            foreach (Step s in _Profile.Steps)
            {
                // Temperature
                _minY = Math.Min(_minY, s.T);
                _maxY = Math.Max(_maxY, s.T);

                if (_Client.Parameters.Contains(Parameter.PreTemp))
                {
                    _maxY = Math.Max(_maxY, s.PreTemp);
                    _minY = Math.Min(_minY, s.PreTemp);
                }

                // Shorten if steps are displayed cropped
                if (s.DurationMin > _maxStepDisplayDuration)
                    _maxX += _maxStepDisplayDuration;
                else
                    _maxX += s.DurationMin;
            }

            // Take a margin
            _maxY += Math.Abs(0.15 * _maxY);
            _minY -= Math.Abs(0.07 * _minY);


            // Max number of hours in 1 view
            float pixelsPerMinute = _SizeOfView.Width / (60 * _HrsInView);
            
            
            _Width = (float)Math.Round(pixelsPerMinute * (float)_maxX);
            _Width = Math.Min(_MaxSizeOfImage, _Width);
            _Width = Math.Max(_SizeOfView.Width, _Width);

            _maxY = Math.Max(100, _maxY); // Scale is 100 minimum (for RH)
        }


        private float YtoCanvas(Double y)
        {
            float a = (_Height - 2 * _MarginY) / ((float)_minY - (float)_maxY);
            float b = _MarginY - a * (float)_maxY;
            return a * (float)y + b;
        }
        private float XtoCanvas(Double x)
        {
            float a = ((float)_Width - 2 * _MarginX) / (float)_maxX;
            float b = _MarginX;
            return a * (float)x + b;
        }


        private void Create()
        {
            _Image = new Bitmap((int)_Width, (int)_Height);
            Graphics g = Graphics.FromImage(_Image);
            g.Clear(Color.White);

            // Horizontal axis
            g.DrawLine(_Pen, 0, YtoCanvas(0), _Width, YtoCanvas(0));

            // Temperature
            float X1 = 0;
            float X2 = 0;
            double T1 = 25;
            double T2 = 0;
            double RH1 = 50;
            double RH2 = 50;
            _StepBorders = new float[_Profile.Steps.Count, 2];

            g.DrawLine(_PenStepBorder, XtoCanvas(X1), 0, XtoCanvas(X1), _Height);

            // Draw from room temperature of 25 °C to first start
            g.DrawLine(_PenTemp, XtoCanvas(X1), YtoCanvas(T1), XtoCanvas(X2), YtoCanvas(_Profile.Steps[0].T));


            for (int i = 0; i < _Profile.Steps.Count; i++)
            {
                Step s = _Profile.Steps[i];


                T2 = s.T;
                RH2 = s.RH;
                X2 = X1 + (float)s.DurationMin;

                // Draw the connecting vertical line (infinite ramp)                
                if (!s.RampCtrlT)
                {
                    g.DrawLine(_PenTemp, XtoCanvas(X1), YtoCanvas(T1), XtoCanvas(X1), YtoCanvas(T2));
                    T1 = s.T;
                }
                if (!s.RampCtrlRH && s.RH > 0)
                {
                    g.DrawLine(_PenRH, XtoCanvas(X1), YtoCanvas(RH1), XtoCanvas(X1), YtoCanvas(RH2));
                    RH1 = s.RH;
                }

                // Draw the profile line
                if (s.DurationMin > _maxStepDisplayDuration)
                {
                    // Shrink period with dotted line
                    X2 = X1 + _maxStepDisplayDuration;

                    if (RH2 > 0)
                    {
                        if (s.RampCtrlRH)  // Draw RH ramp with dots in the middle
                        {
                            float x1 = XtoCanvas(X1); float x2 = XtoCanvas(X2);
                            float y1 = YtoCanvas(RH1); float y2 = YtoCanvas(RH2);
                            PointF P1 = new PointF(x1, y1);
                            PointF P2 = new PointF(x1 + 0.25F * (x2 - x1), y1 + 0.25F * (y2 - y1));
                            PointF P3 = new PointF(x1 + 0.75F * (x2 - x1), y1 + 0.75F * (y2 - y1));
                            PointF P4 = new PointF(x2, y2);
                            g.DrawLine(_PenRH, P1, P2);
                            _PenRH.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
                            g.DrawLine(_PenRH, P2, P3);
                            _PenRH.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
                            g.DrawLine(_PenRH, P3, P4);
                        }
                        else // Draw straight RH line with dots in the middle
                        {
                            g.DrawLine(_PenRH, XtoCanvas(X1), YtoCanvas(RH1), XtoCanvas(X1 + 0.25 * _maxStepDisplayDuration), YtoCanvas(RH2));
                            _PenRH.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
                            g.DrawLine(_PenRH, XtoCanvas(X1 + 0.25 * _maxStepDisplayDuration), YtoCanvas(RH1), XtoCanvas(X1 + 0.75 * _maxStepDisplayDuration), YtoCanvas(RH2));
                            _PenRH.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
                            g.DrawLine(_PenRH, XtoCanvas(X1 + 0.75 * _maxStepDisplayDuration), YtoCanvas(RH1), XtoCanvas(X2), YtoCanvas(RH2));
                        }
                    }

                    if (s.RampCtrlT) // Draw T ramp with dots in the middle
                    {
                        float x1 = XtoCanvas(X1); float x2 = XtoCanvas(X2);
                        float y1 = YtoCanvas(T1); float y2 = YtoCanvas(T2);
                        PointF P1 = new PointF(x1, y1);
                        PointF P2 = new PointF(x1 + 0.25F * (x2 - x1), y1 + 0.25F * (y2 - y1));
                        PointF P3 = new PointF(x1 + 0.75F * (x2 - x1), y1 + 0.75F * (y2 - y1));
                        PointF P4 = new PointF(x2, y2);
                        g.DrawLine(_PenTemp, P1, P2);
                        _PenTemp.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
                        g.DrawLine(_PenTemp, P2, P3);
                        _PenTemp.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
                        g.DrawLine(_PenTemp, P3, P4);
                    }
                    else // Draw straight T line with dots in the middle
                    {
                        g.DrawLine(_PenTemp, XtoCanvas(X1), YtoCanvas(T1), XtoCanvas(X1 + 0.25 * _maxStepDisplayDuration), YtoCanvas(T2));
                        _PenTemp.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
                        g.DrawLine(_PenTemp, XtoCanvas(X1 + 0.25 * _maxStepDisplayDuration), YtoCanvas(T1), XtoCanvas(X1 + 0.75 * _maxStepDisplayDuration), YtoCanvas(T2));
                        _PenTemp.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
                        g.DrawLine(_PenTemp, XtoCanvas(X1 + 0.75 * _maxStepDisplayDuration), YtoCanvas(T1), XtoCanvas(X2), YtoCanvas(T2));
                    }



                }
                else // Period is shorter than _maxStepDisplayDuration
                {
                    if (RH2 > 0)
                    {
                        if (RH2 == T2)
                            g.DrawLine(_PenRH, XtoCanvas(X1), YtoCanvas(RH1) + _PenRH.Width / 2, XtoCanvas(X2), YtoCanvas(RH2) + _PenRH.Width / 2);
                        else
                            g.DrawLine(_PenRH, XtoCanvas(X1), YtoCanvas(RH1), XtoCanvas(X2), YtoCanvas(RH2));
                    }

                    if (T2 == RH2)
                        g.DrawLine(_PenTemp, XtoCanvas(X1), YtoCanvas(T1) - _PenTemp.Width / 2, XtoCanvas(X2), YtoCanvas(T2) - _PenTemp.Width / 2);
                    else
                        g.DrawLine(_PenTemp, XtoCanvas(X1), YtoCanvas(T1), XtoCanvas(X2), YtoCanvas(T2));
                }


                // Draw temperature text (below if RH > T or if T < 0)
                if (!s.RampCtrlT)
                {
                    string sTemp = String.Format("{0:0}", s.T);
                    if (s.T <= 0 || RH2 > T2)
                        g.DrawString(sTemp, _Font, new SolidBrush(_PenTemp.Color), new PointF(XtoCanvas(X1) + 2, YtoCanvas(s.T) + 2));
                    else
                        g.DrawString(sTemp, _Font, new SolidBrush(_PenTemp.Color), new PointF(XtoCanvas(X1) + 2, YtoCanvas(s.T) - g.MeasureString(sTemp, _Font).Height - 2));

                }

                // Draw RH text (always below line)
                if (!s.RampCtrlRH && s.RH > 0)
                {
                    string sRH = String.Format("{0:0}", s.RH);
                    g.DrawString(sRH, _Font, new SolidBrush(_PenRH.Color), new PointF(XtoCanvas(X1) + 2, YtoCanvas(s.RH) + 2));
                }


                // Draw pre-temp 
                if (_Client.Parameters.Contains(Parameter.PreTemp))
                    g.DrawLine(_PenPreTemp, XtoCanvas(X1), YtoCanvas(s.PreTemp), XtoCanvas(X2), YtoCanvas(s.PreTemp));






                // Draw step border
                string stepInfo = String.Format("{0:00}", s.PK + 1);
                g.DrawLine(_PenStepBorder, XtoCanvas(X2), 0, XtoCanvas(X2), _Height);
                float h = g.MeasureString(stepInfo, _Font).Height;
                float w = g.MeasureString(stepInfo, _Font).Width;
                if (w < XtoCanvas(X2) - XtoCanvas(X1))
                    g.DrawString(stepInfo, _Font, Brushes.Black, new PointF(XtoCanvas(X1) + 2, _Height - h - 2));

                // Add to stepBorders
                _StepBorders[i, 0] = X1;
                _StepBorders[i, 1] = X2;


                T1 = T2;
                RH1 = RH2;
                X1 = X2;
            }


            // Loops boxes
            foreach (Loop loop in _Profile.Loops)
            {
                float xLeft = _StepBorders[loop.First, 0];
                float xRight = _StepBorders[loop.Last, 1];
                g.DrawRectangle(_PenLoopBox, XtoCanvas(xLeft) + 3, _MarginY, XtoCanvas(xRight) - XtoCanvas(xLeft) - 6, _Height - 2 * _MarginY);
                string l = string.Format("{0:0}X", loop.N);
                g.DrawString(l, _Font, new SolidBrush(_PenLoopBox.Color), new PointF(XtoCanvas(xLeft) + 6, _MarginY));
            }

            //g.DrawString(_Width.ToString(), _Font, Brushes.Black, new PointF(0, 0));

            g.Dispose();
        }


        public Bitmap GetImage()
        {
            SetScale();
            Create();
            return _Image;
        }

        public Int32 StepAtMousePosition(Int32 X)
        {
            for (int i = 0; i < _StepBorders.GetLength(0); i++)
                if (X > XtoCanvas(_StepBorders[i, 0]) && X < XtoCanvas(_StepBorders[i, 1]))
                    return i;
            return -1;
        }


    }
}
