using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using System.Collections;

namespace C1.Silverlight.FlexGridExcel
{
    /// <summary>
    /// Represents a MS Excel style sheet.
    /// </summary>
    public class XLStyles
    {
        //-------------------------------------------------------------------------
        #region ** fields

        Dictionary<int, XLNumberFormat> _numberFormats;
        Dictionary<int, XLCellFormat> _cellFormats;
        Dictionary<int, XLFont> _fonts;
        int _currentNumberFormat = 200;

        #endregion

        //-------------------------------------------------------------------------
        #region ** ctor

        /// <summary>
        /// Initializes a new instance of an <see cref="XLStyles"/>.
        /// </summary>
        /// <param name="document">Document that contains the style sheet definition.</param>
        public XLStyles(XDocument document)
        {
            Document = document;
            // read number formats
            _numberFormats = new Dictionary<int, XLNumberFormat>();
            AddImplicitNumFmts();
            foreach (var element in document.Descendants(XLWorkbook.SheetNS + XLConstants.NUMBER_FORMAT))
            {
                XLNumberFormat numFmt = new XLNumberFormat(element);
                _numberFormats[numFmt.Id.Value] = numFmt;
            }
            // read fonts
            _fonts = new Dictionary<int, XLFont>();
            int index = 0;
            foreach (var element in document.Descendants(XLWorkbook.SheetNS + XLConstants.FONT))
            {
                _fonts[index++] = new XLFont(element);
            }
            // read cell formats
            _cellFormats = new Dictionary<int, XLCellFormat>();
            index = 0;
            XElement cellFormats = document.Descendants(XLWorkbook.SheetNS + XLConstants.CELL_FORMATS).FirstOrDefault();
            if (cellFormats != null)
            {
                foreach (var element in cellFormats.Descendants(XLWorkbook.SheetNS + XLConstants.CELL_FORMAT))
                {
                    _cellFormats[index++] = new XLCellFormat(element);
                }
            }
        }

        #endregion

        //-------------------------------------------------------------------------
        #region ** object model

        /// <summary>
        /// Gets the document that contains the style sheet definition.
        /// </summary>
        public XDocument Document { get; private set; }
        /// <summary>
        /// Gets the cell format associated with the specified key in the style sheet.
        /// </summary>
        /// <param name="key">The key of the value to get.</param>
        /// <param name="value">When this method returns, contains the value associated with the specified
        ///  key, if the key is found; otherwise, the default value for the type of the
        ///  value parameter. This parameter is passed uninitialized.</param>
        /// <returns>true if the table contains a string with the specified key; otherwise, false.</returns>
        public bool TryGetValue(int key, out XLCellFormat value)
        {
            return TryGetValue<XLCellFormat>(out value, _cellFormats, key);
        }
        /// <summary>
        /// Gets the cell number format associated with the specified key in the style sheet.
        /// </summary>
        /// <param name="key">The key of the value to get.</param>
        /// <param name="value">When this method returns, contains the value associated with the specified
        ///  key, if the key is found; otherwise, the default value for the type of the
        ///  value parameter. This parameter is passed uninitialized.</param>
        /// <returns>true if the table contains a string with the specified key; otherwise, false.</returns>
        public bool TryGetValue(int key, out XLNumberFormat value)
        {
            return TryGetValue<XLNumberFormat>(out value, _numberFormats, key);
        }
        /// <summary>
        /// Gets the font associated with the specified key in the style sheet.
        /// </summary>
        /// <param name="key">The key of the value to get.</param>
        /// <param name="value">When this method returns, contains the value associated with the specified
        ///  key, if the key is found; otherwise, the default value for the type of the
        ///  value parameter. This parameter is passed uninitialized.</param>
        /// <returns>true if the table contains a string with the specified key; otherwise, false.</returns>
        public bool TryGetValue(int key, out XLFont value)
        {
            return TryGetValue<XLFont>(out value, _fonts, key);
        }
        /// <summary>
        /// Adds the cell format value to the style sheet.
        /// </summary>
        /// <param name="value">The value to add.</param>
        /// <returns>The key of the added value.</returns>
        public int AddValue(XLCellFormat value)
        {
            return AddValue<XLCellFormat>(value, _cellFormats);
        }
        /// <summary>
        /// Adds the number format value to the style sheet.
        /// </summary>
        /// <param name="value">The value to add.</param>
        /// <returns>The key of the added value.</returns>
        public int AddValue(XLNumberFormat value)
        {
            return AddValue<XLNumberFormat>(value, _numberFormats);
        }
        /// <summary>
        /// Adds the font value to the style sheet.
        /// </summary>
        /// <param name="value">The value to add.</param>
        /// <returns>The key of the added value.</returns>
        public int AddValue(XLFont value)
        {
            return AddValue<XLFont>(value, _fonts);
        }

        #endregion

        //-------------------------------------------------------------------------
        #region ** implementation

        bool TryGetValue<T>(out T value, Dictionary<int, T> dictionary, int key)
        {
            return dictionary.TryGetValue(key, out value);
        }

