//========================================================================
//
// StructElement.h
//
// This file is licensed under the GPLv2 or later
//
// Copyright 2013, 2014 Igalia S.L.
// Copyright 2014 Luigi Scarso <luigi.scarso@gmail.com>
// Copyright 2014, 2018 Albert Astals Cid <aacid@kde.org>
//
//========================================================================

#ifndef STRUCTELEMENT_H
#define STRUCTELEMENT_H

#ifdef USE_GCC_PRAGMAS
#pragma interface
#endif

#include "goo/gtypes.h"
#include "goo/GooString.h"
#include "MarkedContentOutputDev.h"
#include "Object.h"
#include <vector>
#include <set>

class GooString;
class Dict;
class StructElement;
class StructTreeRoot;


class Attribute {
public:
  enum Type {
    Unknown = 0,        // Uninitialized, parsing error, etc.
    UserProperty,       // User defined attribute (i.e. non-standard)

    // Common standard attributes
    Placement, WritingMode, BackgroundColor, BorderColor, BorderStyle,
    BorderThickness, Color, Padding,

    // Block element standard attributes
    SpaceBefore, SpaceAfter, StartIndent, EndIndent, TextIndent, TextAlign,
    BBox, Width, Height, BlockAlign, InlineAlign, TBorderStyle, TPadding,

    // Inline element standard attributes
    BaselineShift, LineHeight, TextDecorationColor, TextDecorationThickness,
    TextDecorationType, RubyAlign, RubyPosition, GlyphOrientationVertical,

    // Column-only standard attributes
    ColumnCount, ColumnGap, ColumnWidths,

    // List-only standard attributes
    ListNumbering,

    // PrintField-only standard attributes
    Role, checked, Desc,

    // Table-only standard attributes
    RowSpan, ColSpan, Headers, Scope, Summary,
  };

  enum Owner {
    UnknownOwner = 0,
    // User-defined attributes
    UserProperties,
    // Standard attributes
    Layout, List, PrintField, Table,
    // Translation to other formats
    XML_1_00, HTML_3_20, HTML_4_01, OEB_1_00, RTF_1_05, CSS_1_00, CSS_2_00,
  };

  // Creates a standard attribute. The name is predefined, and the
  // value is type-checked to conform to the PDF specification.
  Attribute(Type type, Object *value);

  // Creates an UserProperty attribute, with an arbitrary name and value.
  Attribute(const char *name, int nameLen, Object *value);

  GBool isOk() const { return type != Unknown; }

  // Name, type and value can be set only on construction.
  Type getType() const { return type; }
  Owner getOwner() const { return owner; }
  const char *getTypeName() const;
  const char *getOwnerName() const;
  Object *getValue() const { return &value; }
  static Object *getDefaultValue(Type type);

  // The caller gets the ownership of the return GooString and is responsible of deleting it
  GooString *getName() const { return type == UserProperty ? name.copy() : new GooString(getTypeName()); }

  // The revision is optional, and defaults to zero.
  Guint getRevision() const { return revision; }
  void setRevision(Guint revisionA) { revision = revisionA; }

  // Hidden elements should not be displayed by the user agent
  GBool isHidden() const { return hidden; }
  void setHidden(GBool hiddenA) { hidden = hiddenA; }

  // The formatted value may be in the PDF, or be left undefined (NULL).
  // In the later case the user agent should provide a default representation.
  const char *getFormattedValue() const { return formatted ? formatted->getCString() : NULL; }
  void setFormattedValue(const char *formattedA);

  ~Attribute();

private:
  Type type;
  Owner owner;
  Guint revision;
  mutable GooString name;
  mutable Object value;
  GBool hidden;
  GooString *formatted;

  GBool checkType(StructElement *element = NULL);
  static Type getTypeForName(const char *name, StructElement *element = NULL);
  static Attribute *parseUserProperty(Dict *property);

  friend class StructElement;
};


class StructElement {
public:
  enum Type {
    Unknown = 0,
    MCID,                                   // MCID reference, used internally
    OBJR,                                   // Object reference, used internally

    Document, Part, Art, Sect, Div,         // Structural elements

    Span, Quote, Note, Reference, BibEntry, // Inline elements
    Code, Link, Annot,
    BlockQuote, Caption, NonStruct,
    TOC, TOCI, Index, Private,

    P, H, H1, H2, H3, H4, H5, H6,           // Paragraph-like

    L, LI, Lbl, LBody,                      // List elements

    Table, TR, TH, TD, THead, TFoot, TBody, // Table elements

    Ruby, RB, RT, RP,                       // Ruby text elements
    Warichu, WT, WP,

    Figure, Formula, Form,                  // Illustration-like elements
  };

  static const Ref InvalidRef;

  const char *getTypeName() const;
  Type getType() const { return type; }
  GBool isOk() const { return type != Unknown; }
  GBool isBlock() const;
  GBool isInline() const;
  GBool isGrouping() const;

  inline GBool isContent() const { return (type == MCID) || isObjectRef(); }
  inline GBool isObjectRef() const { return (type == OBJR && c->ref.num != -1 && c->ref.gen != -1); }

  int getMCID() const { return c->mcid; }
  Ref getObjectRef() const { return c->ref; }
  Ref getParentRef() { return isContent() ? parent->getParentRef() : s->parentRef.getRef(); }
  GBool hasPageRef() const;
  GBool getPageRef(Ref& ref) const;
  StructTreeRoot *getStructTreeRoot() { return treeRoot; }

