#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.Collections;
using System.Reflection;
using System.Text;
using System.IO;
using PdfSharp.Internal;
using PdfSharp.Pdf.IO;
using PdfSharp.Pdf.Filters;
using PdfSharp.Pdf.Advanced;
using PdfSharp.Pdf.Internal;

namespace PdfSharp.Pdf
{
  public enum VCF
  {
    None, Create, CreateIndirect,
  }

  /// <summary>
  /// Represents a PDF dictionary object.
  /// </summary>
  [DebuggerDisplay("(pairs={Elements.Count})")]
  public class PdfDictionary : PdfObject, IEnumerable
  {
    protected DictionaryElements elements;

    public PdfDictionary()
    {
    }

    public PdfDictionary(PdfDocument document)
      : base(document)
    {
    }

    /// <summary>
    /// Initializes a new instance from an existing dictionary. Used for object type transformation.
    /// </summary>
    protected PdfDictionary(PdfDictionary dict)
      : base(dict)
    {
      if (dict.elements != null)
        dict.elements.SetOwner(this);
      if (dict.stream != null)
        dict.stream.SetOwner(this);
    }

    /// <summary>
    /// Creates a copy of this dictionary. Direct values are deep copied. Indirect references are not
    /// modified.
    /// </summary>
    public new PdfDictionary Clone()
    {
      return (PdfDictionary)Copy();
    }

    /// <summary>
    /// This function is useful for importing objects from external documents. The returned object is not
    /// yet complete. irefs refer to external objects and directed objects are coloned but their document
    /// property is null. A cloned dictionary or array needs a 'fix-up' to be a valid object.
    /// </summary>
    protected override object Copy()
    {
      PdfDictionary dict = (PdfDictionary)base.Copy();
      if (dict.elements != null)
      {
        dict.elements = dict.elements.Clone();
        dict.elements.SetOwner(dict);
        PdfName[] names = dict.elements.KeyNames;
        foreach (PdfName name in names)
        {
          PdfObject obj = dict.elements[name] as PdfObject;
          if (obj != null)
          {
            obj = obj.Clone();
            // Recall that obj.Document is now null
            dict.elements[name] = obj;
          }
        }
      }
      if (dict.stream != null)
      {
        dict.stream = dict.stream.Clone();
        dict.stream.SetOwner(dict);
      }
      return dict;
    }

    /// <summary>
    /// Gets the hashtable containing the elements of this dictionary.
    /// </summary>
    public DictionaryElements Elements
    {
      get
      {
        if (this.elements == null)
          this.elements = new DictionaryElements(this);
        return this.elements;
      }
    }

    public IEnumerator GetEnumerator()
    {
      return Elements.GetEnumerator();
    }

    /// <summary>
    /// Returns a string with the content of this object in a readable form. Useful for debugging purposes only.
    /// </summary>
    public override string ToString()
    {
      // Get keys and sort
      PdfName[] keys = Elements.KeyNames;
      ArrayList list = new ArrayList(keys);
      list.Sort(PdfName.Comparer);
      list.CopyTo(keys, 0);

      StringBuilder pdf = new StringBuilder();
      pdf.Append("<< ");
      foreach (PdfName key in keys)
        pdf.Append(key.ToString() + " " + Elements[key].ToString() + " ");
      pdf.Append(">>");

      return pdf.ToString();
    }

    internal override void WriteObject(PdfWriter writer)
    {
      writer.WriteBeginObject(this);
      //int count = Elements.Count;
      PdfName[] keys = Elements.KeyNames;

#if DEBUG
      // TODO: automatically set length
      if (this.stream != null)
        Debug.Assert(Elements.Contains(PdfDictionary.PdfStream.Keys.Length), "Dictionary has a stream but no length is set.");
#endif

#if DEBUG
      // Sort keys for debugging purposes. Comparing PDF files with for example programms like
      // Araxis Merge is easier with sorted keys.
      if (writer.Layout == PdfWriterLayout.Verbose)
      {
        ArrayList list = new ArrayList(keys);
        list.Sort(PdfName.Comparer);
        list.CopyTo(keys, 0);
      }
#endif

      foreach (PdfName key in keys)
        WriteDictionaryElement(writer, key);
      if (Stream != null)
        WriteDictionaryStream(writer);
      writer.WriteEndObject();
    }

