using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;

namespace PDFAnnotationList
{
    /// <summary>
    /// XMLプレッドシートとして書き込みます。
    /// </summary>
    class XmlSpreadsheetWriter
    {
        /// <summary>
        /// 注釈をXMLプレッドシートに書き込みます。
        /// </summary>
        /// <param name="filePath">書き込むファイルのパス。</param>
        /// <param name="annots">注釈。</param>
        public void Write(string filePath, IEnumerable<Annotation> annots)
        {
            StringBuilder xmlText = new StringBuilder();
            xmlText.Append(GetHeader());
            xmlText.Append(GetAnnotRows(annots));
            xmlText.Append(GetFooter());
            File.WriteAllText(filePath, xmlText.ToString(), Encoding.UTF8);
        }

        /// <summary>
        /// XMLスプレッドシートのヘッダーを取得します。
        /// </summary>
        /// <returns>XMLスプレッドシートのヘッダー。</returns>
        private string GetHeader()
        {
            return
@"<?xml version=""1.0""?>
<?mso-application progid=""Excel.Sheet""?>
<Workbook xmlns=""urn:schemas-microsoft-com:office:spreadsheet""
xmlns:x=""urn:schemas-microsoft-com:office:excel""
xmlns:ss=""urn:schemas-microsoft-com:office:spreadsheet"">
<Styles>
<Style ss:ID=""DateTimeFormat"">
<NumberFormat ss:Format=""yyyy/mm/dd\ hh:mm:ss;@""/>
</Style>
<Style ss:ID=""TitleRow"">
<Font ss:Bold=""1"" ss:Size=""11"" />
</Style>
</Styles>

<Worksheet ss:Name=""Sheet1"">
<Table>
<Row>
<Cell ss:StyleID=""TitleRow""><Data ss:Type=""String"">Page</Data></Cell>
<Cell ss:StyleID=""TitleRow""><Data ss:Type=""String"">Author</Data></Cell>
<Cell ss:StyleID=""TitleRow""><Data ss:Type=""String"">Creation</Data></Cell>
<Cell ss:StyleID=""TitleRow""><Data ss:Type=""String"">Modified</Data></Cell>
<Cell ss:StyleID=""TitleRow""><Data ss:Type=""String"">Comment</Data></Cell>
<Cell ss:StyleID=""TitleRow""><Data ss:Type=""String"">Reply</Data></Cell>
</Row>
";
        }

        /// <summary>
        /// XMLプレッドシートの行を取得します。
        /// </summary>
        /// <param name="annots">行に対応する注釈。</param>
        private string GetAnnotRows(IEnumerable<Annotation> annots)
        {
            List<Annotation> sortedAnnots = new List<Annotation>(annots);
            sortedAnnots.Sort(
                delegate(Annotation a, Annotation b) { return a.ModifiedDate.CompareTo(b.ModifiedDate); }
            );

            StringBuilder result = new StringBuilder();

            foreach (Annotation annot in sortedAnnots)
            {
                List<string> childrenRows = GetAnnotChildrenRows(annot);

                result.AppendLine("<Row>");
                string line = GetNumberCell(annot.PageNumber)
                            + GetStringCell(annot.Author)
                            + GetDateTimeCell(annot.CreationDate)
                            + GetDateTimeCell(annot.ModifiedDate)
                            + GetStringCell(annot.Contents);

                if(childrenRows.Count > 0)
                {
                    line = line.Replace("ss:MergeDown=\"0\"", "ss:MergeDown=\"" + (childrenRows.Count - 1) + "\"");
                    Annotation child = annot.Children[0];
                    line += GetReplyCell(child);
                }

                result.AppendLine(line);
                result.AppendLine("</Row>");

                for(int i = 1; i < childrenRows.Count; i++)
                {
                    result.AppendLine(childrenRows[i]);
                }
            }
            
            return result.ToString();
        }

        /// <summary>
        /// 返信注釈の行を取得します。
        /// </summary>
        /// <param name="annot">返信の行を取得する注釈。</param>
        private List<string> GetAnnotChildrenRows(Annotation annot)
        {
            List<Annotation> children = new List<Annotation>(annot.Children);
            children.Sort(
                delegate(Annotation a, Annotation b) { return a.ModifiedDate.CompareTo(b.ModifiedDate); }
            );

            List<string> result = new List<string>();
            foreach(Annotation child in children)
            {
                result.Add("<Row>"
                             + GetReplyCell(child).Replace("<Cell", "<Cell ss:Index=\"6\"")
                             + "</Row>");
                result.AddRange(GetAnnotChildrenRows(child));
            }

            return result;
        }

        /// <summary>
        /// 返信のセルを取得します。
        /// </summary>
        /// <param name="replyAnnot">返信の注釈。</param>
        private string GetReplyCell(Annotation replyAnnot)
        {
            return GetStringCell(replyAnnot.Contents + "\n" + replyAnnot.Author + " " + replyAnnot.ModifiedDate.ToString());
        }

        /// <summary>
        /// XMLスプレッドシートのフッターを取得します。
        /// </summary>
        /// <returns>XMLスプレッドシートのフッター。</returns>
        private string GetFooter()
        {
            return "</Table></Worksheet></Workbook>";
        }

        /// <summary>
        /// DateTime型セルのXMLノードを取得します。
        /// </summary>
        /// <param name="number">セルの値。</param>
        /// <returns>セルのXMLノード。</returns>
        private string GetDateTimeCell(DateTime date)
        {
            if (date == new DateTime(0))
            {
                return GetStringCell("");
            }

            string result = GetCellNode("DateTime", String.Format("{0:yyyy-MM-dd}T{0:HH:mm:ss.fff}", date));
            return result.Replace("<Cell", "<Cell ss:StyleID=\"DateTimeFormat\"");
        }

        /// <summary>
        /// Number型セルのXMLノードを取得します。
        /// </summary>
        /// <param name="number">セルの値。</param>
        /// <returns>セルのXMLノード。</returns>
        private string GetNumberCell(int number)
        {
            return GetCellNode("Number", number.ToString());
        }

        /// <summary>
        /// String型セルのXMLノードを取得します。
        /// </summary>
        /// <param name="number">セルの値。</param>
        /// <returns>セルのXMLノード。</returns>
        private string GetStringCell(string str)
        {
            string cellValue = str.Replace("\r\n", "\n");
            cellValue = cellValue.Replace('\r', '\n');
            cellValue = cellValue.Replace("\n", "&#10;");
            cellValue = cellValue.Replace('\t', ' ');

            return GetCellNode("String", cellValue);
        }

        /// <summary>
        /// セルのXMLノードを取得します。
        /// </summary>
        /// <param name="valueType">セルの型。</param>
        /// <param name="value">セルの値。</param>
        /// <returns>セルのXMLノード。</returns>
        private string GetCellNode(string type, string value)
        {
            StringBuilder result = new StringBuilder();
            result.Append("<Cell ss:MergeDown=\"0\"><Data ss:Type=\"").Append(type).Append("\">");
            result.Append(value);
            result.Append("</Data></Cell>");
            return result.ToString();
        }

    }
}
