#region MigraDoc - Creating Documents on the Fly
//
// Authors:
//   Stefan Lange
//   Klaus Potzesny
//   David Stephensen
//
// Copyright (c) 2001-2017 empira Software GmbH, Cologne Area (Germany)
//
// http://www.pdfsharp.com
// http://www.migradoc.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 MigraDoc.DocumentObjectModel.publics;
using MigraDoc.DocumentObjectModel.Visitors;

namespace MigraDoc.DocumentObjectModel
{
    /// <summary>
    /// Represents style templates for paragraph or character formatting.
    /// </summary>
    public sealed class Style : DocumentObject, IVisitable
    {
        /// <summary>
        /// Initializes a new instance of the Style class.
        /// </summary>
        public Style()
        { }

        /// <summary>
        /// Initializes a new instance of the Style class with the specified parent.
        /// </summary>
        public Style(DocumentObject parent) : base(parent) { }

        /// <summary>
        /// Initializes a new instance of the Style class with name and base style name.
        /// </summary>
        public Style(string name, string baseStyleName)
            : this()
        {
            // baseStyleName can be null or empty
            if (name == null)
                throw new ArgumentNullException("name");
            if (name == "")
                throw new ArgumentException("name");

            _name.Value = name;
            _baseStyle.Value = baseStyleName;
        }

        /// <summary>
        /// Creates a deep copy of this object.
        /// </summary>
        public new Style Clone()
        {
            return (Style)DeepCopy();
        }

        /// <summary>
        /// Implements the deep copy of the object.
        /// </summary>
        protected override object DeepCopy()
        {
            Style style = (Style)base.DeepCopy();
            if (style._paragraphFormat != null)
            {
                style._paragraphFormat = style._paragraphFormat.Clone();
                style._paragraphFormat._parent = style;
            }
            return style;
        }

        /// <summary>
        /// Returns the value with the specified name and value access.
        /// </summary>
        public override object GetValue(string name, GV flags) //newStL
        {
            if (name == null)
                throw new ArgumentNullException("name");
            if (name == "")
                throw new ArgumentException("name");

            if (name.ToLower().StartsWith("font"))
                return ParagraphFormat.GetValue(name);
            return base.GetValue(name, flags);
        }

        #region Properties
        /// <summary>
        /// Indicates whether the style is read-only. 
        /// </summary>
        public bool IsReadOnly { get; set; }

        /// <summary>
        /// Gets the font of ParagraphFormat. 
        /// Calling style.Font is just a shortcut to style.ParagraphFormat.Font.
        /// </summary>
        [DV]
        public Font Font
        {
            get { return ParagraphFormat.Font; }
            // SetParent will be called inside ParagraphFormat.
            set { ParagraphFormat.Font = value; }
        }

        /// <summary>
        /// Gets the name of the style.
        /// </summary>
        public string Name
        {
            get { return _name.Value; }
        }
        [DV]
        public NString _name = NString.NullValue;

        /// <summary>
        /// Gets the ParagraphFormat. To prevent read-only styles from being modified, a copy of its ParagraphFormat
        /// is returned in this case.
        /// </summary>
        public ParagraphFormat ParagraphFormat
        {
            get
            {
                if (_paragraphFormat == null)
                    _paragraphFormat = new ParagraphFormat(this);
                if (IsReadOnly)
                    return _paragraphFormat.Clone();
                return _paragraphFormat;
            }
            set
            {
                SetParent(value);
                _paragraphFormat = value;
            }
        }
        [DV]
        public ParagraphFormat _paragraphFormat;