    /// <summary>
    /// Writes a key/value pair of this dictionary. This function is intended to be overridden
    /// in derived classes.
    /// </summary>
    internal virtual void WriteDictionaryElement(PdfWriter writer, PdfName key)
    {
      if (key == null)
        throw new ArgumentNullException("key");
      PdfItem item = Elements[key];
      key.WriteObject(writer);
      item.WriteObject(writer);
      writer.NewLine();
    }

    /// <summary>
    /// Writes the stream of this dictionary. This function is intended to be overridden
    /// in a derived class.
    /// </summary>
    internal virtual void WriteDictionaryStream(PdfWriter writer)
    {
      writer.WriteStream(this, (writer.Options & PdfWriterOptions.OmitStream) == PdfWriterOptions.OmitStream);
    }

    /// <summary>
    /// Gets or sets the PDF stream belonging to this dictionary. Returns null if the dictionary has
    /// no stream. To create the stream, call the CreateStream function.
    /// </summary>
    public PdfStream Stream
    {
      get { return this.stream; }
      set { this.stream = value; }
    }
    PdfStream stream;

    /// <summary>
    /// Creates the stream of this dictionary and initializes it with the specified byte array.
    /// The function must not be called if the dictionary already has a strem.
    /// </summary>
    public PdfStream CreateStream(byte[] value)
    {
      if (this.stream != null)
        throw new InvalidOperationException("The dictionary already has a stream.");

      this.stream = new PdfStream(value, this);
      // Always set the length
      Elements[PdfStream.Keys.Length] = new PdfInteger(this.stream.Length);
      return this.stream;
    }

    /// <summary>
    /// When overridden in a derived class, gets the KeysMeta of this dictionary type.
    /// </summary>
    internal virtual DictionaryMeta Meta
    {
      get { return null; }
    }

    /// <summary>
    /// Represents the interface to the elements of a PDF dictionary.
    /// </summary>
    public sealed class DictionaryElements : IDictionary, ICloneable
    {
      Hashtable elements;
      PdfDictionary owner;

      internal DictionaryElements(PdfDictionary dict)
      {
        this.elements = new Hashtable();
        this.owner = dict;
      }

      object ICloneable.Clone()
      {
        DictionaryElements elements = (DictionaryElements)MemberwiseClone();
        elements.elements = (Hashtable)elements.elements.Clone();
        elements.owner = null;
        return elements;
      }

      /// <summary>
      /// Creates a shallow copy of this object. The clone is not owned by a dictionary anymore.
      /// </summary>
      public DictionaryElements Clone()
      {
        return (DictionaryElements)((ICloneable)this).Clone();
      }

      /// <summary>
      /// Moves this instance to another dictionary during object type transformation.
      /// </summary>
      internal void SetOwner(PdfDictionary dict)
      {
        this.owner = dict;
        //???
        //if (dict.elements != null)
        //  Debug.Assert(dict.elements == this);
        dict.elements = this;
      }

      internal PdfDictionary Owner
      {
        get { return this.owner; }
      }

      /// <summary>
      /// Converts the specified value to boolean.
      /// If the value not exists, the function returns false.
      /// If the value is not convertible, the function throws an InvalidCastException.
      /// </summary>
      public bool GetBoolean(string key, bool create)
      {
        object obj = this[key];
        if (obj == null)
        {
          if (create)
            this[key] = new PdfBoolean();
          return false;
        }
        if (obj is PdfReference)
          obj = ((PdfReference)obj).Value;

        if (obj is PdfBoolean)
          return ((PdfBoolean)obj).Value;
        else if (obj is PdfBooleanObject)
          return ((PdfBooleanObject)obj).Value;
        throw new InvalidCastException("GetBoolean: Object is not a boolean.");
      }

      public bool GetBoolean(string key)
      {
        return GetBoolean(key, false);
      }

      public void SetBoolean(string key, bool value)
      {
        this[key] = new PdfBoolean(value);
      }

      /// <summary>
      /// Converts the specified value to integer.
      /// If the value not exists, the function returns 0.
      /// If the value is not convertible, the function throws an InvalidCastException.
      /// </summary>
      public int GetInteger(string key, bool create)
      {
        object obj = this[key];
        if (obj == null)
        {
          if (create)
            this[key] = new PdfInteger();
          return 0;
        }
        if (obj is PdfReference)
          obj = ((PdfReference)obj).Value;

        if (obj is PdfInteger)
          return ((PdfInteger)obj).Value;
        if (obj is PdfIntegerObject)
          return ((PdfIntegerObject)obj).Value;
        throw new InvalidCastException("GetInteger: Object is not an integer.");
      }

