#region PDFsharp - A .NET library for processing PDF
//
// Authors:
//   Stefan Lange (mailto:Stefan.Lange@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 System.Runtime.InteropServices;
using System.ComponentModel;
using System.IO;
#if Gdip
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
#endif
#if Wpf
using System.Windows.Media;
#endif
using PdfSharp.Internal;
using PdfSharp.Drawing.Pdf;
using PdfSharp.Fonts.TrueType;
using PdfSharp.Pdf;
using PdfSharp.Pdf.IO;
using PdfSharp.Pdf.Advanced;
using PdfSharp.Pdf.Filters;
using PdfSharp.Pdf.Internal;

namespace PdfSharp.Drawing
{
  /// <summary>
  /// 
  /// </summary>
  public class XForm : XImage
  {
    protected enum FormState
    {
      /// <summary>
      /// The form is an imported page.
      /// </summary>
      NotATemplate,

      /// <summary>
      /// The template is just created.
      /// </summary>
      Created,

      /// <summary>
      /// XGraphics.FromForm() was called.
      /// </summary>
      UnderConstruction,

      /// <summary>
      /// The form was drawn at least once and is 'frozen' now.
      /// </summary>
      Finished,
    }

    protected XForm()
    {
    }

    /// <summary>
    /// Initializes a new instance of the XForm class such that it can be drawn on the specifed graphics
    /// object.
    /// </summary>
    /// <param name="gfx">The graphics object that later is used to draw this form.</param>
    /// <param name="size">The size in points of the form.</param>
    public XForm(XGraphics gfx, XSize size)
    {
      if (gfx == null)
        throw new ArgumentNullException("gfx");
      if (size.width < 1 || size.height < 1)
        throw new ArgumentNullException("size", "The size of the XPdfForm is to small.");

      this.fromState = FormState.Created;
      this.templateSize = size;

      // If gfx belongs to a PdfPage also create the PdfFormXObject
      if (gfx.PdfPage != null)
      {
        this.document = gfx.PdfPage.Owner;
        this.pdfForm = new PdfFormXObject(this.document, this);
        PdfRectangle rect = new PdfRectangle(XPoint.Empty, size);
        this.pdfForm.Elements.SetRectangle(PdfFormXObject.Keys.BBox, rect);
      }
    }

    /// <summary>
    /// Initializes a new instance of the XForm class such that it can be drawn on the specifed graphics
    /// object.
    /// </summary>
    /// <param name="gfx">The graphics object that later is used to draw this form.</param>
    /// <param name="width">The width of the form.</param>
    /// <param name="height">The height of the form.</param>
    public XForm(XGraphics gfx, XUnit width, XUnit height)
      : this(gfx, new XSize(width, height))
    { }

    public XForm(PdfDocument document, XSize size)
    {
      if (size.width < 1 || size.height < 1)
        throw new ArgumentNullException("size", "The size of the XPdfForm is to small.");
      // I must tie the XPdfForm to a document immediately, because otherwise I would have no place where
      // to store the resources.
      if (document == null)
        throw new ArgumentNullException("document", "An XPdfForm template must be associated with a document.");

      this.fromState = FormState.Created;
      this.document = document;
      this.pdfForm = new PdfFormXObject(document, this);
      this.templateSize = size;
      PdfRectangle rect = new PdfRectangle(XPoint.Empty, size);
      this.pdfForm.Elements.SetRectangle(PdfFormXObject.Keys.BBox, rect);
    }

    public XForm(PdfDocument document, XUnit width, XUnit height)
      : this(document, new XSize(width, height))
    { }

    /// <summary>
    /// This function should be called when drawing the content of this form is finished.
    /// The XGraphics object used for drawing the content is disposed by this function and 
    /// cannot be used for any further drawing operations.
    /// PDFsharp automatically calls this function when this form was used the first time
    /// in a DrawImage function. 
    /// </summary>
    public void DrawingFinished()
    {
      if (this.fromState == FormState.Finished)
        return;

      if (this.fromState == FormState.NotATemplate)
        throw new InvalidOperationException("This object is an imported PDF page and you cannot finish drawing on it because you must not draw on it at all.");

      Finish();
    }

    /// <summary>
    /// Called from XGraphics constructor that creates an instance that work on this form.
    /// </summary>
    internal void AssociateGraphics(XGraphics gfx)
    {
      if (this.fromState == FormState.NotATemplate)
        throw new NotImplementedException("The current version of PDFsharp cannot draw on an imported page.");

      if (this.fromState == FormState.UnderConstruction)
        throw new InvalidOperationException("An XGraphics object already exists for this form.");

      if (this.fromState == FormState.Finished)
        throw new InvalidOperationException("After drawing a form it cannot be modified anymore.");

      Debug.Assert(this.fromState == FormState.Created);
      this.fromState = FormState.UnderConstruction;
      this.gfx = gfx;
    }
    internal XGraphics gfx;