  // Optional element identifier.
  const GooString *getID() const { return isContent() ? nullptr : s->id; }
  GooString *getID() { return isContent() ? nullptr : s->id; }

  // Optional ISO language name, e.g. en_US
  GooString *getLanguage() {
    if (!isContent() && s->language) return s->language;
    return parent ? parent->getLanguage() : NULL;
  }
  const GooString *getLanguage() const {
    if (!isContent() && s->language) return s->language;
    return parent ? parent->getLanguage() : NULL;
  }

  // Optional revision number, defaults to zero.
  Guint getRevision() const { return isContent() ? 0 : s->revision; }
  void setRevision(Guint revision) { if (isContent()) s->revision = revision; }

  // Optional element title, in human-readable form.
  const GooString *getTitle() const { return isContent() ? nullptr : s->title; }
  GooString *getTitle() { return isContent() ? nullptr : s->title; }

  // Optional element expanded abbreviation text.
  const GooString *getExpandedAbbr() const { return isContent() ? nullptr : s->expandedAbbr; }
  GooString *getExpandedAbbr() { return isContent() ? nullptr : s->expandedAbbr; }

  unsigned getNumChildren() const { return isContent() ? 0 : s->elements.size(); }
  const StructElement *getChild(int i) const { return isContent() ? nullptr : s->elements.at(i); }
  StructElement *getChild(int i) { return isContent() ? nullptr : s->elements.at(i); }

  void appendChild(StructElement *element) {
    if (!isContent() && element && element->isOk()) {
      s->elements.push_back(element);
    }
  }

  unsigned getNumAttributes() const { return isContent() ? 0 : s->attributes.size(); }
  const Attribute *getAttribute(int i) const { return isContent() ? nullptr : s->attributes.at(i); }
  Attribute *getAttribute(int i) { return isContent() ? nullptr : s->attributes.at(i); }

  void appendAttribute(Attribute *attribute) {
    if (!isContent() && attribute) {
      s->attributes.push_back(attribute);
    }
  }

  const Attribute* findAttribute(Attribute::Type attributeType, GBool inherit = gFalse,
                                 Attribute::Owner owner = Attribute::UnknownOwner) const;

  const GooString *getAltText() const { return isContent() ? nullptr : s->altText; }
  GooString *getAltText() { return isContent() ? nullptr : s->altText; }

  const GooString *getActualText() const { return isContent() ? nullptr : s->actualText; }
  GooString *getActualText() { return isContent() ? nullptr : s->actualText; }

  // Content text referenced by the element:
  //
  // - For MCID reference elements, this is just the text of the
  //   corresponding marked content object in the page stream, regardless
  //   of the setting of the "recursive" flag.
  // - For other elements, if the "recursive" flag is set, the text
  //   enclosed by *all* the child MCID reference elements of the subtree
  //   is returned. The text is assembled by traversing the leaf MCID
  //   reference elements in logical order.
  // - In any other case, the function returns NULL.
  //
  // A new string is returned, and the ownership passed to the caller.
  //
  GooString *getText(GBool recursive = gTrue) const {
    return appendSubTreeText(nullptr, recursive);
  }

  const TextSpanArray getTextSpans() const {
    if (!isContent())
      return TextSpanArray();
    MarkedContentOutputDev mcdev(getMCID());
    return getTextSpansInternal(mcdev);
  }

  ~StructElement();

private:
  GooString* appendSubTreeText(GooString *string, GBool recursive) const;
  const TextSpanArray& getTextSpansInternal(MarkedContentOutputDev& mcdev) const;

  typedef std::vector<Attribute*>     AttrPtrArray;
  typedef std::vector<StructElement*> ElemPtrArray;

  struct StructData {
    Object       parentRef;
    GooString   *altText;
    GooString   *actualText;
    GooString   *id;
    GooString   *title;
    GooString   *expandedAbbr;
    GooString   *language;
    Guint        revision;
    ElemPtrArray elements;
    AttrPtrArray attributes;

    StructData();
    ~StructData();

    StructData(const StructData &) = delete;
    StructData& operator=(const StructData &) = delete;
  };

  // Data in content elements (MCID, MCR)
  struct ContentData {
    union {
      int mcid;
      Ref ref;
    };

    ContentData(int mcidA): mcid(mcidA) {}
    ContentData(const Ref& r) { ref.num = r.num; ref.gen = r.gen; }
  };

  // Common data
  Type type;
  StructTreeRoot *treeRoot;
  StructElement *parent;
  mutable Object pageRef;

  union {
    StructData  *s;
    ContentData *c;
  };

  StructElement(Dict *elementDict, StructTreeRoot *treeRootA, StructElement *parentA, std::set<int> &seen);
  StructElement(int mcid, StructTreeRoot *treeRootA, StructElement *parentA);
  StructElement(const Ref &ref, StructTreeRoot *treeRootA, StructElement *parentA);

  void parse(Dict* elementDict);
  StructElement* parseChild(Object *ref, Object* childObj, std::set<int> &seen);
  void parseChildren(Dict* element, std::set<int> &seen);
  void parseAttributes(Dict *element, GBool keepExisting = gFalse);

  friend class StructTreeRoot;
};

#endif