      public int GetInteger(string key)
      {
        return GetInteger(key, false);
      }

      public void SetInteger(string key, int value)
      {
        this[key] = new PdfInteger(value);
      }

      /// <summary>
      /// Converts the specified value to double.
      /// If the value not exists, the function returns 0.
      /// If the value is not convertible, the function throws an InvalidCastException.
      /// </summary>
      public double GetReal(string key, bool create)
      {
        object obj = this[key];
        if (obj == null)
        {
          if (create)
            this[key] = new PdfReal();
          return 0;
        }
        if (obj is PdfReference)
          obj = ((PdfReference)obj).Value;

        if (obj is PdfReal)
          return ((PdfReal)obj).Value;
        else if (obj is PdfRealObject)
          return ((PdfRealObject)obj).Value;
        else if (obj is PdfInteger)
          return ((PdfInteger)obj).Value;
        else if (obj is PdfIntegerObject)
          return ((PdfIntegerObject)obj).Value;
        throw new InvalidCastException("GetReal: Object is not a number.");
      }

      public double GetReal(string key)
      {
        return GetReal(key, false);
      }

      public void SetReal(string key, double value)
      {
        this[key] = new PdfReal(value);
      }

      /// <summary>
      /// Converts the specified value to String.
      /// If the value not exists, the function returns the empty string.
      /// </summary>
      public string GetString(string key, bool create)
      {
        object obj = this[key];
        if (obj == null)
        {
          if (create)
            this[key] = new PdfString();
          return "";
        }
        if (obj is PdfReference)
          obj = ((PdfReference)obj).Value;

        if (obj is PdfString)
          return ((PdfString)obj).Value;
        else if (obj is PdfStringObject)
          return ((PdfStringObject)obj).Value;
        else if (obj is PdfName)
          return ((PdfName)obj).Value;
        else if (obj is PdfNameObject)
          return ((PdfNameObject)obj).Value;
        throw new InvalidCastException("GetString: Object is not a string.");
      }

      public string GetString(string key)
      {
        return GetString(key, false);
      }

      public void SetString(string key, string value)
      {
        this[key] = new PdfString(value);
      }

      /// <summary>
      /// Converts the specified value to a name.
      /// If the value not exists, the function returns the empty string.
      /// </summary>
      public string GetName(string key)
      {
        object obj = this[key];
        if (obj == null)
        {
          //if (create)
          //  this[key] = new Pdf();
          return "";
        }
        if (obj is PdfReference)
          obj = ((PdfReference)obj).Value;

        if (obj is PdfName)
          return ((PdfName)obj).Value;
        else if (obj is PdfNameObject)
          return ((PdfNameObject)obj).Value;

        throw new InvalidCastException("GetName: Object is not a name.");
      }

      /// <summary>
      /// Sets the specified name value.
      /// If the value doesn't start with a slash, it is added automatically.
      /// </summary>
      public void SetName(string key, string value)
      {
        if (value == null)
          throw new ArgumentNullException("value");

        if (value.Length == 0 || value[0] != '/')
          value = "/" + value;

        this[key] = new PdfName(value);
      }

      /// <summary>
      /// Converts the specified value to PdfRectangle.
      /// If the value not exists, the function returns an empty rectangle.
      /// If the value is not convertible, the function throws an InvalidCastException.
      /// </summary>
      public PdfRectangle GetRectangle(string key, bool create)
      {
        PdfRectangle value = new PdfRectangle();
        object obj = this[key];
        if (obj == null)
        {
          if (create)
            this[key] = value = new PdfRectangle();
          return value;
        }
        if (obj is PdfReference)
          obj = ((PdfReference)obj).Value;

        PdfArray array = obj as PdfArray;
        if (array != null && array.Elements.Count == 4)
        {
          value = new PdfRectangle(array.Elements.GetReal(0), array.Elements.GetReal(1),
            array.Elements.GetReal(2), array.Elements.GetReal(3));
          this[key] = value;
        }
        else
          value = (PdfRectangle)obj;
        return value;
      }

      public PdfRectangle GetRectangle(string key)
      {
        return GetRectangle(key, false);
      }

      public void SetRectangle(string key, PdfRectangle rect)
      {
        this.elements[key] = rect;
      }

