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

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

namespace NPlot
{

    /// <summary>
    /// Legend functionality specific to Legends associated with a PlotSurface2D.
    /// </summary>
    public class Legend : LegendBase
    {

        /// <summary>
        /// Enumeration of possible Legend placements.
        /// </summary>
        public enum Placement
        {
            /// <summary>
            /// Inside the plot area.
            /// </summary>
            Inside = 0,
            /// <summary>
            /// Outside the plot area.
            /// </summary>
            Outside = 1
        }

        private int xOffset_;
        private int yOffset_;
        private PlotSurface2D.XAxisPosition xAttach_;
        private PlotSurface2D.YAxisPosition yAttach_;
        private Placement horizontalEdgePlacement_;
        private Placement verticalEdgePlacement_;
        private bool neverShiftAxes_;

        /// <summary>
        /// Whether or not the positions of the Axes may be shifted to make
        /// room for the Legend. 
        /// </summary>
        public bool NeverShiftAxes
        {
            get
            {
                return neverShiftAxes_;
            }
            set
            {
                neverShiftAxes_ = value;
            }
        }


        /// <summary>
        /// Offset from the chosen Y-Axis. TODO: better description.
        /// </summary>
        public int XOffset
        {
            get
            {
                return xOffset_;
            }
            set
            {
                xOffset_ = value;
            }
        }


        /// <summary>
        /// Offset from the X-Axis. TODO: better description.
        /// </summary>
        public int YOffset
        {
            get
            {
                return yOffset_;
            }
            set
            {
                yOffset_ = value;
            }
        }


        /// <summary>
        /// Whether or not to attach the legend on the inside of the top
        /// or bottom axis (which, is specified using the AttachTo method) or the 
        /// outside. 
        /// </summary>
        public Legend.Placement VerticalEdgePlacement
        {
            get
            {
                return verticalEdgePlacement_;
            }
            set
            {
                verticalEdgePlacement_ = value;
            }
        }

        
        /// <summary>
        /// Whether or not to attach the legend on the inside of the
        /// left or right axis (which, is specified using the AttachTo method) 
        /// or the outside.
        /// </summary>
        public Legend.Placement HorizontalEdgePlacement
        {
            get
            {
                return horizontalEdgePlacement_;
            }
            set
            {
                horizontalEdgePlacement_ = value;
            }
        }


        /// <summary>
        /// Specify the Axes to attach the legend to. 
        /// </summary>
        /// <param name="xa">Specify which horizontal axis the legend should be attached to.</param>
        /// <param name="ya">Specify which vertical axis the legend should be attached to.</param>
        public void AttachTo( PlotSurface2D.XAxisPosition xa, PlotSurface2D.YAxisPosition ya )
        {
            xAttach_ = xa;
            yAttach_ = ya; 
        }


        /// <summary>
        /// Default constructor.
        /// </summary>
        public Legend()
        {
            xAttach_ = PlotSurface2D.XAxisPosition.Top;
            yAttach_ = PlotSurface2D.YAxisPosition.Right;
            xOffset_ = 10;
            yOffset_ = 1;
            verticalEdgePlacement_ = Placement.Outside;
            horizontalEdgePlacement_ = Placement.Inside;
            neverShiftAxes_ = false;
        }