        /// <summary>
        /// Gets or sets the name of the base style.
        /// </summary>
        public string BaseStyle
        {
            get { return _baseStyle.Value; }
            set
            {
                if (value == null || value == "" && _baseStyle.Value != "")
                    throw new ArgumentException(DomSR.EmptyBaseStyle);

                // Self assignment is allowed
                if (String.Compare(_baseStyle.Value, value, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    _baseStyle.Value = value;  // character case may change...
                    return;
                }

                if (String.Compare(_name.Value, DefaultParagraphName, StringComparison.OrdinalIgnoreCase) == 0 ||
                    String.Compare(_name.Value, DefaultParagraphFontName, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    string msg = String.Format("Style '{0}' has no base style and that cannot be altered.", _name);
                    throw new ArgumentException(msg);
                }

                Styles styles = (Styles)_parent;
                // The base style must exists
                int idxBaseStyle = styles.GetIndex(value);
                if (idxBaseStyle == -1)
                {
                    string msg = String.Format("Base style '{0}' does not exist.", value);
                    throw new ArgumentException(msg);
                }
                if (idxBaseStyle > 1)
                {
                    // Is this style in the base style chain of the new base style
                    Style style = styles[idxBaseStyle] as Style;
                    while (style != null)
                    {
                        if (style == this)
                        {
                            string msg = String.Format("Base style '{0}' leads to a circular dependency.", value);
                            throw new ArgumentException(msg);
                        }
                        style = styles[style.BaseStyle];
                    }
                }

                // Now setting new base style is safe
                _baseStyle.Value = value;
            }
        }
        [DV]
        public NString _baseStyle = NString.NullValue;

        /// <summary>
        /// Gets the StyleType of the style.
        /// </summary>
        public StyleType Type
        {
            get
            {
                //old
                //if (IsNull("Type"))
                //{
                //  if (String.Compare (this .baseStyle.Value, DefaultParagraphFontName, true) == 0)
                //    SetValue("Type", StyleType.Character);
                //  else
                //  {
                //    Style bsStyle = GetBaseStyle();
                //    if (bsStyle == null)
                //      throw new ArgumentException("User defined style has no valid base Style.");
                //
                //    SetValue("Type", bsStyle.Type);
                //  }
                //}
                //return styleType;

                if (_styleType.IsNull)
                {
                    if (String.Compare(_baseStyle.Value, DefaultParagraphFontName, StringComparison.OrdinalIgnoreCase) == 0)
                        _styleType.Value = (int)StyleType.Character;
                    else
                    {
                        Style baseStyle = GetBaseStyle();
                        if (baseStyle == null)
                            throw new InvalidOperationException("User defined style has no valid base Style.");

                        _styleType.Value = (int)baseStyle.Type;
                    }
                }
                return (StyleType)_styleType.Value;
            }
        }
        [DV(Type = typeof(StyleType))]
        public NEnum _styleType = NEnum.NullValue(typeof(StyleType));

        /// <summary>
        /// Determines whether the style is the style Normal or DefaultParagraphFont.
        /// </summary>
        public bool IsRootStyle
        {
            get
            {
                return String.Compare(Name, DefaultParagraphFontName, StringComparison.OrdinalIgnoreCase) == 0 ||
                       String.Compare(Name, DefaultParagraphName, StringComparison.OrdinalIgnoreCase) == 0;
            }
        }

        /// <summary>
        /// Get the BaseStyle of the current style.
        /// </summary>
        public Style GetBaseStyle()
        {
            if (IsRootStyle)
                return null;

            Styles styles = Parent as Styles;
            if (styles == null)
                throw new InvalidOperationException("A parent object is required for this operation; access failed");
            if (_baseStyle.Value == "")
                throw new ArgumentException("User defined Style defined without a BaseStyle");

            // TODO Remove German remarks!
            //REVIEW KlPo4StLa Spezialbehandlung für den DefaultParagraphFont krüppelig(DefaultParagraphFont wird bei Zugriff über styles["name"] nicht zurückgeliefert).
            if (_baseStyle.Value == DefaultParagraphFontName)
                return styles[0];

            return styles[_baseStyle.Value];
        }

        /// <summary>
        /// Indicates whether the style is a predefined (build in) style.
        /// </summary>
        public bool BuildIn
        {
            get { return _buildIn.Value; }
        }
        [DV]
        public NBool _buildIn = NBool.NullValue;
        // TODO: rename to builtIn.

        /// <summary>
        /// Gets or sets a comment associated with this object.
        /// </summary>
        public string Comment
        {
            get { return _comment.Value; }
            set { _comment.Value = value; }
        }
        [DV]
        public NString _comment = NString.NullValue;
        #endregion

        // Names of the root styles. Root styles have no BaseStyle.

        /// <summary>
        /// Name of the default character style.
        /// </summary>
        public const string DefaultParagraphFontName = StyleNames.DefaultParagraphFont;

        /// <summary>
        /// Name of the default paragraph style.
        /// </summary>
        public const string DefaultParagraphName = StyleNames.Normal;

        #region public
        /// <summary>
        /// Converts Style into DDL.
        /// </summary>
        public override void Serialize(Serializer serializer)
        {
#if DEBUG_ // Test
      if (Name == StyleNames.Heading1 || Name == StyleNames.Heading2)
        Name.GetType();
#endif

            // For build-in styles all properties that differ from their default values
            // are serialized.
            // For user-defined styles all non-null properties are serialized.
            Styles buildInStyles = Styles.BuildInStyles;
            Style refStyle = null;
            Font refFont = null;
            ParagraphFormat refFormat = null;

            serializer.WriteComment(_comment.Value);
            if (_buildIn.Value)
            {
                // BaseStyle is never null, but empty only for "Normal" and "DefaultParagraphFont"
                if (BaseStyle == "")
                {
                    // case: style is "Normal"
                    if (String.Compare(_name.Value, DefaultParagraphName, StringComparison.OrdinalIgnoreCase) != 0)
                        throw new ArgumentException("public Error: BaseStyle not set.");

                    refStyle = buildInStyles[buildInStyles.GetIndex(Name)];
                    refFormat = refStyle.ParagraphFormat;
                    refFont = refFormat.Font;
                    string name = DdlEncoder.QuoteIfNameContainsBlanks(Name);
                    serializer.WriteLineNoCommit(name);
                }
                else
                {
                    // case: any build-in style except "Normal"
                    refStyle = buildInStyles[buildInStyles.GetIndex(Name)];
                    refFormat = refStyle.ParagraphFormat;
                    refFont = refFormat.Font;
                    if (String.Compare(BaseStyle, refStyle.BaseStyle, StringComparison.OrdinalIgnoreCase) == 0)
                    {
                        // case: build-in style with unmodified base style name
                        string name = DdlEncoder.QuoteIfNameContainsBlanks(Name);
                        serializer.WriteLineNoCommit(name);
                        // It's fine if we have the predefined base style, but ...
                        // ... the base style may have been modified or may even have a modified base style.
                        // Methinks it's wrong to compare with the built-in style, so let's compare with the
                        // real base style:
                        refStyle = Document.Styles[Document.Styles.GetIndex(_baseStyle.Value)];
                        refFormat = refStyle.ParagraphFormat;
                        refFont = refFormat.Font;
                        // Note: we must write "Underline = none" if the base style has "Underline = single" - we cannot
                        // detect this if we compare with the built-in style that has no underline.
                        // Known problem: Default values like "OutlineLevel = Level1" will now be serialized
                        // TODO: optimize...
                    }
                    else
                    {
                        // case: build-in style with modified base style name
                        string name = DdlEncoder.QuoteIfNameContainsBlanks(Name);
                        string baseName = DdlEncoder.QuoteIfNameContainsBlanks(BaseStyle);
                        serializer.WriteLine(name + " : " + baseName);
                        refStyle = Document.Styles[Document.Styles.GetIndex(_baseStyle.Value)];
                        refFormat = refStyle.ParagraphFormat;
                        refFont = refFormat.Font;
                    }
                }
            }
            else
            {
                // case: user-defined style; base style always exists

                string name = DdlEncoder.QuoteIfNameContainsBlanks(Name);
                string baseName = DdlEncoder.QuoteIfNameContainsBlanks(BaseStyle);
                serializer.WriteLine(name + " : " + baseName);

#if true
                Style refStyle0 = Document.Styles[Document.Styles.GetIndex(_baseStyle.Value)];
                refStyle = Document.Styles[_baseStyle.Value];
                refFormat = refStyle != null ? refStyle.ParagraphFormat : null;
#else
        refFormat = null;
#endif
            }

            serializer.BeginContent();

            if (!IsNull("ParagraphFormat"))
            {
                if (!ParagraphFormat.IsNull("Font"))
                    Font.Serialize(serializer, refFormat != null ? refFormat.Font : null);

                if (Type == StyleType.Paragraph)
                    ParagraphFormat.Serialize(serializer, "ParagraphFormat", refFormat);
            }

            serializer.EndContent();
        }

        /// <summary>
        /// Sets all properties to Null that have the same value as the base style.
        /// </summary>
        private void Optimize()
        {
            // just here as a reminder to do it...
        }

        /// <summary>
        /// Allows the visitor object to visit the document object and its child objects.
        /// </summary>
        void IVisitable.AcceptVisitor(DocumentObjectVisitor visitor, bool visitChildren)
        {
            visitor.VisitStyle(this);
        }

        /// <summary>
        /// Returns the meta object of this instance.
        /// </summary>
        public override Meta Meta
        {
            get { return _meta ?? (_meta = new Meta(typeof(Style))); }
        }
        static Meta _meta;
        #endregion
    }
}