      public DateTime GetDateTime(string key, DateTime defaultValue)
      {
        object obj = this[key];
        if (obj == null)
        {
          //if (create)
          //  this[key] = new Pdf();
          return defaultValue;
        }
        if (obj is PdfReference)
          obj = ((PdfReference)obj).Value;

        if (obj is PdfDate)
          return ((PdfDate)obj).Value;

        string date = "";
        if (obj is PdfString)
          date = ((PdfString)obj).Value;
        else if (obj is PdfStringObject)
          date = ((PdfNameObject)obj).Value;
        else
          throw new InvalidCastException("GetName: Object is not a name.");

        if (date != "")
        {
          try
          {
            defaultValue = Parser.ParseDateTime(date, defaultValue);
          }
          catch { }
        }
        return defaultValue;
      }

      public void SetDateTime(string key, DateTime value)
      {
        this.elements[key] = new PdfDate(value);
      }

      internal int GetEnumFromName(string key, object defaultValue, bool create)
      {
        if (!(defaultValue is Enum))
          throw new ArgumentException("defaultValue");

        object obj = this[key];
        if (obj == null)
        {
          if (create)
            this[key] = new PdfName(defaultValue.ToString());
          return (int)defaultValue;
        }
        Debug.Assert(obj is Enum);
        return (int)Enum.Parse(defaultValue.GetType(), obj.ToString().Substring(1));
      }

      internal int GetEnumFromName(string key, object defaultValue)
      {
        return GetEnumFromName(key, defaultValue, false);
      }

      internal void SetEnumAsName(string key, object value)
      {
        if (!(value is Enum))
          throw new ArgumentException("value");
        this.elements[key] = new PdfName("/" + value.ToString());
      }

      /// <summary>
      /// Gets the value for the specified key. If the value does not exists, it is optionally created.
      /// </summary>
      public PdfItem GetValue(string key, VCF options)
      {
        PdfObject obj;
        PdfDictionary dict;
        PdfArray array;
        PdfReference iref;
        PdfItem value = this[key];
        if (value == null)
        {
          if (options != VCF.None)
          {
            Type type = GetValueType(key);
            if (type != null)
            {
              Debug.Assert(typeof(PdfItem).IsAssignableFrom(type), "Type not allowed.");
              if (typeof(PdfDictionary).IsAssignableFrom(type))
              {
                value = obj = CreateDictionary(type, null);
              }
              else if (typeof(PdfArray).IsAssignableFrom(type))
              {
                value = obj = CreateArray(type, null);
              }
              else
                throw new NotImplementedException("Type other than array or dictionary.");

              if (options == VCF.CreateIndirect)
              {
                this.owner.Owner.irefTable.Add(obj);
                this[key] = obj.Reference;
              }
              else
                this[key] = obj;
            }
            else
              throw new NotImplementedException("Cannot create value for key: " + key);
          }
        }
        else
        {
          // The value exists and can returned. But for imported documents check for neccessary
          // object type transformation.
          if ((iref = value as PdfReference) != null)
          {
            // Case: value is an indirect reference
            value = iref.Value;
            if (value == null)
            {
              // If we come here PDF file is currupt
              throw new InvalidOperationException("Indirect reference without value.");
            }
            else
            {
              if (true) // || this.owner.Document.IsImported)
              {
                Type type = GetValueType(key);
                Debug.Assert(type != null, "No value type specified in meta information. Please send this file to PDFsharp support.");

                if (type != null && type != value.GetType())
                {
                  if (typeof(PdfDictionary).IsAssignableFrom(type))
                  {
                    Debug.Assert(value is PdfDictionary, "Bug in PDFsharp. Please send this file to PDFsharp support.");
                    value = CreateDictionary(type, (PdfDictionary)value);
                  }
                  else if (typeof(PdfArray).IsAssignableFrom(type))
                  {
                    Debug.Assert(value is PdfArray, "Bug in PDFsharp. Please send this file to PDFsharp support.");
                    value = CreateArray(type, (PdfArray)value);
                  }
                  else
                    throw new NotImplementedException("Type other than array or dictionary.");
                }
              }
            }
            return value;
          }
          // Transformation is only possible after PDF import.
          if (true) // || this.owner.Document.IsImported)
          {
            // Case: value is a direct object
            if ((dict = value as PdfDictionary) != null)
            {
              Type type = GetValueType(key);
              Debug.Assert(type != null, "No value type specified in meta information. Please send this file to PDFsharp support.");
              if (dict.GetType() != type)
                dict = CreateDictionary(type, dict);
              return dict;
            }
            else if ((array = value as PdfArray) != null)
            {
              Type type = GetValueType(key);
              Debug.Assert(type != null, "No value type specified in meta information. Please send this file to PDFsharp support.");
              if (array.GetType() != type)
                array = CreateArray(type, array);
              return array;
            }
          }
        }
        return value;
      }

