/*
 * NPlot - A charting library for .NET
 * 
 * PlotSurface2D.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.
 */

// #define DEBUG_BOUNDING_BOXES

using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace NPlot
{
    /// <summary>
    /// Implements the surface on which IDrawables are drawn. Is extended
    /// by Bitmap.PlotSurface2D, Windows.PlotSurface2D etc. TODO: better explanation.
    /// </summary>
    public class PlotSurface2D : IPlotSurface2D
    {
        /// <summary>
        /// Possible positions of the X axis.
        /// </summary>
        public enum XAxisPosition
        {
            /// <summary>
            /// X axis is on the top.
            /// </summary>
            Top = 1,
            //Center = 2,
            /// <summary>
            /// X axis is on the bottom.
            /// </summary>
            Bottom = 3,
        }

        /// <summary>
        /// Possible positions of the Y axis.
        /// </summary>
        public enum YAxisPosition
        {
            /// <summary>
            /// Y axis on the left.
            /// </summary>
            Left = 1,
            // Center
            /// <summary>
            /// Y axis on the right.
            /// </summary>
            Right = 3,
        }

        private readonly StringFormat titleDrawFormat_;

        private bool autoScaleAutoGeneratedAxes_;
        private bool autoScaleTitle_;
        private ArrayList axesConstraints_;
        private object bbTitleCache_;

        private object bbXAxis1Cache_;
        private object bbXAxis2Cache_;
        private object bbYAxis1Cache_;
        private object bbYAxis2Cache_;
        private ArrayList drawables_;
        private int legendZOrder_ = -1;
        private Legend legend_;
        private SortedList ordering_;
        private PhysicalAxis pXAxis1Cache_;
        private PhysicalAxis pXAxis2Cache_;
        private PhysicalAxis pYAxis1Cache_;
        private PhysicalAxis pYAxis2Cache_;
        private int padding_;
        private object plotAreaBoundingBoxCache_;
        private IRectangleBrush plotBackBrush_;

        private object plotBackColor_;
        private System.Drawing.Bitmap plotBackImage_;
        private SmoothingMode smoothingMode_;
        private Brush titleBrush_;
        private Font titleFont_;
        private string title_;
        private int uniqueCounter_;
        private Axis xAxis1_;
        private Axis xAxis2_;
        private ArrayList xAxisPositions_;
        private Axis yAxis1_;
        private Axis yAxis2_;
        private ArrayList yAxisPositions_;
        private ArrayList zPositions_;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public PlotSurface2D()
        {
            // only create this once.
            titleDrawFormat_ = new StringFormat();
            titleDrawFormat_.Alignment = StringAlignment.Center;

            Init();
        }

        /// <summary>
        /// The physical bounding box of the last drawn plot surface area is available here.
        /// </summary>
        public Rectangle PlotAreaBoundingBoxCache
        {
            get
            {
                if (plotAreaBoundingBoxCache_ == null)
                {
                    return Rectangle.Empty;
                }
                else
                {
                    return (Rectangle) plotAreaBoundingBoxCache_;
                }
            }
        }

        /// <summary>
        /// The physical XAxis1 that was last drawn.
        /// </summary>
        public PhysicalAxis PhysicalXAxis1Cache
        {
            get { return pXAxis1Cache_; }
        }

        /// <summary>
        /// The physical YAxis1 that was last drawn.
        /// </summary>
        public PhysicalAxis PhysicalYAxis1Cache
        {
            get { return pYAxis1Cache_; }
        }

        /// <summary>
        /// The physical XAxis2 that was last drawn.
        /// </summary>
        public PhysicalAxis PhysicalXAxis2Cache
        {
            get { return pXAxis2Cache_; }
        }

        /// <summary>
        /// The physical YAxis2 that was last drawn.
        /// </summary>
        public PhysicalAxis PhysicalYAxis2Cache
        {
            get { return pYAxis2Cache_; }
        }

        /// <summary>
        /// The bottom abscissa axis.
        /// </summary>
        public Axis XAxis1
        {
            get { return xAxis1_; }
            set { xAxis1_ = value; }
        }

        /// <summary>
        /// The left ordinate axis.
        /// </summary>
        public Axis YAxis1
        {
            get { return yAxis1_; }
            set { yAxis1_ = value; }
        }

        /// <summary>
        /// The top abscissa axis.
        /// </summary>
        public Axis XAxis2
        {
            get { return xAxis2_; }
            set { xAxis2_ = value; }
        }

        /// <summary>
        /// The right ordinate axis.
        /// </summary>
        public Axis YAxis2
        {
            get { return yAxis2_; }
            set { yAxis2_ = value; }
        }

        /// <summary>
        /// The chart title.
        /// </summary>
        public string Title
        {
            get { return title_; }
            set { title_ = value; }
        }

        /// <summary>
        /// The plot title font.
        /// </summary>
        public Font TitleFont
        {
            get { return titleFont_; }
            set { titleFont_ = value; }
        }

        /// <summary>
        /// The distance in pixels to leave between of the edge of the bounding rectangle
        /// supplied to the Draw method, and the markings that make up the plot.
        /// </summary>
        public int Padding
        {
            get { return padding_; }
            set { padding_ = value; }
        }

        /// <summary>
        /// Sets the title to be drawn using a solid brush of this color.
        /// </summary>
        public Color TitleColor
        {
            set { titleBrush_ = new SolidBrush(value); }
        }

        /// <summary>
        /// The brush used for drawing the title.
        /// </summary>
        public Brush TitleBrush
        {
            get { return titleBrush_; }
            set { titleBrush_ = value; }
        }

        /// <summary>
        /// A color used to paint the plot background. Mutually exclusive with PlotBackImage and PlotBackBrush
        /// </summary>
        public Color PlotBackColor
        {
            set
            {
                plotBackColor_ = value;
                plotBackBrush_ = null;
                plotBackImage_ = null;
            }
        }

        /// <summary>
        /// An imaged used to paint the plot background. Mutually exclusive with PlotBackColor and PlotBackBrush
        /// </summary>
        public System.Drawing.Bitmap PlotBackImage
        {
            set
            {
                plotBackImage_ = value;
                plotBackColor_ = null;
                plotBackBrush_ = null;
            }
        }

        /// <summary>
        /// A Rectangle brush used to paint the plot background. Mutually exclusive with PlotBackColor and PlotBackBrush
        /// </summary>
        public IRectangleBrush PlotBackBrush
        {
            set
            {
                plotBackBrush_ = value;
                plotBackColor_ = null;
                plotBackImage_ = null;
            }
        }

        /// <summary>
        /// Smoothing mode to use when drawing plots.
        /// </summary>
        public SmoothingMode SmoothingMode
        {
            get { return smoothingMode_; }
            set { smoothingMode_ = value; }
        }

        /// <summary>
        /// Adds a drawable object to the plot surface with z-order 0. If the object is an IPlot,
        /// the PlotSurface2D axes will also be updated.
        /// </summary>
        /// <param name="p">The IDrawable object to add to the plot surface.</param>
        public void Add(IDrawable p)
        {
            Add(p, 0);
        }

        /// <summary>
        /// Adds a drawable object to the plot surface. If the object is an IPlot,
        /// the PlotSurface2D axes will also be updated.
        /// </summary>
        /// <param name="p">The IDrawable object to add to the plot surface.</param>
        /// <param name="zOrder">The z-ordering when drawing (objects with lower numbers are drawn first)</param>
        public void Add(IDrawable p, int zOrder)
        {
            Add(p, XAxisPosition.Bottom, YAxisPosition.Left, zOrder);
        }

        /// <summary>
        /// Adds a drawable object to the plot surface against the specified axes with
        /// z-order of 0. If the object is an IPlot, the PlotSurface2D axes will also
        /// be updated.
        /// </summary>
        /// <param name="p">the IDrawable object to add to the plot surface</param>
        /// <param name="xp">the x-axis to add the plot against.</param>
        /// <param name="yp">the y-axis to add the plot against.</param>
        public void Add(IDrawable p, XAxisPosition xp, YAxisPosition yp)
        {
            Add(p, xp, yp, 0);
        }

        /// <summary>
        /// Adds a drawable object to the plot surface against the specified axes. If
        /// the object is an IPlot, the PlotSurface2D axes will also be updated.
        /// </summary>
        /// <param name="p">the IDrawable object to add to the plot surface</param>
        /// <param name="xp">the x-axis to add the plot against.</param>
        /// <param name="yp">the y-axis to add the plot against.</param>
        /// <param name="zOrder">The z-ordering when drawing (objects with lower numbers are drawn first)</param>
        public void Add(IDrawable p, XAxisPosition xp, YAxisPosition yp, int zOrder)
        {
            drawables_.Add(p);
            xAxisPositions_.Add(xp);
            yAxisPositions_.Add(yp);
            zPositions_.Add((double) zOrder);
            // fraction is to make key unique. With 10 million plots at same z, this buggers up.. 
            double fraction = (double) (++uniqueCounter_)/10000000.0f;
            ordering_.Add(zOrder + fraction, drawables_.Count - 1);

            // if p is just an IDrawable, then it can't affect the axes.
            if (p is IPlot)
            {
                UpdateAxes(false);
            }
        }

        /// <summary>
        /// Clears the plot and resets all state to the default.
        /// </summary>
        public void Clear()
        {
            Init();
        }

        /// <summary>
        /// Legend to use. If this property is null [default], then the plot
        /// surface will have no corresponding legend.
        /// </summary>
        public Legend Legend
        {
            get { return legend_; }
            set { legend_ = value; }
        }

        /// <summary>
        /// Add an axis constraint to the plot surface. Axes constraints give you
        /// control over where NPlot positions each axes, and the world - pixel
        /// ratio.
        /// </summary>
        /// <param name="constraint">The axis constraint to add.</param>
        public void AddAxesConstraint(AxesConstraint constraint)
        {
            axesConstraints_.Add(constraint);
        }

        /// <summary>
        /// Whether or not the title will be scaled according to size of the plot surface.
        /// </summary>
        public bool AutoScaleTitle
        {
            get { return autoScaleTitle_; }
            set { autoScaleTitle_ = value; }
        }

        /// <summary>
        /// When plots are added to the plot surface, the axes they are attached to
        /// are immediately modified to reflect data of the plot. If
        /// AutoScaleAutoGeneratedAxes is true when a plot is added, the axes will
        /// be turned in to auto scaling ones if they are not already [tick marks,
        /// tick text and label size scaled to size of plot surface]. If false,
        /// axes will not be autoscaling.
        /// </summary>
        public bool AutoScaleAutoGeneratedAxes
        {
            get { return autoScaleAutoGeneratedAxes_; }
            set { autoScaleAutoGeneratedAxes_ = value; }
        }

        /// <summary>
        /// Remove a drawable object.
        /// Note that axes are not updated.
        /// </summary>
        /// <param name="p">Drawable to remove.</param>
        /// <param name="updateAxes">if true, the axes are updated.</param>
        public void Remove(IDrawable p, bool updateAxes)
        {
            int index = drawables_.IndexOf(p);
            if (index < 0)
                return;
            drawables_.RemoveAt(index);
            xAxisPositions_.RemoveAt(index);
            yAxisPositions_.RemoveAt(index);
            zPositions_.RemoveAt(index);

            if (updateAxes)
            {
                UpdateAxes(true);
            }

            RefreshZOrdering();
        }

        /// <summary>
        /// Gets an array list containing all drawables currently added to the PlotSurface2D.
        /// </summary>
        public ArrayList Drawables
        {
            get { return drawables_; }
        }

        /// <summary>
        /// Setting this value determines the order (relative to IDrawables added to the plot surface)
        /// that the legend is drawn.
        /// </summary>
        public int LegendZOrder
        {
            get { return legendZOrder_; }
            set { legendZOrder_ = value; }
        }

        /// <summary>
        /// Performs a hit test with the given point and returns information
        /// about the object being hit.
        /// </summary>
        /// <param name="p">The point to test.</param>
        /// <returns></returns>
        public ArrayList HitTest(Point p)
        {
            ArrayList a = new ArrayList();

            // this is the case if PlotSurface has been cleared.
            if (bbXAxis1Cache_ == null)
            {
                return a;
            }
            else if (bbXAxis1Cache_ != null && ((Rectangle) bbXAxis1Cache_).Contains(p))
            {
                a.Add(xAxis1_);
                return a;
            }
            else if (bbYAxis1Cache_ != null && ((Rectangle) bbYAxis1Cache_).Contains(p))
            {
                a.Add(yAxis1_);
                return a;
            }
            else if (bbXAxis2Cache_ != null && ((Rectangle) bbXAxis2Cache_).Contains(p))
            {
                a.Add(xAxis2_);
                return a;
            }
            else if (bbXAxis2Cache_ != null && ((Rectangle) bbYAxis2Cache_).Contains(p))
            {
                a.Add(yAxis2_);
                return a;
            }
            else if (bbTitleCache_ != null && ((Rectangle) bbTitleCache_).Contains(p))
            {
                a.Add(this);
                return a;
            }
            else if (plotAreaBoundingBoxCache_ != null && ((Rectangle) plotAreaBoundingBoxCache_).Contains(p))
            {
                a.Add(this);
                return a;
            }

            return a;
        }

        private void Init()
        {
            drawables_ = new ArrayList();
            xAxisPositions_ = new ArrayList();
            yAxisPositions_ = new ArrayList();
            zPositions_ = new ArrayList();
            ordering_ = new SortedList();
            FontFamily fontFamily = new FontFamily("Arial");
            TitleFont = new Font(fontFamily, 14, FontStyle.Regular, GraphicsUnit.Pixel);
            padding_ = 10;
            title_ = "";
            autoScaleTitle_ = false;
            autoScaleAutoGeneratedAxes_ = false;
            xAxis1_ = null;
            xAxis2_ = null;
            yAxis1_ = null;
            yAxis2_ = null;
            pXAxis1Cache_ = null;
            pYAxis1Cache_ = null;
            pXAxis2Cache_ = null;
            pYAxis2Cache_ = null;
            titleBrush_ = new SolidBrush(Color.Black);
            plotBackColor_ = Color.White;

            legend_ = null;

            smoothingMode_ = SmoothingMode.None;

            axesConstraints_ = new ArrayList();
        }

        private float DetermineScaleFactor(int w, int h)
        {
            float diag = (float) Math.Sqrt(w*w + h*h);
            float scaleFactor = (diag/1400.0f)*2.4f;

            if (scaleFactor > 1.0f)
            {
                return scaleFactor;
            }
            else
            {
                return 1.0f;
            }
        }

        private void UpdateAxes(bool recalculateAll)
        {
            if (drawables_.Count != xAxisPositions_.Count || drawables_.Count != yAxisPositions_.Count)
            {
                throw new NPlotException("plots and axis position arrays our of sync");
            }

            int position = 0;

            // if we're not recalculating axes using all iplots then set
            // position to last one in list.
            if (!recalculateAll)
            {
                position = drawables_.Count - 1;
                if (position < 0) position = 0;
            }

            if (recalculateAll)
            {
                xAxis1_ = null;
                yAxis1_ = null;
                xAxis2_ = null;
                yAxis2_ = null;
            }

            for (int i = position; i < drawables_.Count; ++i)
            {
                // only update axes if this drawable is an IPlot.
                if (!(drawables_[position] is IPlot))
                    continue;

                IPlot p = (IPlot) drawables_[position];
                XAxisPosition xap = (XAxisPosition) xAxisPositions_[position];
                YAxisPosition yap = (YAxisPosition) yAxisPositions_[position];

                if (xap == XAxisPosition.Bottom)
                {
                    if (xAxis1_ == null)
                    {
                        xAxis1_ = p.SuggestXAxis();
                        if (xAxis1_ != null)
                        {
                            xAxis1_.TicksAngle = -(float) Math.PI/2.0f;
                        }
                    }
                    else
                    {
                        xAxis1_.LUB(p.SuggestXAxis());
                    }

                    if (xAxis1_ != null)
                    {
                        xAxis1_.MinPhysicalLargeTickStep = 50;

                        if (AutoScaleAutoGeneratedAxes)
                        {
                            xAxis1_.AutoScaleText = true;
                            xAxis1_.AutoScaleTicks = true;
                            xAxis1_.TicksIndependentOfPhysicalExtent = true;
                        }
                        else
                        {
                            xAxis1_.AutoScaleText = false;
                            xAxis1_.AutoScaleTicks = false;
                            xAxis1_.TicksIndependentOfPhysicalExtent = false;
                        }
                    }
                }

                if (xap == XAxisPosition.Top)
                {
                    if (xAxis2_ == null)
                    {
                        xAxis2_ = p.SuggestXAxis();
                        if (xAxis2_ != null)
                        {
                            xAxis2_.TicksAngle = (float) Math.PI/2.0f;
                        }
                    }
                    else
                    {
                        xAxis2_.LUB(p.SuggestXAxis());
                    }

                    if (xAxis2_ != null)
                    {
                        xAxis2_.MinPhysicalLargeTickStep = 50;

                        if (AutoScaleAutoGeneratedAxes)
                        {
                            xAxis2_.AutoScaleText = true;
                            xAxis2_.AutoScaleTicks = true;
                            xAxis2_.TicksIndependentOfPhysicalExtent = true;
                        }
                        else
                        {
                            xAxis2_.AutoScaleText = false;
                            xAxis2_.AutoScaleTicks = false;
                            xAxis2_.TicksIndependentOfPhysicalExtent = false;
                        }
                    }
                }

                if (yap == YAxisPosition.Left)
                {
                    if (yAxis1_ == null)
                    {
                        yAxis1_ = p.SuggestYAxis();
                        if (yAxis1_ != null)
                        {
                            yAxis1_.TicksAngle = (float) Math.PI/2.0f;
                        }
                    }
                    else
                    {
                        yAxis1_.LUB(p.SuggestYAxis());
                    }

                    if (yAxis1_ != null)
                    {
                        if (AutoScaleAutoGeneratedAxes)
                        {
                            yAxis1_.AutoScaleText = true;
                            yAxis1_.AutoScaleTicks = true;
                            yAxis1_.TicksIndependentOfPhysicalExtent = true;
                        }
                        else
                        {
                            yAxis1_.AutoScaleText = false;
                            yAxis1_.AutoScaleTicks = false;
                            yAxis1_.TicksIndependentOfPhysicalExtent = false;
                        }
                    }
                }

                if (yap == YAxisPosition.Right)
                {
                    if (yAxis2_ == null)
                    {
                        yAxis2_ = p.SuggestYAxis();
                        if (yAxis2_ != null)
                        {
                            yAxis2_.TicksAngle = -(float) Math.PI/2.0f;
                        }
                    }
                    else
                    {
                        yAxis2_.LUB(p.SuggestYAxis());
                    }

                    if (yAxis2_ != null)
                    {
                        if (AutoScaleAutoGeneratedAxes)
                        {
                            yAxis2_.AutoScaleText = true;
                            yAxis2_.AutoScaleTicks = true;
                            yAxis2_.TicksIndependentOfPhysicalExtent = true;
                        }
                        else
                        {
                            yAxis2_.AutoScaleText = false;
                            yAxis2_.AutoScaleTicks = false;
                            yAxis2_.TicksIndependentOfPhysicalExtent = false;
                        }
                    }
                }
            }
        }

        private void DetermineAxesToDraw(out Axis xAxis1, out Axis xAxis2, out Axis yAxis1, out Axis yAxis2)
        {
            xAxis1 = xAxis1_;
            xAxis2 = xAxis2_;
            yAxis1 = yAxis1_;
            yAxis2 = yAxis2_;

            if (xAxis1_ == null)
            {
                if (xAxis2_ == null)
                {
                    throw new NPlotException("Error: No X-Axis specified");
                }
                xAxis1 = (Axis) xAxis2_.Clone();
                xAxis1.HideTickText = true;
                xAxis1.TicksAngle = -(float) Math.PI/2.0f;
            }

            if (xAxis2_ == null)
            {
                // don't need to check if xAxis1_ == null, as case already handled above.
                xAxis2 = (Axis) xAxis1_.Clone();
                xAxis2.HideTickText = true;
                xAxis2.TicksAngle = (float) Math.PI/2.0f;
            }

            if (yAxis1_ == null)
            {
                if (yAxis2_ == null)
                {
                    throw new NPlotException("Error: No Y-Axis specified");
                }
                yAxis1 = (Axis) yAxis2_.Clone();
                yAxis1.HideTickText = true;
                yAxis1.TicksAngle = (float) Math.PI/2.0f;
            }

            if (yAxis2_ == null)
            {
                // don't need to check if yAxis1_ == null, as case already handled above.
                yAxis2 = (Axis) yAxis1_.Clone();
                yAxis2.HideTickText = true;
                yAxis2.TicksAngle = -(float) Math.PI/2.0f;
            }
        }

        private void DeterminePhysicalAxesToDraw(Rectangle bounds,
                                                 Axis xAxis1, Axis xAxis2, Axis yAxis1, Axis yAxis2,
                                                 out PhysicalAxis pXAxis1, out PhysicalAxis pXAxis2,
                                                 out PhysicalAxis pYAxis1, out PhysicalAxis pYAxis2)
        {
            Rectangle cb = bounds;

            pXAxis1 = new PhysicalAxis(xAxis1,
                                       new Point(cb.Left, cb.Bottom), new Point(cb.Right, cb.Bottom));
            pYAxis1 = new PhysicalAxis(yAxis1,
                                       new Point(cb.Left, cb.Bottom), new Point(cb.Left, cb.Top));
            pXAxis2 = new PhysicalAxis(xAxis2,
                                       new Point(cb.Left, cb.Top), new Point(cb.Right, cb.Top));
            pYAxis2 = new PhysicalAxis(yAxis2,
                                       new Point(cb.Right, cb.Bottom), new Point(cb.Right, cb.Top));

            int bottomIndent = padding_;
            if (!pXAxis1.Axis.Hidden)
            {
                // evaluate its bounding box
                Rectangle bb = pXAxis1.GetBoundingBox();
                // finally determine its indentation from the bottom
                bottomIndent = bottomIndent + bb.Bottom - cb.Bottom;
            }

            int leftIndent = padding_;
            if (!pYAxis1.Axis.Hidden)
            {
                // evaluate its bounding box
                Rectangle bb = pYAxis1.GetBoundingBox();
                // finally determine its indentation from the left
                leftIndent = leftIndent - bb.Left + cb.Left;
            }

            int topIndent = padding_;
            float scale = DetermineScaleFactor(bounds.Width, bounds.Height);
            int titleHeight;
            if (AutoScaleTitle)
            {
                titleHeight = Utils.ScaleFont(titleFont_, scale).Height;
            }
            else
            {
                titleHeight = titleFont_.Height;
            }

            //count number of new lines in title.
            int nlCount = 0;
            for (int i = 0; i < title_.Length; ++i)
            {
                if (title_[i] == '\n')
                    nlCount += 1;
            }
            titleHeight = (int) ((nlCount*0.75 + 1.0f)*titleHeight);

            if (!pXAxis2.Axis.Hidden)
            {
                // evaluate its bounding box
                Rectangle bb = pXAxis2.GetBoundingBox();
                topIndent = topIndent - bb.Top + cb.Top;

                // finally determine its indentation from the top
                // correct top indendation to take into account plot title
                if (title_ != "")
                {
                    topIndent += (int) (titleHeight*1.3f);
                }
            }

            int rightIndent = padding_;
            if (!pYAxis2.Axis.Hidden)
            {
                // evaluate its bounding box
                Rectangle bb = pYAxis2.GetBoundingBox();

                // finally determine its indentation from the right
                rightIndent = (rightIndent + bb.Right - cb.Right);
            }

            // now we have all the default calculated positions and we can proceed to
            // "move" the axes to their right places

            // primary axes (bottom, left)
            pXAxis1.PhysicalMin = new Point(cb.Left + leftIndent, cb.Bottom - bottomIndent);
            pXAxis1.PhysicalMax = new Point(cb.Right - rightIndent, cb.Bottom - bottomIndent);
            pYAxis1.PhysicalMin = new Point(cb.Left + leftIndent, cb.Bottom - bottomIndent);
            pYAxis1.PhysicalMax = new Point(cb.Left + leftIndent, cb.Top + topIndent);

            // secondary axes (top, right)
            pXAxis2.PhysicalMin = new Point(cb.Left + leftIndent, cb.Top + topIndent);
            pXAxis2.PhysicalMax = new Point(cb.Right - rightIndent, cb.Top + topIndent);
            pYAxis2.PhysicalMin = new Point(cb.Right - rightIndent, cb.Bottom - bottomIndent);
            pYAxis2.PhysicalMax = new Point(cb.Right - rightIndent, cb.Top + topIndent);
        }

        /// <summary>
        /// Draw the the PlotSurface2D and all contents [axes, drawables, and legend] on the
        /// supplied graphics surface.
        /// </summary>
        /// <param name="g">The graphics surface on which to draw.</param>
        /// <param name="bounds">
        /// A bounding box on this surface that denotes the area on the
        /// surface to confine drawing to.
        /// </param>
        public void Draw(Graphics g, Rectangle bounds)
        {
            // determine font sizes and tick scale factor.
            float scale = DetermineScaleFactor(bounds.Width, bounds.Height);

            // if there is nothing to plot, return.
            if (drawables_.Count == 0)
            {
                // draw title
                float x_center = (bounds.Left + bounds.Right)/2.0f;
                float y_center = (bounds.Top + bounds.Bottom)/2.0f;
                Font scaled_font;
                if (AutoScaleTitle)
                {
                    scaled_font = Utils.ScaleFont(titleFont_, scale);
                }
                else
                {
                    scaled_font = titleFont_;
                }
                g.DrawString(title_, scaled_font, titleBrush_, new PointF(x_center, y_center), titleDrawFormat_);

                return;
            }

            // determine the [non physical] axes to draw based on the axis properties set.
            Axis xAxis1 = null;
            Axis xAxis2 = null;
            Axis yAxis1 = null;
            Axis yAxis2 = null;
            DetermineAxesToDraw(out xAxis1, out xAxis2, out yAxis1, out yAxis2);

            // apply scale factor to axes as desired.

            if (xAxis1.AutoScaleTicks)
                xAxis1.TickScale = scale;
            if (xAxis1.AutoScaleText)
                xAxis1.FontScale = scale;
            if (yAxis1.AutoScaleTicks)
                yAxis1.TickScale = scale;
            if (yAxis1.AutoScaleText)
                yAxis1.FontScale = scale;
            if (xAxis2.AutoScaleTicks)
                xAxis2.TickScale = scale;
            if (xAxis2.AutoScaleText)
                xAxis2.FontScale = scale;
            if (yAxis2.AutoScaleTicks)
                yAxis2.TickScale = scale;
            if (yAxis2.AutoScaleText)
                yAxis2.FontScale = scale;

            // determine the default physical positioning of those axes.
            PhysicalAxis pXAxis1 = null;
            PhysicalAxis pYAxis1 = null;
            PhysicalAxis pXAxis2 = null;
            PhysicalAxis pYAxis2 = null;
            DeterminePhysicalAxesToDraw(
                bounds, xAxis1, xAxis2, yAxis1, yAxis2,
                out pXAxis1, out pXAxis2, out pYAxis1, out pYAxis2);

            float oldXAxis2Height = pXAxis2.PhysicalMin.Y;

            // Apply axes constraints
            for (int i = 0; i < axesConstraints_.Count; ++i)
            {
                ((AxesConstraint) axesConstraints_[i]).ApplyConstraint(
                    pXAxis1, pYAxis1, pXAxis2, pYAxis2);
            }

            /////////////////////////////////////////////////////////////////////////
            // draw legend if have one.
            // Note: this will update axes if necessary. 

            Point legendPosition = new Point(0, 0);
            if (legend_ != null)
            {
                legend_.UpdateAxesPositions(
                    pXAxis1, pYAxis1, pXAxis2, pYAxis2,
                    drawables_, scale, padding_, bounds,
                    out legendPosition);
            }

            float newXAxis2Height = pXAxis2.PhysicalMin.Y;

            float titleExtraOffset = oldXAxis2Height - newXAxis2Height;

            // now we are ready to define the bounding box for the plot area (to use in clipping
            // operations.
            plotAreaBoundingBoxCache_ = new Rectangle(
                Math.Min(pXAxis1.PhysicalMin.X, pXAxis1.PhysicalMax.X),
                Math.Min(pYAxis1.PhysicalMax.Y, pYAxis1.PhysicalMin.Y),
                Math.Abs(pXAxis1.PhysicalMax.X - pXAxis1.PhysicalMin.X + 1),
                Math.Abs(pYAxis1.PhysicalMin.Y - pYAxis1.PhysicalMax.Y + 1)
                );
            bbXAxis1Cache_ = pXAxis1.GetBoundingBox();
            bbXAxis2Cache_ = pXAxis2.GetBoundingBox();
            bbYAxis1Cache_ = pYAxis1.GetBoundingBox();
            bbYAxis2Cache_ = pYAxis2.GetBoundingBox();

            // Fill in the background. 
            if (plotBackColor_ != null)
            {
                g.FillRectangle(
                    new SolidBrush((Color) plotBackColor_),
                    (Rectangle) plotAreaBoundingBoxCache_);
            }
            else if (plotBackBrush_ != null)
            {
                g.FillRectangle(
                    plotBackBrush_.Get((Rectangle) plotAreaBoundingBoxCache_),
                    (Rectangle) plotAreaBoundingBoxCache_);
            }
            else if (plotBackImage_ != null)
            {
                g.DrawImage(
                    Utils.TiledImage(plotBackImage_, new Size(
                                                         ((Rectangle) plotAreaBoundingBoxCache_).Width,
                                                         ((Rectangle) plotAreaBoundingBoxCache_).Height)),
                    (Rectangle) plotAreaBoundingBoxCache_);
            }

            // draw title
            float xt = (pXAxis2.PhysicalMax.X + pXAxis2.PhysicalMin.X)/2.0f;
            float yt = bounds.Top + padding_ - titleExtraOffset;
            Font scaledFont;
            if (AutoScaleTitle)
            {
                scaledFont = Utils.ScaleFont(titleFont_, scale);
            }
            else
            {
                scaledFont = titleFont_;
            }
            g.DrawString(title_, scaledFont, titleBrush_, new PointF(xt, yt), titleDrawFormat_);

            //count number of new lines in title.
            int nlCount = 0;
            for (int i = 0; i < title_.Length; ++i)
            {
                if (title_[i] == '\n')
                    nlCount += 1;
            }

            SizeF s = g.MeasureString(title_, scaledFont);
            bbTitleCache_ = new Rectangle((int) (xt - s.Width/2), (int) (yt), (int) (s.Width), (int) (s.Height)*(nlCount + 1));

            // draw drawables..
            SmoothingMode smoothSave = g.SmoothingMode;

            g.SmoothingMode = smoothingMode_;

            bool legendDrawn = false;

            for (int i_o = 0; i_o < ordering_.Count; ++i_o)
            {
                int i = (int) ordering_.GetByIndex(i_o);
                double zOrder = (double) ordering_.GetKey(i_o);
                if (zOrder > legendZOrder_)
                {
                    // draw legend.
                    if (!legendDrawn && legend_ != null)
                    {
                        legend_.Draw(g, legendPosition, drawables_, scale);
                        legendDrawn = true;
                    }
                }

                IDrawable drawable = (IDrawable) drawables_[i];
                XAxisPosition xap = (XAxisPosition) xAxisPositions_[i];
                YAxisPosition yap = (YAxisPosition) yAxisPositions_[i];

                PhysicalAxis drawXAxis;
                PhysicalAxis drawYAxis;

                if (xap == XAxisPosition.Bottom)
                {
                    drawXAxis = pXAxis1;
                }
                else
                {
                    drawXAxis = pXAxis2;
                }

                if (yap == YAxisPosition.Left)
                {
                    drawYAxis = pYAxis1;
                }
                else
                {
                    drawYAxis = pYAxis2;
                }

                // set the clipping region.. (necessary for zoom)
                g.Clip = new Region((Rectangle) plotAreaBoundingBoxCache_);
                // plot.
                drawable.Draw(g, drawXAxis, drawYAxis);
                // reset it..
                g.ResetClip();
            }

            if (!legendDrawn && legend_ != null)
            {
                legend_.Draw(g, legendPosition, drawables_, scale);
            }

            // cache the physical axes we used on this draw;
            pXAxis1Cache_ = pXAxis1;
            pYAxis1Cache_ = pYAxis1;
            pXAxis2Cache_ = pXAxis2;
            pYAxis2Cache_ = pYAxis2;

            g.SmoothingMode = smoothSave;

            // now draw axes.
            Rectangle axisBounds;
            pXAxis1.Draw(g, out axisBounds);
            pXAxis2.Draw(g, out axisBounds);
            pYAxis1.Draw(g, out axisBounds);
            pYAxis2.Draw(g, out axisBounds);

#if DEBUG_BOUNDING_BOXES
			g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbXAxis1Cache_ );
			g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbXAxis2Cache_ );
			g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbYAxis1Cache_ );
			g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbYAxis2Cache_ );
			g.DrawRectangle( new Pen(Color.Red,5.0F),(Rectangle) plotAreaBoundingBoxCache_);
			//if(this.ShowLegend)g.DrawRectangle( new Pen(Color.Chocolate, 3.0F), (Rectangle) bbLegendCache_);
			g.DrawRectangle( new Pen(Color.DeepPink,2.0F), (Rectangle) bbTitleCache_);