        int AddValue<T>(T value, Dictionary<int, T> dictionary)
        {
            if (dictionary.ContainsValue(value))
            {
                foreach (var item in dictionary)
                {
                    if (item.Value.Equals(value))
                    {
                        return item.Key;
                    }
                }
            }
            else
            {
                int key = dictionary.Count;
                XElement parentElement = null;
                XElement childElement = null;

                // Add element to the dictionary and then to the xml document cancellable 
                //
                if (typeof(T) == typeof(XLNumberFormat))
                {
                    XLNumberFormat numFmt = value as XLNumberFormat;
                    // The number formats use a different key
                    if (!numFmt.Id.HasValue)
                    {
                        numFmt.Id = ++_currentNumberFormat;
                    }
                    key = numFmt.Id.Value;
                    parentElement = Document.Descendants(XLWorkbook.SheetNS + XLConstants.NUMBER_FORMATS).First();
                    childElement = numFmt.Element;
                }
                else if (typeof(T) == typeof(XLFont))
                {
                    XLFont font = value as XLFont;
                    parentElement = Document.Descendants(XLWorkbook.SheetNS + XLConstants.FONTS).First();
                    childElement = font.Element;
                }
                else if (typeof(T) == typeof(XLCellFormat))
                {
                    XLCellFormat cellFormat = value as XLCellFormat;
                    parentElement = Document.Descendants(XLWorkbook.SheetNS + XLConstants.CELL_FORMATS).First();
                    childElement = cellFormat.Element;
                }
                dictionary[key] = value;
                parentElement.Add(childElement);
                parentElement.SetAttributeValue(XLConstants.COUNT, dictionary.Count);
                return key;
            }
            return -1;
        }

        void AddImplicitNumFmts()
        {
            // add the implicit number formats to the dictionary
            _numberFormats.Add(1,  new XLNumberFormat() { Id = 1,  FormatCode = "0" });
            _numberFormats.Add(2,  new XLNumberFormat() { Id = 2,  FormatCode = "0.00" });
            _numberFormats.Add(3,  new XLNumberFormat() { Id = 3,  FormatCode = "#,##0" });
            _numberFormats.Add(4,  new XLNumberFormat() { Id = 4,  FormatCode = "#,##0.00" });
            _numberFormats.Add(9,  new XLNumberFormat() { Id = 9,  FormatCode = "0%" });
            _numberFormats.Add(10, new XLNumberFormat() { Id = 10, FormatCode = "0.00%" });
            _numberFormats.Add(11, new XLNumberFormat() { Id = 11, FormatCode = "0.00E+00" });
            _numberFormats.Add(12, new XLNumberFormat() { Id = 12, FormatCode = "# ?/?" });
            _numberFormats.Add(13, new XLNumberFormat() { Id = 13, FormatCode = "# ??/??" });
            _numberFormats.Add(14, new XLNumberFormat() { Id = 14, FormatCode = "mm-dd-yy" });
            _numberFormats.Add(15, new XLNumberFormat() { Id = 15, FormatCode = "d-mmm-yy" });
            _numberFormats.Add(16, new XLNumberFormat() { Id = 16, FormatCode = "d-mmm" });
            _numberFormats.Add(17, new XLNumberFormat() { Id = 17, FormatCode = "mmm-yy" });
            _numberFormats.Add(18, new XLNumberFormat() { Id = 18, FormatCode = "h:mm AM/PM" });
            _numberFormats.Add(19, new XLNumberFormat() { Id = 19, FormatCode = "h:mm:ss AM/PM" });
            _numberFormats.Add(20, new XLNumberFormat() { Id = 20, FormatCode = "h:mm" });
            _numberFormats.Add(21, new XLNumberFormat() { Id = 21, FormatCode = "h:mm:ss" });
            _numberFormats.Add(22, new XLNumberFormat() { Id = 22, FormatCode = "m/d/yy h:mm" });
            _numberFormats.Add(37, new XLNumberFormat() { Id = 37, FormatCode = "#,##0 ;(#,##0)" });
            _numberFormats.Add(38, new XLNumberFormat() { Id = 38, FormatCode = "#,##0 ;[Red](#,##0)" });
            _numberFormats.Add(39, new XLNumberFormat() { Id = 39, FormatCode = "#,##0.00;(#,##0.00)" });
            _numberFormats.Add(40, new XLNumberFormat() { Id = 40, FormatCode = "#,##0.00;[Red](#,##0.00)" });
            _numberFormats.Add(45, new XLNumberFormat() { Id = 45, FormatCode = "mm:ss" });
            _numberFormats.Add(46, new XLNumberFormat() { Id = 46, FormatCode = "[h]:mm:ss" });
            _numberFormats.Add(47, new XLNumberFormat() { Id = 47, FormatCode = "mmss.0" });
            _numberFormats.Add(48, new XLNumberFormat() { Id = 48, FormatCode = "##0.0E+0" });
            _numberFormats.Add(49, new XLNumberFormat() { Id = 49, FormatCode = "@" });
        }

        #endregion
    }
}
