//
// NPlot - A charting library for .NET
// 
// LinePlot.cs
// Copyright (C) 2003-2006 Matt Howlett and others.
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
// 3. Neither the name of NPlot nor the names of its contributors may
//    be used to endorse or promote products derived from this software without
//    specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
//
//    Copyright (C) 2010 Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Drawing;

using NPlot;


namespace Hiperscan.NPlotEngine
{

    public class CustomLinePlot : NPlot.BaseSequencePlot, NPlot.IPlot, NPlot.ISequencePlot
    {
        public CustomLinePlot()
        {
        }

        public CustomLinePlot(object data_source)
        {
            this.DataSource = data_source;
        }

        public CustomLinePlot(object ordinate_data, object abscissa_data)
        {
            this.OrdinateData = ordinate_data;
            this.AbscissaData = abscissa_data;
        }

        public void DrawLineOrShadow(Graphics g, PhysicalAxis x_axis, PhysicalAxis y_axis, bool draw_shadow)
        {
            Pen shadow_pen = null;
            if (draw_shadow)
            {
                shadow_pen = (Pen)this.Pen.Clone();
                shadow_pen.Color = this.ShadowColor;
            }

            SequenceAdapter data = new SequenceAdapter(this.DataSource, this.DataMember, this.OrdinateData, this.AbscissaData);

            ITransform2D t = Transform2D.GetTransformer(x_axis, y_axis);

            int point_count = data.Count;

            if (data.Count == 0)
                return;

            // clipping is now handled assigning a clip region in the
            // graphic object before this call
            if (point_count == 1)
            {
                PointF physical = t.Transform(data[0]);

                if (draw_shadow)
                {
                    g.DrawLine(shadow_pen,
                               physical.X - 0.5f + this.ShadowOffset.X,
                               physical.Y + this.ShadowOffset.Y,
                               physical.X + 0.5f + this.ShadowOffset.X,
                               physical.Y + this.ShadowOffset.Y);
                }
                else
                {
                    g.DrawLine(Pen, physical.X - 0.5f, physical.Y, physical.X + 0.5f, physical.Y);
                }
            }
            else
            {
                // prepare for clipping
                double left_cutoff  = x_axis.PhysicalToWorld(x_axis.PhysicalMin, false);
                double right_cutoff = x_axis.PhysicalToWorld(x_axis.PhysicalMax, false);

                if (left_cutoff > right_cutoff)
                {
                    CustomLinePlot.Swap(ref left_cutoff, ref right_cutoff);
                }

                if (draw_shadow)
                {
                    // correct cut-offs
                    double shadow_correction = 
                        x_axis.PhysicalToWorld(ShadowOffset, false) - x_axis.PhysicalToWorld(new Point(0, 0), false);

                    left_cutoff  -= shadow_correction;
                    right_cutoff -= shadow_correction;
                }

                // TKL: patch to speed things up if zoomed out
                PointF last_p2 = PointF.Empty;
                for (int i = 1; i < point_count; ++i)
                {
                    // check to see if any values null. If so, then continue.
                    double dx1 = data[i - 1].X;
                    double dx2 = data[i].X;
                    double dy1 = data[i - 1].Y;
                    double dy2 = data[i].Y;
                    if (double.IsNaN(dx1) || double.IsNaN(dy1) ||
                        double.IsNaN(dx2) || double.IsNaN(dy2))
                    {
                        continue;
                    }

                    // do horizontal clipping here, to speed up
                    if ((dx1 < left_cutoff || right_cutoff < dx1) &&
                        (dx2 < left_cutoff || right_cutoff < dx2))
                    {
                        continue;
                    }

                    // else draw line.
                    PointF p1 = t.Transform(data[i - 1]);
                    PointF p2 = t.Transform(data[i]);

                    // TKL: patch to speed things up if zoomed out
                    if (last_p2 == PointF.Empty)
                        last_p2 = p1;

                    if ((Math.Pow(p2.X - last_p2.X, 2f) + Math.Pow(p2.Y - last_p2.Y, 2f) < 16f) && i < point_count - 1)
                        continue;

                    p1 = last_p2;
                    last_p2 = p2;
                    //                    // when very far zoomed in, points can fall ontop of each other,
                    //                    // and g.DrawLine throws an overflow exception
                    //                    if (p1.Equals(p2))
                    //                        continue;

                    if (draw_shadow)
                    {
                        g.DrawLine(shadow_pen,
                                   p1.X + ShadowOffset.X,
                                   p1.Y + ShadowOffset.Y,
                                   p2.X + ShadowOffset.X,
                                   p2.Y + ShadowOffset.Y);
                    }
                    else
                    {
                        g.DrawLine(Pen, p1.X, p1.Y, p2.X, p2.Y);
                    }
                }
            }

        }

        public void Draw(Graphics g, PhysicalAxis x_axis, PhysicalAxis y_axis)
        {
            if (this.Shadow)
                this.DrawLineOrShadow(g, x_axis, y_axis, true);

            this.DrawLineOrShadow(g, x_axis, y_axis, false);
        }

        public Axis SuggestXAxis()
        {
            SequenceAdapter data = new SequenceAdapter(this.DataSource, this.DataMember, this.OrdinateData, this.AbscissaData);

            return data.SuggestXAxis();
        }

        public Axis SuggestYAxis()
        {
            SequenceAdapter data = new SequenceAdapter(this.DataSource, this.DataMember, this.OrdinateData, this.AbscissaData);

            return data.SuggestYAxis();
        }

        public virtual void DrawInLegend(Graphics g, Rectangle start_end)
        {
            g.DrawLine(Pen, start_end.Left, (start_end.Top + start_end.Bottom) / 2,
                       start_end.Right, (start_end.Top + start_end.Bottom) / 2);
        }

        public static void Swap(ref double a, ref double b)
        {
            double c = a;
            a = b;
            b = c;
        }

        public bool Shadow        { get; set; } = false;
        public Color ShadowColor  { get; set; } = Color.FromArgb(100, 100, 100);
        public Point ShadowOffset { get; set; } = new Point(1, 1);
        public Pen Pen            { get; set; } = new Pen(Color.Black);

        public System.Drawing.Color Color
        {
            set
            {
                if (this.Pen != null)
                    this.Pen.Color = value;
                else
                    this.Pen = new Pen(value);
            }
            get
            {
                return Pen.Color;
            }
        }
    }
}