#region PDFsharp Charting - A .NET charting library based on PDFsharp
//
// Authors:
//   Niklas Schneider (mailto:Niklas.Schneider@pdfsharp.com)
//
// Copyright (c) 2005-2007 empira Software GmbH, Cologne (Germany)
//
// http://www.pdfsharp.com
// http://sourceforge.net/projects/pdfsharp
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Diagnostics;
using PdfSharp.Drawing;

namespace PdfSharp.Charting.Renderers
{
  /// <summary>
  /// Represents a Y axis renderer used for charts of type Column2D or Line.
  /// </summary>
  internal class VerticalYAxisRenderer : YAxisRenderer
  {
    /// <summary>
    /// Initializes a new instance of the VerticalYAxisRenderer class with the
    /// specified renderer parameters.
    /// </summary>
    internal VerticalYAxisRenderer(RendererParameters parms) : base(parms)
    {
    }

    /// <summary>
    /// Returns a initialized rendererInfo based on the Y axis.
    /// </summary>
    internal override RendererInfo Init()
    {
      Chart chart = (Chart)this.rendererParms.DrawingItem;
      XGraphics gfx = this.rendererParms.Graphics;

      AxisRendererInfo yari = new AxisRendererInfo();
      yari.axis = chart.yAxis;
      InitScale(yari);
      if (yari.axis != null)
      {
        ChartRendererInfo cri = (ChartRendererInfo)this.rendererParms.RendererInfo;
        InitTickLabels(yari, cri.DefaultFont);
        InitAxisTitle(yari, cri.DefaultFont);
        InitAxisLineFormat(yari);
        InitGridlines(yari);
      }
      return yari;
    }

    /// <summary>
    /// Calculates the space used for the Y axis.
    /// </summary>
    internal override void Format()
    {
      AxisRendererInfo yari = ((ChartRendererInfo)this.rendererParms.RendererInfo).yAxisRendererInfo;
      if (yari.axis != null)
      {
        XGraphics gfx = this.rendererParms.Graphics;

        XSize size = new XSize(0, 0);

        // height of all ticklabels
        double yMin = yari.MinimumScale;
        double yMax = yari.MaximumScale;
        double yMajorTick = yari.MajorTick;
        double lineHeight = Double.MinValue;
        XSize labelSize = new XSize(0, 0);
        for (double y = yMin; y <= yMax; y += yMajorTick)
        {
          string str = y.ToString(yari.TickLabelsFormat);
          labelSize = gfx.MeasureString(str, yari.TickLabelsFont);
          size.Height += labelSize.Height;
          size.Width = Math.Max(size.Width, labelSize.Width);
          lineHeight = Math.Max(lineHeight, labelSize.Height);
        }

        // add space for tickmarks
        size.Width += yari.MajorTickMarkWidth * 1.5;

        // Measure axis title
        XSize titleSize = new XSize(0, 0);
        if (yari.axisTitleRendererInfo != null)
        {
          RendererParameters parms = new RendererParameters();
          parms.Graphics = gfx;
          parms.RendererInfo = yari;
          AxisTitleRenderer atr = new AxisTitleRenderer(parms);
          atr.Format();
          titleSize.Height = yari.axisTitleRendererInfo.Height;
          titleSize.Width = yari.axisTitleRendererInfo.Width;
        }

        yari.Height = Math.Max(size.Height, titleSize.Height);
        yari.Width = size.Width + titleSize.Width;

        yari.InnerRect = yari.Rect;
        yari.InnerRect.Y += yari.TickLabelsFont.Height / 2;
        yari.LabelSize = labelSize;
      }
    }

