/*******************************************************************************
 * You may amend and distribute as you like, but don't remove this header!
 *
 * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
 * See http://www.codeplex.com/EPPlus for details.
 *
 * Copyright (C) 2011  Jan Källman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU Lesser General Public License for more details.
 *
 * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
 * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
 *
 * All code and executables are provided "as is" with no warranty either express or implied. 
 * The author accepts no liability for any damage or loss of business that this product may cause.
 *
 * Code change notes:
 * 
 * Author							Change						Date
 * ******************************************************************************
 * Jan Källman		                Initial Release		        2009-12-22
 * Jan Källman		License changed GPL-->LGPL 2011-12-16
 *******************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Collections;
using System.IO;
using System.Drawing;
using System.Linq;
using OfficeOpenXml.Drawing.Chart;
using OfficeOpenXml.Table.PivotTable;
using OfficeOpenXml.Utils;
namespace OfficeOpenXml.Drawing
{
    /// <summary>
    /// Collection for Drawing objects.
    /// </summary>
    public class ExcelDrawings : IEnumerable<ExcelDrawing>, IDisposable
    {
        private XmlDocument _drawingsXml=new XmlDocument();
        private Dictionary<string, int> _drawingNames;
        private List<ExcelDrawing> _drawings;
        internal class ImageCompare
        {
            internal byte[] image { get; set; }
            internal string relID { get; set; }

            internal bool Comparer(byte[] compareImg)
            {
                if (compareImg.Length != image.Length)
                {
                    return false;
                }

                for (int i = 0; i < image.Length; i++)
                {
                    if (image[i] != compareImg[i])
                    {
                        return false;
                    }
                }
                return true; //Equal
            }
        }
        //internal List<ImageCompare> _pics = new List<ImageCompare>();
        internal Dictionary<string, string> _hashes = new Dictionary<string, string>();
        internal ExcelPackage _package;
        internal Packaging.ZipPackageRelationship _drawingRelation = null;
        internal ExcelDrawings(ExcelPackage xlPackage, ExcelWorksheet sheet)
        {
                _drawingsXml = new XmlDocument();                
                _drawingsXml.PreserveWhitespace = false;
                _drawings = new List<ExcelDrawing>();
                _drawingNames = new Dictionary<string,int>(StringComparer.InvariantCultureIgnoreCase);
                _package = xlPackage;
                Worksheet = sheet;
                XmlNode node = sheet.WorksheetXml.SelectSingleNode("//d:drawing", sheet.NameSpaceManager);
                CreateNSM();
                if (node != null)
                {
                    _drawingRelation = sheet.Part.GetRelationship(node.Attributes["r:id"].Value);
                    _uriDrawing = UriHelper.ResolvePartUri(sheet.WorksheetUri, _drawingRelation.TargetUri);

                    _part = xlPackage.Package.GetPart(_uriDrawing);
                    XmlHelper.LoadXmlSafe(_drawingsXml, _part.GetStream()); 

                    AddDrawings();
                }
         }
        internal ExcelWorksheet Worksheet { get; set; }
        /// <summary>
        /// A reference to the drawing xml document
        /// </summary>
        public XmlDocument DrawingXml
        {
            get
            {
                return _drawingsXml;
            }
        }
        private void AddDrawings()
        {
            // Look inside all children for the drawings because they could be inside
            // Markup Compatibility AlternativeContent/Choice or AlternativeContent/Fallback nodes.
            // The code below currently pretends that loading all Choice alternative drawings doesn't cause a problem
            // elsewhere. This seems to be ok for the time being as encountered drawing files so far only seem to have
            // one Choice node (and no Fallback) underneath the AlternativeContent node. (Excel 2013 that is.)
            // This change prevents CodePlex issue #15028 from occurring. 
            // (the drawing xml part (that ONLY contained AlternativeContent nodes) was incorrectly being garbage collected when the package was saved)
            XmlNodeList list = _drawingsXml.SelectNodes("//*[self::xdr:twoCellAnchor or self::xdr:oneCellAnchor or self::xdr:absoluteAnchor]", NameSpaceManager);

            foreach (XmlNode node in list)
            {
                
                ExcelDrawing dr;
                switch(node.LocalName)
                {
                    case "oneCellAnchor":
                        //dr = new ExcelDrawing(this, node, "xdr:sp/xdr:nvSpPr/xdr:cNvPr/@name");                        
                        dr = ExcelDrawing.GetDrawing(this, node); //Issue 15373
                        break;
                    case "twoCellAnchor":
                        dr = ExcelDrawing.GetDrawing(this, node);
                        break;
                    case "absoluteAnchor":
                        dr = ExcelDrawing.GetDrawing(this, node);
                        break;
                    default: //"absoluteCellAnchor":
                        dr = null;
                        break;
                }
                if (dr != null)
                {
                    _drawings.Add(dr);
                    if (!_drawingNames.ContainsKey(dr.Name))
                    {
                        _drawingNames.Add(dr.Name, _drawings.Count - 1);
                    }
                }
            }
        }


        #region NamespaceManager
        /// <summary>
        /// Creates the NamespaceManager. 
        /// </summary>
        private void CreateNSM()
        {
            NameTable nt = new NameTable();
            _nsManager = new XmlNamespaceManager(nt);
            _nsManager.AddNamespace("a", ExcelPackage.schemaDrawings);
            _nsManager.AddNamespace("xdr", ExcelPackage.schemaSheetDrawings);
            _nsManager.AddNamespace("c", ExcelPackage.schemaChart);
            _nsManager.AddNamespace("r", ExcelPackage.schemaRelationships);
        }
        /// <summary>
        /// Provides access to a namespace manager instance to allow XPath searching
        /// </summary>
        XmlNamespaceManager _nsManager=null;
        public XmlNamespaceManager NameSpaceManager
        {
            get
            {
                return _nsManager;
            }
        }
        #endregion
        #region IEnumerable Members

        public IEnumerator GetEnumerator()
        {
            return (_drawings.GetEnumerator());
        }
        #region IEnumerable<ExcelDrawing> Members

        IEnumerator<ExcelDrawing> IEnumerable<ExcelDrawing>.GetEnumerator()
        {
            return (_drawings.GetEnumerator());
        }

        #endregion

        /// <summary>
        /// Returns the drawing at the specified position.  
        /// </summary>
        /// <param name="PositionID">The position of the drawing. 0-base</param>
        /// <returns></returns>
        public ExcelDrawing this[int PositionID]
        {
            get
            {
                return (_drawings[PositionID]);
            }
        }

        /// <summary>
        /// Returns the drawing matching the specified name
        /// </summary>
        /// <param name="Name">The name of the worksheet</param>
        /// <returns></returns>
        public ExcelDrawing this[string Name]
        {
            get
            {
                if (_drawingNames.ContainsKey(Name))
                {
                    return _drawings[_drawingNames[Name]];
                }
                else
                {
                    return null;
                }
            }
        }
        public int Count
        {
            get
            {
                if (_drawings == null)
                {
                    return 0;
                }
                else
                {
                    return _drawings.Count;
                }
            }
        }
        Packaging.ZipPackagePart _part=null;
        internal Packaging.ZipPackagePart Part
        {
            get
            {
                return _part;
            }        
        }
        Uri _uriDrawing=null;
        public Uri UriDrawing
        {
            get
            {
                return _uriDrawing;
            }
        }
        #endregion
        #region Add functions
            /// <summary>
            /// Add a new chart to the worksheet.
            /// Do not support Bubble-, Radar-, Stock- or Surface charts. 
            /// </summary>
            /// <param name="Name"></param>
            /// <param name="ChartType">Type of chart</param>
            /// <param name="PivotTableSource">The pivottable source for a pivotchart</param>    
            /// <returns>The chart</returns>
            public ExcelChart AddChart(string Name, eChartType ChartType, ExcelPivotTable PivotTableSource)
            {
                if(_drawingNames.ContainsKey(Name))
                {
                    throw new Exception("Name already exists in the drawings collection");
                }

                if (ChartType == eChartType.StockHLC ||
                    ChartType == eChartType.StockOHLC ||
                    ChartType == eChartType.StockVOHLC)
                {
                    throw(new NotImplementedException("Chart type is not supported in the current version"));
                }
                if (Worksheet is ExcelChartsheet && _drawings.Count > 0)
                {
                    throw new InvalidOperationException("Chart Worksheets can't have more than one chart");                
                }
                XmlElement drawNode = CreateDrawingXml();

                ExcelChart chart = ExcelChart.GetNewChart(this, drawNode, ChartType, null, PivotTableSource);
                chart.Name = Name;
                _drawings.Add(chart);
                _drawingNames.Add(Name, _drawings.Count - 1);
                return chart;
            }
            /// <summary>
            /// Add a new chart to the worksheet.
            /// Do not support Bubble-, Radar-, Stock- or Surface charts. 
            /// </summary>
            /// <param name="Name"></param>
            /// <param name="ChartType">Type of chart</param>
            /// <returns>The chart</returns>
            public ExcelChart AddChart(string Name, eChartType ChartType)
            {
                return AddChart(Name, ChartType, null);
            }
            /// <summary>
            /// Add a picure to the worksheet
            /// </summary>
            /// <param name="Name"></param>
            /// <param name="image">An image. Allways saved in then JPeg format</param>
            /// <returns></returns>
            public ExcelPicture AddPicture(string Name, Image image)
            {
               return AddPicture(Name, image, null);
            }
            /// <summary>
            /// Add a picure to the worksheet
            /// </summary>
            /// <param name="Name"></param>
            /// <param name="image">An image. Allways saved in then JPeg format</param>
            /// <param name="Hyperlink">Picture Hyperlink</param>
            /// <returns></returns>
            public ExcelPicture AddPicture(string Name, Image image, Uri Hyperlink)
            {
                if (image != null)
                {
                    if (_drawingNames.ContainsKey(Name))
                    {
                        throw new Exception("Name already exists in the drawings collection");
                    }
                    XmlElement drawNode = CreateDrawingXml();
                    drawNode.SetAttribute("editAs", "oneCell");
                    ExcelPicture pic = new ExcelPicture(this, drawNode, image, Hyperlink);
                    pic.Name = Name;
                    _drawings.Add(pic);
                    _drawingNames.Add(Name, _drawings.Count - 1);
                    return pic;
                }
                throw (new Exception("AddPicture: Image can't be null"));
            }
            /// <summary>
            /// Add a picure to the worksheet
            /// </summary>
            /// <param name="Name"></param>
            /// <param name="ImageFile">The image file</param>
            /// <returns></returns>
            public ExcelPicture AddPicture(string Name, FileInfo ImageFile)
            {
               return AddPicture(Name, ImageFile, null);
            }
            /// <summary>
            /// Add a picure to the worksheet
            /// </summary>
            /// <param name="Name"></param>
            /// <param name="ImageFile">The image file</param>
            /// <param name="Hyperlink">Picture Hyperlink</param>
            /// <returns></returns>
            public ExcelPicture AddPicture(string Name, FileInfo ImageFile, Uri Hyperlink)
            {
                if (Worksheet is ExcelChartsheet && _drawings.Count > 0)
                {
                    throw new InvalidOperationException("Chart worksheets can't have more than one drawing");
                }
                if (ImageFile != null)
               {
                  if (_drawingNames.ContainsKey(Name))
                  {
                     throw new Exception("Name already exists in the drawings collection");
                  }
                  XmlElement drawNode = CreateDrawingXml();
                  drawNode.SetAttribute("editAs", "oneCell");
                  ExcelPicture pic = new ExcelPicture(this, drawNode, ImageFile, Hyperlink);
                  pic.Name = Name;
                  _drawings.Add(pic);
                  _drawingNames.Add(Name, _drawings.Count - 1);
                  return pic;
               }
               throw (new Exception("AddPicture: ImageFile can't be null"));
            }

        /// <summary>
        /// Add a new shape to the worksheet
        /// </summary>
        /// <param name="Name">Name</param>
        /// <param name="Style">Shape style</param>
        /// <returns>The shape object</returns>
    
        public ExcelShape AddShape(string Name, eShapeStyle Style)
            {
                if (Worksheet is ExcelChartsheet && _drawings.Count > 0)
                {
                    throw new InvalidOperationException("Chart worksheets can't have more than one drawing");
                }
                if (_drawingNames.ContainsKey(Name))
                {
                    throw new Exception("Name already exists in the drawings collection");
                }
                XmlElement drawNode = CreateDrawingXml();

                ExcelShape shape = new ExcelShape(this, drawNode, Style);
                shape.Name = Name;
                shape.Style = Style;
                _drawings.Add(shape);
                _drawingNames.Add(Name, _drawings.Count - 1);
                return shape;
            }
        /// <summary>
        /// Add a new shape to the worksheet
        /// </summary>
        /// <param name="Name">Name</param>
        /// <param name="Source">Source shape</param>
        /// <returns>The shape object</returns>
        public ExcelShape AddShape(string Name, ExcelShape Source)
        {
            if (Worksheet is ExcelChartsheet && _drawings.Count > 0)
            {
                throw new InvalidOperationException("Chart worksheets can't have more than one drawing");
            }
            if (_drawingNames.ContainsKey(Name))
            {
                throw new Exception("Name already exists in the drawings collection");
            }
            XmlElement drawNode = CreateDrawingXml();
            drawNode.InnerXml = Source.TopNode.InnerXml;

            ExcelShape shape = new ExcelShape(this, drawNode);
            shape.Name = Name;
            shape.Style = Source.Style;
            _drawings.Add(shape);
            _drawingNames.Add(Name, _drawings.Count - 1);
            return shape;
        }
            private XmlElement CreateDrawingXml()
            {
                if (DrawingXml.OuterXml == "")
                {
                    DrawingXml.LoadXml(string.Format("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><xdr:wsDr xmlns:xdr=\"{0}\" xmlns:a=\"{1}\" />", ExcelPackage.schemaSheetDrawings, ExcelPackage.schemaDrawings));
                    _uriDrawing = new Uri(string.Format("/xl/drawings/drawing{0}.xml", Worksheet.SheetID),UriKind.Relative);

                    Packaging.ZipPackage package = Worksheet._package.Package;
                    _part = package.CreatePart(_uriDrawing, "application/vnd.openxmlformats-officedocument.drawing+xml", _package.Compression);

                    StreamWriter streamChart = new StreamWriter(_part.GetStream(FileMode.Create, FileAccess.Write));
                    DrawingXml.Save(streamChart);
                    streamChart.Close();
                    package.Flush();

                    _drawingRelation = Worksheet.Part.CreateRelationship(UriHelper.GetRelativeUri(Worksheet.WorksheetUri, _uriDrawing), Packaging.TargetMode.Internal, ExcelPackage.schemaRelationships + "/drawing");
                    XmlElement e = Worksheet.WorksheetXml.CreateElement("drawing", ExcelPackage.schemaMain);
                    e.SetAttribute("id",ExcelPackage.schemaRelationships, _drawingRelation.Id);

                    Worksheet.WorksheetXml.DocumentElement.AppendChild(e);
                    package.Flush();                    
                }
                XmlNode colNode = _drawingsXml.SelectSingleNode("//xdr:wsDr", NameSpaceManager);
                XmlElement drawNode;
                if (this.Worksheet is ExcelChartsheet)
                {
                    drawNode = _drawingsXml.CreateElement("xdr", "absoluteAnchor", ExcelPackage.schemaSheetDrawings);
                    XmlElement posNode = _drawingsXml.CreateElement("xdr", "pos", ExcelPackage.schemaSheetDrawings);
                    posNode.SetAttribute("y", "0");
                    posNode.SetAttribute("x", "0");
                    drawNode.AppendChild(posNode);
                    XmlElement extNode = _drawingsXml.CreateElement("xdr", "ext", ExcelPackage.schemaSheetDrawings);
                    extNode.SetAttribute("cy", "6072876");
                    extNode.SetAttribute("cx", "9299263");
                    drawNode.AppendChild(extNode);
                    colNode.AppendChild(drawNode);
                }
                else
                {
                    drawNode = _drawingsXml.CreateElement("xdr", "twoCellAnchor", ExcelPackage.schemaSheetDrawings);
                    colNode.AppendChild(drawNode);
                    //Add from position Element;
                    XmlElement fromNode = _drawingsXml.CreateElement("xdr", "from", ExcelPackage.schemaSheetDrawings);
                    drawNode.AppendChild(fromNode);
                    fromNode.InnerXml = "<xdr:col>0</xdr:col><xdr:colOff>0</xdr:colOff><xdr:row>0</xdr:row><xdr:rowOff>0</xdr:rowOff>";

                    //Add to position Element;
                    XmlElement toNode = _drawingsXml.CreateElement("xdr", "to", ExcelPackage.schemaSheetDrawings);
                    drawNode.AppendChild(toNode);
                    toNode.InnerXml = "<xdr:col>10</xdr:col><xdr:colOff>0</xdr:colOff><xdr:row>10</xdr:row><xdr:rowOff>0</xdr:rowOff>";
                }

                return drawNode;
            }
        #endregion
        #region Remove methods
            /// <summary>
            /// Removes a drawing.
            /// </summary>
            /// <param name="Index">The index of the drawing</param>
        public void Remove(int Index)
        {
            if (Worksheet is ExcelChartsheet && _drawings.Count > 0)
            {
                throw new InvalidOperationException("Can' remove charts from chart worksheets");
            }
            RemoveDrawing(Index);
        }

        internal void RemoveDrawing(int Index)
        {
            var draw = _drawings[Index];
            draw.DeleteMe();
            for (int i = Index + 1; i < _drawings.Count; i++)
            {
                _drawingNames[_drawings[i].Name]--;
            }
            _drawingNames.Remove(draw.Name);
            _drawings.Remove(draw);
        }
        /// <summary>
        /// Removes a drawing.
        /// </summary>
        /// <param name="Drawing">The drawing</param>
        public void Remove(ExcelDrawing Drawing)
        {
            Remove(_drawingNames[Drawing.Name]);
        }
        /// <summary>
        /// Removes a drawing.
        /// </summary>
        /// <param name="Name">The name of the drawing</param>
        public void Remove(string Name)
        {
            Remove(_drawingNames[Name]);
        }
        /// <summary>
        /// Removes all drawings from the collection
        /// </summary>
        public void Clear()
        {
            if (Worksheet is ExcelChartsheet && _drawings.Count > 0)
            {
                throw new InvalidOperationException("Can' remove charts from chart worksheets");
            }
            ClearDrawings();
        }

        internal void ClearDrawings()
        {
            while (Count > 0)
            {
                RemoveDrawing(0);
            }
        }
        #endregion
            internal void AdjustWidth(int[,] pos)
            {
                var ix = 0;
                //Now set the size for all drawings depending on the editAs property.
                foreach (OfficeOpenXml.Drawing.ExcelDrawing d in this)
                {
                    if (d.EditAs != Drawing.eEditAs.TwoCell)
                    {
                        if (d.EditAs == Drawing.eEditAs.Absolute)
                        {
                            d.SetPixelLeft(pos[ix, 0]);
                        }
                        d.SetPixelWidth(pos[ix, 1]);
                        
                    }
                    ix++;
                }
            }
            internal void AdjustHeight(int[,] pos)
            {
                var ix = 0;
                //Now set the size for all drawings depending on the editAs property.
                foreach (OfficeOpenXml.Drawing.ExcelDrawing d in this)
                {
                    if (d.EditAs != Drawing.eEditAs.TwoCell)
                    {
                        if (d.EditAs == Drawing.eEditAs.Absolute)
                        {
                            d.SetPixelTop(pos[ix, 0]);
                        }
                        d.SetPixelHeight(pos[ix, 1]);

                    }
                    ix++;
                }
            }
            internal int[,] GetDrawingWidths()
            {
                int[,] pos = new int[Count, 2];
                int ix = 0;
                //Save the size for all drawings
                foreach (ExcelDrawing d in this)
                {
                    pos[ix, 0] = d.GetPixelLeft();
                    pos[ix++, 1] = d.GetPixelWidth();
                }
                return pos;
            }
            internal int[,] GetDrawingHeight()
            {
                int[,] pos = new int[Count, 2];
                int ix = 0;
                //Save the size for all drawings
                foreach (ExcelDrawing d in this)
                {
                    pos[ix, 0] = d.GetPixelTop();
                    pos[ix++, 1] = d.GetPixelHeight();
                }
                return pos;
            }

            public void Dispose()
            {
                _drawingsXml = null;
                _hashes.Clear();
                _hashes = null;
                _part = null;
                _drawingNames.Clear();
                _drawingNames = null;
                _drawingRelation = null;
                foreach (var d in _drawings)
                {
                    d.Dispose();
                }
                _drawings.Clear();
                _drawings = null;
            }
    }
}
