using System;
using System.Collections.Generic;
using System.Text;

using iTextSharp.text.pdf;

namespace PDFAnnotationList
{
    /// <summary>
    /// PDFファイルの注釈を読み込みます。
    /// </summary>
    class PDFAnnotationReader
    {
        /// <summary>
        /// PDFオブジェクトのラベル
        /// </summary>
        class ObjectLabel
        {
            public int ObjectNumber { get; private set; }
            public int GenerationNumbe { get; private set; }
            
            public ObjectLabel(int objectNumber, int generationNumber)
            {
                ObjectNumber = objectNumber;
                GenerationNumbe = generationNumber;
            }
        }
        class ObjectLabelEqualityComparer : IEqualityComparer<ObjectLabel>
        {
            bool IEqualityComparer<ObjectLabel>.Equals(ObjectLabel a, ObjectLabel b)
            {
                return (a.ObjectNumber == b.ObjectNumber) && (a.GenerationNumbe == b.GenerationNumbe);
            }

            int IEqualityComparer<ObjectLabel>.GetHashCode(ObjectLabel label)
            {
                return label.ObjectNumber ^ label.GenerationNumbe;
            }
        }

        /// <summary>
        /// 注釈のリレーション
        /// </summary>
        class AnnotationRelation
        {
            public Annotation Annotation { get; private set; }
            public ObjectLabel InReplyTo { get; private set; }

            public AnnotationRelation(Annotation annotation, ObjectLabel inReplyTo)
            {
                this.Annotation = annotation;
                this.InReplyTo = inReplyTo;
            }
        }

        /// <summary>
        /// AnnotationRelationを作ります。
        /// </summary>
        /// <param name="annotDic">注釈の辞書。</param>
        /// <param name="pageNumber">注釈のページ。</param>
        /// <returns>AnnotationRelationの実体。</returns>
        private static AnnotationRelation CreateAnnotationRelation(PdfDictionary annotDic, int pageNumber)
        {
            ObjectLabel irt = null;
            if (annotDic.Contains(PdfName.IRT))
            {
                PdfIndirectReference irtRef = annotDic.GetAsIndirectObject(PdfName.IRT);
                irt = new ObjectLabel(irtRef.Number, irtRef.Generation);
            }
            Annotation test = new Annotation(annotDic, pageNumber);
            return new AnnotationRelation(new Annotation(annotDic, pageNumber), irt);
        }

        /// <summary>
        /// PDFファイルを読み込み注釈を取得します。
        /// </summary>
        /// <param name="filePath">PDFファイルのパス。</param>
        /// <returns>Annotationオブジェクトのリスト。</returns>
        public static ICollection<Annotation> Read(string filePath)
        {
            List<Annotation> result = new List<Annotation>();

            PdfReader reader = new PdfReader(new RandomAccessFileOrArray(filePath), null);

            for (int i = 1; i <= reader.NumberOfPages; i++)
            {
                PdfDictionary pageDic = reader.GetPageN(i);
                result.AddRange(ReadPage(pageDic, i));
            }

            return result;
        }

        /// <summary>
        /// ページを読み込み、ページ内の注釈を取得します。
        /// </summary>
        /// <param name="pageDic">ページのPdfDictionary。</param>
        /// <param name="pageNumber">ページ番号。</param>
        /// <returns>ページ内に含まれる注釈のリスト。</returns>
        private static List<Annotation> ReadPage(PdfDictionary pageDic, int pageNumber)
        {
            Dictionary<ObjectLabel, AnnotationRelation> _allAnnots =
                new Dictionary<ObjectLabel, AnnotationRelation>(new ObjectLabelEqualityComparer());

            PdfArray annotArray = (PdfArray)PdfReader.GetPdfObject(pageDic.Get(PdfName.ANNOTS));

            if (annotArray == null)
            {
                return new List<Annotation>();
            }

            foreach (PdfIndirectReference annot in annotArray.ArrayList)
            {
                PdfDictionary annotDic = (PdfDictionary)PdfReader.GetPdfObject(annot);
                PdfName subType = (PdfName)annotDic.Get(PdfName.SUBTYPE);
                if (AcceptAnnotation(subType))
                {
                    _allAnnots.Add(new ObjectLabel(annot.Number, annot.Generation), CreateAnnotationRelation(annotDic, pageNumber));
                }
            }

            return RelateAnnotations(_allAnnots);
        }

        /// <summary>
        /// 注釈を関連付けます。
        /// </summary>
        /// <param name="annotations">関連付ける注釈の辞書。</param>
        /// <returns>関連付けた注釈のリスト。</returns>
        private static List<Annotation> RelateAnnotations(IDictionary<ObjectLabel, AnnotationRelation> annotations)
        {
            List<Annotation> result = new List<Annotation>();

            foreach (AnnotationRelation annot in annotations.Values)
            {
                if (annot.InReplyTo == null)
                {
                    result.Add(annot.Annotation);
                }
                else
                {
                    annotations[annot.InReplyTo].Annotation.Children.Add(annot.Annotation);
                }
            }

            return result;
        }

        /// <summary>
        /// 処理対象の注釈かどうか取得します。
        /// </summary>
        /// <param name="annotSubType">注釈のサブタイプ。</param>
        /// <returns>処理対象の注釈はtrue、処理対象外の注釈はfalse。</returns>
        private static bool AcceptAnnotation(PdfName annotSubType)
        {
            if (annotSubType == PdfName.TEXT)       return true;
            if (annotSubType == PdfName.HIGHLIGHT)  return true;
            if (annotSubType == PdfName.UNDERLINE)  return true;
            if (annotSubType == PdfName.FREETEXT)   return true;
            if (annotSubType == PdfName.LINE)       return true;
            if (annotSubType == PdfName.SQUARE)     return true;
            if (annotSubType == PdfName.CIRCLE)     return true;
            if (annotSubType == PdfName.POLYGON)    return true;
            if (annotSubType == PdfName.POLYLINE)   return true;
            if (annotSubType == PdfName.SQUIGGLY)   return true;
            if (annotSubType == PdfName.STRIKEOUT)  return true;
            if (annotSubType == PdfName.STAMP)      return true;
            if (annotSubType == PdfName.INK)        return true;
           // if (annotSubType == PdfName.POPUP) return true;
            return false;
        }
    }
}