    protected override void Dispose(bool disposing)
    {
      base.Dispose(disposing);
    }

    /// <summary>
    /// Sets the form in the state FormState.Finished.
    /// </summary>
    internal virtual void Finish()
    {
      if (this.fromState == FormState.NotATemplate || this.fromState == FormState.Finished)
        return;

      if (this.gfx.metafile != null)
        this.image = this.gfx.metafile;

      Debug.Assert(this.fromState == FormState.Created || this.fromState == FormState.UnderConstruction);
      this.fromState = FormState.Finished;
      this.gfx.Dispose();
      this.gfx = null;

      if (this.pdfRenderer != null)
      {
        //this.pdfForm.CreateStream(PdfEncoders.RawEncoding.GetBytes(this.pdfRenderer.GetContent()));
        this.pdfRenderer.Close();
        Debug.Assert(this.pdfRenderer == null);

        if (this.document.Options.CompressContentStreams)
        {
          this.pdfForm.Stream.Value = Filtering.FlateDecode.Encode(this.pdfForm.Stream.Value);
          this.pdfForm.Elements["/Filter"] = new PdfName("/FlateDecode");
        }
        int length = this.pdfForm.Stream.Length;
        this.pdfForm.Elements.SetInteger("/Length", length);
      }
    }

    internal PdfDocument Owner
    {
      get { return this.document; }
    }
    protected PdfDocument document;

    internal bool IsTemplate
    {
      get { return this.fromState != FormState.NotATemplate; }
    }
    protected FormState fromState;

    /// <summary>
    /// Get the width of the page identified by the property PageNumber.
    /// </summary>
    public override int Width
    {
      get { return (int)this.templateSize.width; }
    }

    /// <summary>
    /// Get the width of the page identified by the property PageNumber.
    /// </summary>
    public override int Height
    {
      get { return (int)this.templateSize.height; }
    }

    public override XSize Size
    {
      get { return this.templateSize; }
    }
    XSize templateSize;

    /// <summary>
    /// Gets 72, the horizontal resolution by design of a form object.
    /// </summary>
    public override double HorizontalResolution
    {
      get { return 72; }
    }

    /// <summary>
    /// Gets 72, the vertical resolution by design of a form object.
    /// </summary>
    public override double VerticalResolution
    {
      get { return 72; }
    }

    public XRect BoundingBox
    {
      get { return this.boundingBox; }
      set { this.boundingBox = value; }  // TODO: pdfForm = null
    }
    XRect boundingBox;

    public virtual XMatrix Transform
    {
      get { return this.transform; }
      set
      {
        if (this.fromState == FormState.Finished)
          throw new InvalidOperationException("After a XPdfForm was once drawn it must not be modified.");
        this.transform = value;
      }
    }
    protected XMatrix transform = XMatrix.Identity;

    internal PdfResources Resources
    {
      get
      {
        Debug.Assert(IsTemplate, "This function is for form templates only.");
        if (this.resources == null)
          this.resources = (PdfResources)this.pdfForm.Elements.GetValue(PdfFormXObject.Keys.Resources, VCF.CreateIndirect);
        return this.resources;
      }
    }
    PdfResources resources;

    /// <summary>
    /// Gets the resource name of the specified font within this form.
    /// </summary>
    internal string GetFontName(XFont font, out PdfFont pdfFont)
    {
      Debug.Assert(IsTemplate, "This function is for form templates only.");
      pdfFont = this.document.FontTable.GetFont(font);
      Debug.Assert(pdfFont != null);
      string name = Resources.AddFont(pdfFont);
      return name;
    }

    /// <summary>
    /// Gets the resource name of the specified image within this form.
    /// </summary>
    internal string GetImageName(XImage image)
    {
      Debug.Assert(IsTemplate, "This function is for form templates only.");
      PdfImage pdfImage = this.document.ImageTable.GetImage(image);
      Debug.Assert(pdfImage != null);
      string name = Resources.AddImage(pdfImage);
      return name;
    }

    internal PdfFormXObject PdfForm
    {
      get
      {
        Debug.Assert(IsTemplate, "This function is for form templates only.");
        if (this.pdfForm.Reference == null)
          this.document.irefTable.Add(this.pdfForm);
        return this.pdfForm;
      }
    }

    /// <summary>
    /// Gets the resource name of the specified form within this form.
    /// </summary>
    internal string GetFormName(XForm form)
    {
      Debug.Assert(IsTemplate, "This function is for form templates only.");
      PdfFormXObject pdfForm = this.document.FormTable.GetForm(form);
      Debug.Assert(pdfForm != null);
      string name = Resources.AddForm(pdfForm);
      return name;
    }

    /// <summary>
    /// The PdfFormXObject gets invalid when PageNumber or transform changed. This is because a modification
    /// of an XPdfForm must not change objects that are already been drawn.
    /// </summary>
    internal PdfFormXObject pdfForm;

    internal XGraphicsPdfRenderer pdfRenderer;
  }
}