    /// <summary>
    /// Draws the vertical Y axis.
    /// </summary>
    internal override void Draw()
    {
      AxisRendererInfo yari = ((ChartRendererInfo)this.rendererParms.RendererInfo).yAxisRendererInfo;

      double yMin = yari.MinimumScale;
      double yMax = yari.MaximumScale;
      double yMajorTick = yari.MajorTick;
      double yMinorTick = yari.MinorTick;

      XMatrix matrix = XMatrix.Identity;
      matrix.Translate(-yari.InnerRect.X, yMax);
      matrix.Scale(1, yari.InnerRect.Height / (yMax - yMin), XMatrixOrder.Append);
      matrix.Scale(1, -1); // mirror horizontal
      matrix.Translate(yari.InnerRect.X, yari.InnerRect.Y, XMatrixOrder.Append);

      // Draw axis.
      // First draw tick marks, second draw axis.
      double majorTickMarkStart = 0, majorTickMarkEnd = 0,
             minorTickMarkStart = 0, minorTickMarkEnd = 0;
      GetTickMarkPos(yari, ref majorTickMarkStart, ref majorTickMarkEnd, ref minorTickMarkStart, ref minorTickMarkEnd);

      XGraphics gfx = this.rendererParms.Graphics;
      LineFormatRenderer lineFormatRenderer = new LineFormatRenderer(gfx, yari.LineFormat);
      LineFormatRenderer minorTickMarkLineFormat = new LineFormatRenderer(gfx, yari.MinorTickMarkLineFormat);
      LineFormatRenderer majorTickMarkLineFormat = new LineFormatRenderer(gfx, yari.MajorTickMarkLineFormat);
      XPoint[] points = new XPoint[2];

      // Draw minor tick marks.
      if (yari.MinorTickMark != TickMarkType.None)
      {
        for (double y = yMin + yMinorTick; y < yMax; y += yMinorTick)
        {
          points[0].X = minorTickMarkStart;
          points[0].Y = y;
          points[1].X = minorTickMarkEnd;
          points[1].Y = y;
          matrix.TransformPoints(points);
          minorTickMarkLineFormat.DrawLine(points[0], points[1]);
        }
      }

      double lineSpace = yari.TickLabelsFont.GetHeight(gfx);
      int cellSpace = yari.TickLabelsFont.FontFamily.GetLineSpacing(yari.TickLabelsFont.Style);
      double xHeight = yari.TickLabelsFont.Metrics.XHeight;

      XSize labelSize = new XSize(0, 0);
      labelSize.Height = lineSpace * xHeight / cellSpace;

      int countTickLabels = (int)((yMax - yMin) / yMajorTick) + 1;
      for (int i = 0; i < countTickLabels; ++i)
      {
        double y = yMin + yMajorTick * i;
        string str = y.ToString(yari.TickLabelsFormat);

        labelSize.Width = gfx.MeasureString(str, yari.TickLabelsFont).Width;

        // Draw major tick marks.
        if (yari.MajorTickMark != TickMarkType.None)
        {
          labelSize.Width += yari.MajorTickMarkWidth * 1.5;
          points[0].X = majorTickMarkStart;
          points[0].Y = y;
          points[1].X = majorTickMarkEnd;
          points[1].Y = y;
          matrix.TransformPoints(points);
          majorTickMarkLineFormat.DrawLine(points[0], points[1]);
        }
        else
          labelSize.Width += SpaceBetweenLabelAndTickmark;

        // Draw label text.
        XPoint[] layoutText = new XPoint[1];
        layoutText[0].X = yari.InnerRect.X + yari.InnerRect.Width - labelSize.Width;
        layoutText[0].Y = y;
        matrix.TransformPoints(layoutText);
        layoutText[0].Y += labelSize.Height / 2; // Center text vertically.
        gfx.DrawString(str, yari.TickLabelsFont, yari.TickLabelsBrush, layoutText[0]);
      }

      // Draw axis.
      if (yari.LineFormat != null && yari.LineFormat.Width > 0)
      {
        points[0].X = yari.InnerRect.X + yari.InnerRect.Width;
        points[0].Y = yMin;
        points[1].X = yari.InnerRect.X + yari.InnerRect.Width;
        points[1].Y = yMax;
        matrix.TransformPoints(points);
        if (yari.MajorTickMark != TickMarkType.None)
        {
          // yMax is at the upper side of the axis
          points[1].Y -= yari.LineFormat.Width / 2;
          points[0].Y += yari.LineFormat.Width / 2;
        }
        lineFormatRenderer.DrawLine(points[0], points[1]);
      }

      // Draw axis title
      if (yari.axisTitleRendererInfo != null && yari.axisTitleRendererInfo.AxisTitleText != "")
      {
        RendererParameters parms = new RendererParameters();
        parms.Graphics = gfx;
        parms.RendererInfo = yari;
        double width = yari.axisTitleRendererInfo.Width;
        yari.axisTitleRendererInfo.Rect = yari.InnerRect;
        yari.axisTitleRendererInfo.Width = width;
        AxisTitleRenderer atr = new AxisTitleRenderer(parms);
        atr.Draw();
      }
    }

