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

namespace NPlot
{

    /// <summary>
    /// Encapsulates functionality
    /// </summary>
    public class LabelPointPlot : PointPlot, ISequencePlot
    {

        /// <summary>
        /// This class us used in conjunction with SequenceAdapter to interpret data
        /// specified to the TextPlot class.
        /// </summary>
        private class TextDataAdapter
        {

            private object data_;
            private object dataSource_;
            private string dataMember_;

            public TextDataAdapter( object dataSource, string dataMember, object data )
            {
                this.data_ = data;
                this.dataSource_ = dataSource;
                this.dataMember_ = dataMember;
            }

            public string this[int i]
            {
                get
                {

                    // this is inefficient [could set up delegates in constructor].

                    if (data_ is string[])
                    {
                        return ((string[])data_)[i];
                    }
                    
                    if (data_ is string)
                    {
                        if (dataSource_ == null)
                        {
                            throw new NPlotException( "Error: DataSource null" );
                        }

                        System.Data.DataRowCollection rows;

                        if ( dataSource_ is System.Data.DataSet )
                        {
                            if (dataMember_ != null)
                            {
                                // TODO error check
                                rows = ((DataTable)((DataSet)dataSource_).Tables[dataMember_]).Rows;
                            }
                            else
                            {
                                // TODO error check
                                rows = ((DataTable)((DataSet)dataSource_).Tables[0]).Rows;
                            }
                        }

                        else if (dataSource_ is System.Data.DataTable )
                        {
                            rows = ((DataTable)dataSource_).Rows;
                        }

                        else
                        {
                            throw new NPlotException ( "not implemented yet" );
                        }

                        return (string)((System.Data.DataRow)(rows[i]))[(string)data_];
                    }

                    if (data_ is System.Collections.ArrayList)
                    {
                        object dataPoint = ((System.Collections.ArrayList)data_)[i];
                        if (dataPoint is string)
                            return (string)dataPoint;
                        throw new NPlotException( "TextDataAdapter: data not in recognised format" );
                    }
                    
                    if (data_ == null)
                    {
                        return "text";
                    }

                    throw new NPlotException( "Text data not of recognised type" );
                }
            }


            public int Count
            {
                get
                {
                    // this is inefficient [could set up delegates in constructor].

                    if (data_ == null)
                    {
                        return 0;
                    }
                    if (data_ is string[])
                    {
                        return ((string[])data_).Length;
                    }
                    if (data_ is System.Collections.ArrayList)
                    {
                        return ((System.Collections.ArrayList)data_).Count;
                    }
                    throw new NPlotException( "Text data not in correct format" );
                }
            }

        }


        /// <summary>
        /// Enumeration of all label positions relative to a point.
        /// </summary>
        public enum LabelPositions
        {
            /// <summary>
            /// Above the point
            /// </summary>
            Above,
            /// <summary>
            /// Below the point
            /// </summary>
            Below,
            /// <summary>
            /// To the left of the point
            /// </summary>
            Left,
            /// <summary>
            /// To the right of the point
            /// </summary>
            Right
        }


        /// <summary>
        /// Default Constructor
        /// </summary>
        public LabelPointPlot()
        {
        }


        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="marker">The marker type to use for this plot.</param>
        public LabelPointPlot( Marker marker ) 
            : base( marker )
        {
        }

        
        /// <summary>
        /// The position of the text label in relation to the point.
        /// </summary>
        public LabelPositions LabelTextPosition
        {
            get
            {
                return labelTextPosition_;
            }
            set
            {
                labelTextPosition_ = value;
            }
        }
        private LabelPositions labelTextPosition_ = LabelPositions.Above;


        /// <summary>
        /// The text datasource to attach to each point.
        /// </summary>
        public object TextData
        {
            get
            {
                return textData_;
            }
            set
            {
                textData_ = value;
            }
        }
        object textData_;


        /// <summary>
        /// The Font used to write text.
        /// </summary>
        public Font Font
        {
            get
            {
                return font_;
            }
            set
            {
                font_ = value;
            }
        }
        private Font font_ = new Font( "Arial", 8.0f );


        /// <summary>
        /// Draws the plot on a GDI+ surface against the provided x and y axes.
        /// </summary>
        /// <param name="g">The GDI+ surface on which to draw.</param>
        /// <param name="xAxis">The X-Axis to draw against.</param>
        /// <param name="yAxis">The Y-Axis to draw against.</param>
        public override void Draw( Graphics g, PhysicalAxis xAxis, PhysicalAxis yAxis )
        {
            SequenceAdapter data = 
                new SequenceAdapter( this.DataSource, this.DataMember, this.OrdinateData, this.AbscissaData );

            TextDataAdapter textData =
                new TextDataAdapter( this.DataSource, this.DataMember, this.TextData );

            // first plot the marker
            // we can do this cast, since the constructor accepts only this type!
            for (int i=0; i<data.Count; ++i)
            {
                try
                {
                    PointD pt = data[i];
                    if ( !double.IsNaN(pt.X) && !double.IsNaN(pt.Y) )
                    {
                        PointF xPos = xAxis.WorldToPhysical( pt.X, false);
                        PointF yPos = yAxis.WorldToPhysical( pt.Y, false);
                        Marker.Draw( g, (int)xPos.X, (int)yPos.Y );
                        if ( textData[i] != "" )
                        {
                            SizeF size = g.MeasureString( textData[i], this.Font );
                            switch (labelTextPosition_)
                            {
                                case LabelPositions.Above:
                                    g.DrawString( textData[i], font_, Brushes.Black, new PointF(xPos.X-size.Width/2,yPos.Y-size.Height-Marker.Size*2/3));
                                    break;
                                case LabelPositions.Below:
                                    g.DrawString( textData[i], font_, Brushes.Black, new PointF(xPos.X-size.Width/2,yPos.Y+Marker.Size*2/3));
                                    break;
                                case LabelPositions.Left:
                                    g.DrawString( textData[i], font_, Brushes.Black, new PointF(xPos.X-size.Width-Marker.Size*2/3,yPos.Y-size.Height/2));
                                    break;
                                case LabelPositions.Right:
                                    g.DrawString( textData[i], font_, Brushes.Black, new PointF(xPos.X+Marker.Size*2/3,yPos.Y-size.Height/2));
                                    break;
                            }
                        }
                    }
                }
                catch
                {
                    throw new NPlotException("Error in TextPlot.Draw");
                }
            }

        }


    }
}