#endif
        }

        /// <summary>
        /// If a plot is removed, then the ordering_ list needs to be
        /// recalculated.
        /// </summary>
        private void RefreshZOrdering()
        {
            uniqueCounter_ = 0;
            ordering_ = new SortedList();
            for (int i = 0; i < zPositions_.Count; ++i)
            {
                double zpos = Convert.ToDouble(zPositions_[i]);
                double fraction = (double) (++uniqueCounter_)/10000000.0f;
                double d = zpos + fraction;
                ordering_.Add(d, i);
            }
        }

        /// <summary>
        /// Returns the x-axis associated with a given plot.
        /// </summary>
        /// <param name="plot">the plot to get associated x-axis.</param>
        /// <returns>the axis associated with the plot.</returns>
        public Axis WhichXAxis(IPlot plot)
        {
            int index = drawables_.IndexOf(plot);
            XAxisPosition p = (XAxisPosition) xAxisPositions_[index];
            if (p == XAxisPosition.Bottom)
                return xAxis1_;
            else
                return xAxis2_;
        }

        /// <summary>
        /// Returns the y-axis associated with a given plot.
        /// </summary>
        /// <param name="plot">the plot to get associated y-axis.</param>
        /// <returns>the axis associated with the plot.</returns>
        public Axis WhichYAxis(IPlot plot)
        {
            int index = drawables_.IndexOf(plot);
            YAxisPosition p = (YAxisPosition) yAxisPositions_[index];
            if (p == YAxisPosition.Left)
                return yAxis1_;
            else
                return yAxis2_;
        }
    }
}