    /// <summary>
    /// Calculates all values necessary for scaling the axis like minimum/maximum scale or
    /// minor/major tick.
    /// </summary>
    private void InitScale(AxisRendererInfo rendererInfo)
    {
      double yMin, yMax;
      CalcYAxis(out yMin, out yMax);
      FineTuneYAxis(rendererInfo, yMin, yMax);

      rendererInfo.MajorTickMarkWidth = DefaultMajorTickMarkWidth;
      rendererInfo.MinorTickMarkWidth = DefaultMinorTickMarkWidth;
    }

    /// <summary>
    /// Gets the top and bottom position of the major and minor tick marks depending on the
    /// tick mark type.
    /// </summary>
    private void GetTickMarkPos(AxisRendererInfo rendererInfo,
                                ref double majorTickMarkStart, ref double majorTickMarkEnd,
                                ref double minorTickMarkStart, ref double minorTickMarkEnd)
    {
      double majorTickMarkWidth = rendererInfo.MajorTickMarkWidth;
      double minorTickMarkWidth = rendererInfo.MinorTickMarkWidth;
      XRect rect = rendererInfo.Rect;

      switch (rendererInfo.MajorTickMark)
      {
        case TickMarkType.Inside:
          majorTickMarkStart = rect.X + rect.Width;
          majorTickMarkEnd = rect.X + rect.Width + majorTickMarkWidth;
          break;

        case TickMarkType.Outside:
          majorTickMarkStart = rect.X + rect.Width;
          majorTickMarkEnd   = rect.X + rect.Width - majorTickMarkWidth;
          break;

        case TickMarkType.Cross:
          majorTickMarkStart = rect.X + rect.Width - majorTickMarkWidth;
          majorTickMarkEnd = rect.X + rect.Width + majorTickMarkWidth;
          break;

        //TickMarkType.None:
        default:
          majorTickMarkStart = 0;
          majorTickMarkEnd = 0;
          break;
      }

      switch (rendererInfo.MinorTickMark)
      {
        case TickMarkType.Inside:
          minorTickMarkStart = rect.X + rect.Width;
          minorTickMarkEnd = rect.X + rect.Width + minorTickMarkWidth;
          break;

        case TickMarkType.Outside:
          minorTickMarkStart = rect.X + rect.Width;
          minorTickMarkEnd = rect.X + rect.Width - minorTickMarkWidth;
          break;

        case TickMarkType.Cross:
          minorTickMarkStart = rect.X + rect.Width - minorTickMarkWidth;
          minorTickMarkEnd = rect.X + rect.Width + minorTickMarkWidth;
          break;

        //TickMarkType.None:
        default:
          minorTickMarkStart = 0;
          minorTickMarkEnd = 0;
          break;
      }
    }

    /// <summary>
    /// Determines the smallest and the largest number from all series of the chart.
    /// </summary>
    protected virtual void CalcYAxis(out double yMin, out double yMax)
    {
      yMin = double.MaxValue;
      yMax = double.MinValue;

      foreach (Series series in ((Chart)this.rendererParms.DrawingItem).SeriesCollection)
      {
        foreach (Point point in series.Elements)
        {
          if (!double.IsNaN(point.value))
          {
            yMin = Math.Min(yMin, point.Value);
            yMax = Math.Max(yMax, point.Value);
          }
        }
      }
    }
  }
}