        /// <summary>
        /// Updates the PlotSurface2D axes to compensate for the legend.
        /// </summary>
        /// <param name="pXAxis1">the bottom x axis</param>
        /// <param name="pYAxis1">the left y axis</param>
        /// <param name="pXAxis2">the top x axis</param>
        /// <param name="pYAxis2">the right y axis</param>
        /// <param name="plots">list of plots.</param>
        /// <param name="scale">scale parameter (for text and other)</param>
        /// <param name="padding">padding around plot within bounds.</param>
        /// <param name="bounds">graphics surface bounds</param>
        /// <param name="position">legend position</param>
        public void UpdateAxesPositions( 
            PhysicalAxis pXAxis1,
            PhysicalAxis pYAxis1,
            PhysicalAxis pXAxis2,
            PhysicalAxis pYAxis2,
            ArrayList plots,
            float scale, int padding, Rectangle bounds,
            out Point position )
        {
        
            int leftIndent = 0;
            int rightIndent = 0;
            int bottomIndent = 0;
            int topIndent = 0;

            position = new Point(0,0);

            // now determine if legend should change any of these (legend should be fully 
            // visible at all times), and draw legend.

            Rectangle legendWidthHeight = this.GetBoundingBox( new Point(0,0), plots, scale );

            if (legendWidthHeight.Width > bounds.Width)
            {
                legendWidthHeight.Width = bounds.Width;
            }

            // (1) calculate legend position.

            // y

            position.Y = this.yOffset_;
            
            if ( this.xAttach_ == PlotSurface2D.XAxisPosition.Bottom )
            {
                position.Y += pYAxis1.PhysicalMin.Y;
                if ( this.horizontalEdgePlacement_ == Legend.Placement.Inside )
                {
                    position.Y -= legendWidthHeight.Height;
                }
            }
            else
            {
                position.Y += pYAxis1.PhysicalMax.Y;
                if ( this.horizontalEdgePlacement_ == Legend.Placement.Outside )
                {
                    position.Y -= legendWidthHeight.Height;
                }
            }
    
            // x

            position.X = this.xOffset_;
        
            if ( this.yAttach_ == PlotSurface2D.YAxisPosition.Left )
            {
                if ( this.verticalEdgePlacement_ == Legend.Placement.Outside ) 
                {
                    position.X -= legendWidthHeight.Width;
                }
                position.X += pXAxis1.PhysicalMin.X;
            }
            else
            {
                if ( this.verticalEdgePlacement_ == Legend.Placement.Inside )
                {
                    position.X -= legendWidthHeight.Width;
                }
                position.X += pXAxis1.PhysicalMax.X;
            }


            // determine update amounts for axes

            if ( !this.neverShiftAxes_ )
            {

                if ( position.X < padding )
                {
                    int changeAmount = -position.X + padding;
                    // only allow axes to move away from bounds.
                    if ( changeAmount > 0 )                    
                    {
                        leftIndent = changeAmount;
                    }
                    position.X += changeAmount;
                }

                if ( position.X + legendWidthHeight.Width > bounds.Right - padding )
                {
                    int changeAmount = (position.X - bounds.Right + legendWidthHeight.Width + padding );
                    // only allow axes to move away from bounds.
                    if ( changeAmount > 0.0f ) 
                    {
                        rightIndent = changeAmount;
                    }
                    position.X -= changeAmount;
                }

                if ( position.Y < padding )
                {
                    int changeAmount = -position.Y + padding;
                    // only allow axes to move away from bounds.
                    if ( changeAmount > 0.0f )                    
                    {
                        topIndent = changeAmount;
                    }
                    position.Y += changeAmount;
                }

                if ( position.Y + legendWidthHeight.Height > bounds.Bottom - padding )
                {
                    int changeAmount = (position.Y - bounds.Bottom + legendWidthHeight.Height + padding );
                    // only allow axes to move away from bounds.
                    if ( changeAmount > 0.0f ) 
                    {
                        bottomIndent = changeAmount;
                    }
                    position.Y -= changeAmount;
                }

                // update axes.

                pXAxis1.PhysicalMin = new Point( pXAxis1.PhysicalMin.X + leftIndent, pXAxis1.PhysicalMin.Y - bottomIndent );
                pXAxis1.PhysicalMax = new Point( pXAxis1.PhysicalMax.X - rightIndent, pXAxis1.PhysicalMax.Y - bottomIndent );
                pYAxis1.PhysicalMin = new Point( pYAxis1.PhysicalMin.X + leftIndent, pYAxis1.PhysicalMin.Y - bottomIndent );
                pYAxis1.PhysicalMax = new Point( pYAxis1.PhysicalMax.X + leftIndent, pYAxis1.PhysicalMax.Y + topIndent );

                pXAxis2.PhysicalMin = new Point( pXAxis2.PhysicalMin.X + leftIndent, pXAxis2.PhysicalMin.Y + topIndent );
                pXAxis2.PhysicalMax = new Point( pXAxis2.PhysicalMax.X - rightIndent, pXAxis2.PhysicalMax.Y + topIndent );
                pYAxis2.PhysicalMin = new Point( pYAxis2.PhysicalMin.X - rightIndent, pYAxis2.PhysicalMin.Y - bottomIndent );
                pYAxis2.PhysicalMax = new Point( pYAxis2.PhysicalMax.X - rightIndent, pYAxis2.PhysicalMax.Y + topIndent );
            
            }

        }

    }

}
