using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using C1.C1Zip;

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

        public static XNamespace SheetNS = XNamespace.Get("http://schemas.openxmlformats.org/spreadsheetml/2006/main");
        public static XNamespace BookRelNS = XNamespace.Get("http://schemas.openxmlformats.org/officeDocument/2006/relationships");
        public static XNamespace RelationsNS = XNamespace.Get("http://schemas.openxmlformats.org/package/2006/relationships");

        Stream _stream;
        string _sheetFileName;

        #endregion

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

        /// <summary>
        /// Creates and initializes a new <see cref="XLWorkbook"/>.
        /// </summary>
        /// <param name="stream">The stream containing the workbook data.</param>
        /// <param name="sheetName">The name of the sheet to load.</param>
        public XLWorkbook(Stream stream, string sheetName)
        {
            // get zip file from the stream
            _stream = stream;
            var zip = new C1ZipFile(stream);

            // load shared strings documents
            SharedStrings = new XLSharedStrings(LoadDocument(zip, XLConstants.SHARED_STRINGS_FILE_NAME));

            // load styles
            StyleSheet = new XLStyles(LoadDocument(zip, XLConstants.STYLES_FILE_NAME));

            // load spreadsheet
            _sheetFileName = GetSheetFileName(zip, sheetName);
            Sheet = new XLSheet(LoadDocument(zip, _sheetFileName));
        }

        #endregion

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

        /// <summary>
        /// Gets the spread sheet associated with this workbook.
        /// </summary>
        public XLSheet Sheet { get; private set; }
        /// <summary>
        /// Gets the style sheet associated with this workbook.
        /// </summary>
        public XLStyles StyleSheet { get; private set; }
        /// <summary>
        /// Gets the shared strings sheet associated with this workbook.
        /// </summary>
        public XLSharedStrings SharedStrings { get; private set; }
        /// <summary>
        /// Save this workbook to a stream.
        /// </summary>
        /// <param name="stream">Stream where the workbook is saved.</param>
        public void Save(Stream stream)
        {
            using (var sw = new StreamWriter(stream))
            {
                // copy the template stream to output stream
                _stream.Flush();
                _stream.Position = 0;
                _stream.CopyTo(stream);
                _stream.Close();

                // create stream with updated sheet
                var updatedSheetStream = new MemoryStream();
                Sheet.Document.Save(updatedSheetStream);
                updatedSheetStream.Flush();
                updatedSheetStream.Position = 0;

                // create stream with updated StyleSheet
                var updatedStyleSheetStream = new MemoryStream();
                StyleSheet.Document.Save(updatedStyleSheetStream);
                updatedStyleSheetStream.Flush();
                updatedStyleSheetStream.Position = 0;

                // find entries to replace
                var zip = new C1ZipFile(stream);
                C1ZipEntry sheetEntry = null;
                C1ZipEntry styleSheetEntry = null;
                foreach (C1ZipEntry entry in zip.Entries)
                {
                    if (entry.FileName.EndsWith(_sheetFileName))
                    {
                        sheetEntry = entry;
                    }
                    else if (entry.FileName.EndsWith(XLConstants.STYLES_FILE_NAME))
                    {
                        styleSheetEntry = entry;
                    }
                }
                sheetEntry.Remove();
                zip.Entries.Add(updatedSheetStream, sheetEntry.FileName);
                styleSheetEntry.Remove();
                zip.Entries.Add(updatedStyleSheetStream, styleSheetEntry.FileName);
            }
        }

        #endregion

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

        static XDocument LoadDocument(C1ZipFile zipFile, string fileName)
        {
            XDocument loadedDoc = null;
            foreach (C1ZipEntry entry in zipFile.Entries)
            {
                if (entry.FileName.EndsWith(fileName))
                {
                    using (var docStream = entry.OpenReader())
                    {
                        if (docStream != null)
                        {
                            loadedDoc = XDocument.Load(docStream);
                        }
                    }
                    break;
                }
            }
            return loadedDoc;
        }

        static string GetSheetFileName(C1ZipFile zipFile, string sheetName)
        {
            // search sheet id from the sheet name
            XDocument workbook = LoadDocument(zipFile, XLConstants.WORKBOOK_FILE_NAME);
            var sheets = workbook.Descendants(SheetNS + XLConstants.SHEET);
            
            string relationId = string.Empty;
            foreach (var item in sheets)
            {
                if ((item.Attribute(XLConstants.NAME) != null) && item.Attribute(XLConstants.NAME).Value.Equals(sheetName))
                {
                    XAttribute att = item.Attributes(BookRelNS + XLConstants.ID).FirstOrDefault();
                    if (att != null)
                    {
                        relationId = att.Value;
                        break;
                    }
                }
            }

            // search sheet file name from sheet id
            XDocument workbookRels = LoadDocument(zipFile, XLConstants.WORKBOOK_RELATIONS_FILE_NAME);
            var rels = workbookRels.Descendants(RelationsNS + XLConstants.RELATIONSHIP);
            string fileName = string.Empty;
            foreach (var item in rels)
            {
                if ((item.Attribute(XLConstants.UPPER_ID) != null) && item.Attribute(XLConstants.UPPER_ID).Value.Equals(relationId))
                {
                    if (item.Attribute(XLConstants.TARGET) != null)
                    {
                        return item.Attribute(XLConstants.TARGET).Value;
                    }
                }
            }
            throw new Exception(string.Format("Sheet: {0} not found", sheetName));
        }

        #endregion
    }
}