      /// <summary>
      /// Short cut for GetValue(key, VCF.None).
      /// </summary>
      public PdfItem GetValue(string key)
      {
        return GetValue(key, VCF.None);
      }

      /// <summary>
      /// Returns the type of the object to be created as value of the specified key.
      /// </summary>
      Type GetValueType(string key)  // TODO: move to PdfObject
      {
        Type type = null;
        DictionaryMeta meta = this.owner.Meta;
        if (meta != null)
        {
          KeyDescriptor kd = meta[key];
          if (kd != null)
            type = kd.GetValueType();
          else
            Debug.WriteLine("Warning: Key not desciptor table: " + key);  // TODO: check what this means...
        }
        else
          Debug.WriteLine("Warning: No meta provided for type: " + this.owner.GetType().Name);  // TODO: check what this means...
        return type;
      }

      PdfArray CreateArray(Type type, PdfArray oldArray)
      {
        ConstructorInfo ctorInfo;
        PdfArray array;
        if (oldArray == null)
        {
          // Use contstructor with signature 'Ctor(PdfDocument owner)'.
          ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
            null, new Type[] { typeof(PdfDocument) }, null);
          Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name);
          array = ctorInfo.Invoke(new object[] { this.owner.Owner }) as PdfArray;
        }
        else
        {
          // Use contstructor with signature 'Ctor(PdfDictionary dict)'.
          ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
            null, new Type[] { typeof(PdfArray) }, null);
          Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name);
          array = ctorInfo.Invoke(new object[] { oldArray }) as PdfArray;
        }
        return array;
      }

      PdfDictionary CreateDictionary(Type type, PdfDictionary oldDictionary)
      {
        ConstructorInfo ctorInfo;
        PdfDictionary dict;
        if (oldDictionary == null)
        {
          // Use contstructor with signature 'Ctor(PdfDocument owner)'.
          ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
            null, new Type[] { typeof(PdfDocument) }, null);
          Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name);
          dict = ctorInfo.Invoke(new object[] { this.owner.Owner }) as PdfDictionary;
        }
        else
        {
          // Use contstructor with signature 'Ctor(PdfDictionary dict)'.
          ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
            null, new Type[] { typeof(PdfDictionary) }, null);
          Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name);
          dict = ctorInfo.Invoke(new object[] { oldDictionary }) as PdfDictionary;
        }
        return dict;
      }

      PdfItem CreateValue(Type type, PdfDictionary oldValue)
      {
        ConstructorInfo ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
          null, new Type[] { typeof(PdfDocument) }, null);
        PdfObject obj = ctorInfo.Invoke(new object[] { this.owner.Owner }) as PdfObject;
        if (oldValue != null)
        {
          obj.Reference = oldValue.Reference;
          obj.Reference.Value = obj;
          if (obj is PdfDictionary)
          {
            PdfDictionary dict = (PdfDictionary)obj;
            dict.elements = ((PdfDictionary)oldValue).elements;
          }
        }
        return obj;
      }

      // HACK
      public void SetValue(string key, PdfItem value)
      {
        Debug.Assert((value is PdfObject && ((PdfObject)value).Reference == null) | !(value is PdfObject),
          "You try to set an indirect object directly into a dictionary.");

        this.elements[key] = value;
      }

      /// <summary>
      /// Returns the indirect object if the value of the specified key is a PdfReference.
      /// </summary>
      [Obsolete("Use GetObject, GetDictionary, GetArray, or GetReference")]
      public PdfObject GetIndirectObject(string key)
      {
        PdfItem item = this[key];
        if (item is PdfReference)
          return ((PdfReference)item).Value;
        return null;
      }

      /// <summary>
      /// Gets the PdfObject with the specified key, or null, if no such object exists. If the key refers to
      /// a reference, the referenced PdfObject is returned.
      /// </summary>
      public PdfObject GetObject(string key)
      {
        PdfItem item = this[key];
        if (item is PdfReference)
          return ((PdfReference)item).Value;
        return item as PdfObject;
      }

      /// <summary>
      /// Gets the PdfArray with the specified key, or null, if no such object exists. If the key refers to
      /// a reference, the referenced PdfArray is returned.
      /// </summary>
      public PdfDictionary GetDictionary(string key)
      {
        return GetObject(key) as PdfDictionary;
      }

      /// <summary>
      /// Gets the PdfArray with the specified key, or null, if no such object exists. If the key refers to
      /// a reference, the referenced PdfArray is returned.
      /// </summary>
      public PdfArray GetArray(string key)
      {
        return GetObject(key) as PdfArray;
      }

      /// <summary>
      /// Gets the PdfReference with the specified key, or null, if no such object exists.
      /// </summary>
      public PdfReference GetReference(string key)
      {
        PdfItem item = this[key];
        return item as PdfReference;
      }

      public void SetReference(string key, PdfObject obj)
      {
        Debug.Assert(obj.Reference != null, "PdfObject must be an indirect object.");
        this[key] = obj.Reference;
      }

      #region IDictionary Members

      public bool IsReadOnly
      {
        get { return false; }
      }

      public IDictionaryEnumerator GetEnumerator()
      {
        return this.elements.GetEnumerator();
      }

      object IDictionary.this[object key]
      {
        get { return this.elements[key]; }
        set
        {
          if (key == null)
            throw new ArgumentNullException("key");
          if (!(key is string))
            throw new ArgumentException("Key must be of type System.String.");
          if (((string)key) == "")
            throw new ArgumentException(PSSR.NameMustStartWithSlash, "key");
          if (((string)key)[0] != '/')
            throw new ArgumentException(PSSR.NameMustStartWithSlash, "key");

          this.elements[key] = value;
        }
      }

      /// <summary>
      /// Gets or sets an entry in the dictionary. The specified key must be a valid PDF name
      /// starting with a slash '/'. This property provides full access to the elements of the
      /// PDF dictionary. Wrong use can lead to errors or corrupt PDF files.
      /// </summary>
      public PdfItem this[string key]
      {
        get { return (PdfItem)((IDictionary)this)[key]; }
        set
        {
          if (value == null)
            throw new ArgumentNullException("value");
#if DEBUG
          if (key == "/MediaBox")
            key.GetType();

          if (value is PdfObject)
          {
            PdfObject obj = (PdfObject)value;
            if (obj.Reference != null)
              throw new ArgumentException("An object with an indirect reference cannot be a direct value. Try to set an indirect refernece.");
          }
          if (value is PdfDictionary)
          {
            PdfDictionary dict = (PdfDictionary)value;
            if (dict.stream != null)
              throw new ArgumentException("A dictionary with stream cannot be a direct value.");
          }
#endif
          ((IDictionary)this)[key] = value;
        }
      }

      /// <summary>
      /// Gets or sets an entry in the dictionary identified by a PdfName object.
      /// </summary>
      public PdfItem this[PdfName key]
      {
        get { return (PdfItem)((IDictionary)this)[key.Value]; }
        set { ((IDictionary)this)[key.Value] = value; }
      }

      void IDictionary.Remove(object key)
      {
        this.elements.Remove(key);
      }

      public void Remove(string key)
      {
        this.elements.Remove(key);
      }

      bool IDictionary.Contains(object key)
      {
        return this.elements.Contains(key);
      }

      /// <summary>
      /// Determines whether the dictionary contains the specified name.
      /// </summary>
      public bool Contains(string key)
      {
        return this.elements.Contains(key);
      }

      public void Clear()
      {
        this.elements.Clear();
      }

      void IDictionary.Add(object key, object value)
      {
        if (key == null)
          throw new ArgumentNullException("key");
        if (!(key is string))
          throw new ArgumentException("key must be of type System.String.");
        if (((string)key) == "")
          throw new ArgumentException("key");
        if (((string)key)[0] != '/')
          throw new ArgumentException("The key must start with a slash '/'.");

        // If object is indirect automatically convert value to reference.
        PdfObject obj = value as PdfObject;
        if (obj != null && obj.IsIndirect)
          value = obj.Reference;

        this.elements.Add(key, value);
      }

      public void Add(object key, PdfItem value)
      {
        ((IDictionary)this).Add(key, value);
      }

      ICollection IDictionary.Keys
      {
        get { return this.elements.Keys; }
      }

      /// <summary>
      /// Gets all keys currently in use in this dictionary as an array of PdfName objects.
      /// </summary>
      public PdfName[] KeyNames
      {
        get
        {
          ICollection values = this.elements.Keys;
          int count = values.Count;
          string[] strings = new string[count];
          values.CopyTo(strings, 0);
          PdfName[] names = new PdfName[count];
          for (int idx = 0; idx < count; idx++)
            names[idx] = new PdfName(strings[idx]);
          return names;
        }
      }

      /// <summary>
      /// Get all keys currently in use in this dictionary as an array of string objects.
      /// </summary>
      public string[] Keys
      {
        get
        {
          ICollection values = this.elements.Keys;
          int count = values.Count;
          string[] keys = new string[count];
          values.CopyTo(keys, 0);
          return keys;
        }
      }

      ICollection IDictionary.Values
      {
        get { return this.elements.Values; }
      }

      /// <summary>
      /// Gets all values currently in use in this dictionary as an array of PdfItem objects.
      /// </summary>
      public PdfItem[] Values
      {
        get
        {
          ICollection values = this.elements.Values;
          PdfItem[] items = new PdfItem[values.Count];
          values.CopyTo(items, 0);
          return items;
        }
      }

      public bool IsFixedSize
      {
        get { return false; }
      }

      #endregion

      #region ICollection Members

      public bool IsSynchronized
      {
        get { return false; }
      }

      public int Count
      {
        get { return this.elements.Count; }
      }

      public void CopyTo(Array array, int index)
      {
        this.elements.CopyTo(array, index);
      }

      public object SyncRoot
      {
        get { return null; }
      }

      #endregion

      #region IEnumerable Members

      IEnumerator System.Collections.IEnumerable.GetEnumerator()
      {
        return ((ICollection)this.elements).GetEnumerator();
      }

      #endregion
    }

    /// <summary>
    /// Base class for all PDF stream objects.
    /// </summary>
    public sealed class PdfStream
    {
      /// <summary>
      /// The dictionary the stream belongs to.
      /// </summary>
      PdfDictionary owner;

      internal PdfStream(PdfDictionary owner)
      {
        if (owner == null)
          throw new ArgumentNullException("owner");
        this.owner = owner;
      }

      /// <summary>
      /// A .NET string can contain char(0) as a valid character.
      /// </summary>
      internal PdfStream(byte[] value, PdfDictionary owner)
        : this(owner)
      {
        this.value = value;
      }

      public PdfStream Clone()
      {
        PdfStream stream = (PdfStream)MemberwiseClone();
        stream.owner = null;
        if (stream.value != null)
        {
          stream.value = new byte[stream.value.Length];
          this.value.CopyTo(stream.value, 0);
        }
        return stream;
      }

      /// <summary>
      /// Moves this instance to another dictionary during object type transformation.
      /// </summary>
      internal void SetOwner(PdfDictionary dict)
      {
        this.owner = dict;
        owner.stream = this;
      }

      public int Length
      {
        get { return this.value != null ? value.Length : 0; }
      }

      //    /// <summary>
      //    /// Gets the native length of the stream, i.e. the number of bytes when they are neither
      //    /// compressed nor encrypted.
      //    /// </summary>
      //    public int Length
      //    {
      //      get {return this.length;}
      //    }

      /// <summary>
      /// Get or sets the bytes of the stream as they are, i.e. if one or more filters exists the bytes are
      /// not unfiltered.
      /// </summary>
      public byte[] Value
      {
        get { return this.value; }
        set 
        {
          if (value == null)
            throw new ArgumentNullException("value");
          this.value = value;
          this.owner.Elements.SetInteger(Keys.Length, value.Length);
        }
      }
      byte[] value;

      /// <summary>
      /// Gets the value of the stream unfiltered. The stream content is not modified by this operation.
      /// </summary>
      public byte[] UnfilteredValue
      {
        get
        {
          byte[] bytes = null;
          if (this.value != null)
          {
            PdfItem filter = this.owner.Elements["/Filter"];
            if (filter != null)
            {
              bytes = Filtering.Decode(this.value, filter);
              if (bytes == null)
              {
                string message = String.Format("«Cannot decode filter '{0}'»", filter.ToString());
                bytes = PdfEncoders.RawEncoding.GetBytes(message);
              }
            }
            else
            {
              bytes = new byte[this.value.Length];
              this.value.CopyTo(bytes, 0);
            }
          }
          return bytes == null ? new byte[0] : bytes;
        }
      }

      /// <summary>
      /// Returns the strem content a raw string.
      /// </summary>
      public override string ToString()
      {
        if (this.value == null)
          return "«null»";

        string stream = "";
        PdfItem filter = this.owner.Elements["/Filter"];
        if (filter != null)
        {
#if true
          byte[] bytes = Filtering.Decode(this.value, filter);
          if (bytes != null)
            stream = PdfEncoders.RawEncoding.GetString(bytes);
#else

          if (this.owner.Elements.GetString("/Filter") == "/FlateDecode")
          {
            stream = Filtering.FlateDecode.DecodeToString(this.value);
          }
#endif
          else
            throw new NotImplementedException("Unknown filter");
        }
        else
          stream = PdfEncoders.RawEncoding.GetString(this.value);

        return stream;
      }

      //internal void WriteObject_(Stream stream)
      //{
      //  if (this.value != null)
      //    stream.Write(this.value, 0, this.value.Length);
      //}

      /// <summary>
      /// Common keys for all streams.
      /// </summary>
      public class Keys : KeysBase
      {
        /// <summary>
        /// (Required) The number of bytes from the beginning of the line following the keyword
        /// stream to the last byte just before the keywordendstream. (There may be an additional
        /// EOL marker, preceding endstream, that is not included in the count and is not logically
        /// part of the stream data.)
        /// </summary>
        [KeyInfo(KeyType.Integer | KeyType.Required)]
        public const string Length = "/Length";

        /// <summary>
        /// (Optional) The name of a filter to be applied in processing the stream data found between
        /// the keywords stream and endstream, or an array of such names. Multiple filters should be
        /// specified in the order in which they are to be applied.
        /// </summary>
        [KeyInfo(KeyType.NameOrArray | KeyType.Optional)]
        public const string Filter = "/Filter";

        /// <summary>
        /// (Optional) A parameter dictionary or an array of such dictionaries, used by the filters
        /// specified by Filter. If there is only one filter and that filter has parameters, DecodeParms
        /// must be set to the filter’s parameter dictionary unless all the filter’s parameters have
        /// their default values, in which case the DecodeParms entry may be omitted. If there are 
        /// multiple filters and any of the filters has parameters set to nondefault values, DecodeParms
        /// must be an array with one entry for each filter: either the parameter dictionary for that
        /// filter, or the null object if that filter has no parameters (or if all of its parameters have
        /// their default values). If none of the filters have parameters, or if all their parameters
        /// have default values, the DecodeParms entry may be omitted.
        /// </summary>
        [KeyInfo(KeyType.ArrayOrDictionary | KeyType.Optional)]
        public const string DecodeParms = "/DecodeParms";

        /// <summary>
        /// (Optional; PDF 1.2) The file containing the stream data. If this entry is present, the bytes
        /// between stream and endstream are ignored, the filters are specified by FFilter rather than
        /// Filter, and the filter parameters are specified by FDecodeParms rather than DecodeParms.
        /// However, the Length entry should still specify the number of those bytes. (Usually, there are
        /// no bytes and Length is 0.)
        /// </summary>
        [KeyInfo("1.2", KeyType.String | KeyType.Optional)]
        public const string F = "/F";

        /// <summary>
        /// (Optional; PDF 1.2) The name of a filter to be applied in processing the data found in the
        /// stream’s external file, or an array of such names. The same rules apply as for Filter.
        /// </summary>
        [KeyInfo("1.2", KeyType.NameOrArray | KeyType.Optional)]
        public const string FFilter = "/FFilter";

        /// <summary>
        /// (Optional; PDF 1.2) A parameter dictionary, or an array of such dictionaries, used by the
        /// filters specified by FFilter. The same rules apply as for DecodeParms.
        /// </summary>
        [KeyInfo("1.2", KeyType.ArrayOrDictionary | KeyType.Optional)]
        public const string FDecodeParms = "/FDecodeParms";

        /// <summary>
        /// Optional; PDF 1.5) A non-negative integer representing the number of bytes in the decoded
        /// (defiltered) stream. It can be used to determine, for example, whether enough disk space is
        /// available to write a stream to a file.
        /// This value should be considered a hint only; for some stream filters, it may not be possible
        /// to determine this value precisely.
        /// </summary>
        [KeyInfo("1.5", KeyType.Integer | KeyType.Optional)]
        public const string DL = "/DL";
      }
    }
  }
}
