package com.gmail.dpierron.calibre.opds;
/**
 *  Class for defining a new tree set of levels based on a tag,
 *  with tags possibly split by a defined character.  This is
 *  a way f implementing what are known as hierarchical tags
 *  in Calibre.
 */
import com.gmail.dpierron.calibre.configuration.Icons;
import com.gmail.dpierron.calibre.datamodel.Book;
import com.gmail.dpierron.calibre.datamodel.Tag;
import com.gmail.dpierron.tools.i18n.Localization;
import com.gmail.dpierron.calibre.trook.TrookSpecificSearchDatabaseManager;
import com.gmail.dpierron.tools.Helper;
import com.gmail.dpierron.tools.RootTreeNode;
import com.gmail.dpierron.tools.TreeNode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.jdom2.Element;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

public class TagTreeSubCatalog extends TagsSubCatalog {
  private final static Logger logger = LogManager.getLogger(TagTreeSubCatalog.class);

  // CONSTRUCTOR(S)

  public TagTreeSubCatalog(List<Object> stuffToFilterOut, List<Book> books) {
    super(stuffToFilterOut, books);
    setCatalogType(Constants.TAGTREE_TYPE);
  }

  public TagTreeSubCatalog(List<Book> books) {
    super(books);
    setCatalogType(Constants.TAGTREE_TYPE);
  }

  //  METHODS

  /**
   * Generate a tag tree for the current level
   *
   * The actual tag associated with a node is stored
   * as data information
   *
   * @param pBreadcrumbs
   * @return
   * @throws IOException
   */
  @Override
  Element getCatalog(Breadcrumbs pBreadcrumbs, boolean inSubDir) throws IOException {
    Element result;
    if (logger.isDebugEnabled()) logger.debug("getCatalog: pBreadcrumbs=" + pBreadcrumbs.toString() + ", inSubDir=" + inSubDir);
    String splitTagsOn = currentProfile.getSplitTagsOn();
    assert Helper.isNotNullOrEmpty(splitTagsOn);
    TreeNode root = new RootTreeNode();
    // Work through the tags creating the tree
    if (logger.isTraceEnabled()) logger.trace("generate initial tree");
    for (Tag tag : getTags()) {
      String[] partsOfTag = tag.getPartsOfTag(splitTagsOn);
      TreeNode currentPositionInTree = root;
      for (int i = 0; i < partsOfTag.length; i++) {
        String partOfTag = partsOfTag[i];
        TreeNode nextPositionInTree = currentPositionInTree.getChildWithId(partOfTag);
        if (nextPositionInTree == null) {
          nextPositionInTree = new TreeNode(partOfTag);
          currentPositionInTree.addChild(nextPositionInTree);
          currentPositionInTree = nextPositionInTree;
        } else
          currentPositionInTree = nextPositionInTree;
      }
      // Mark the tag this node uses
      currentPositionInTree.setData(tag);
    }
    // browse the tree, removing unneeded levels (single childs up to the leafs)
    if (logger.isTraceEnabled()) logger.trace("remove unneeded levels");
    removeUnNeededLevelsInTree(root, null);
    // Now get the resulting page set
    result = getLevelOfTreeNode(pBreadcrumbs, root);
    if (logger.isDebugEnabled()) logger.debug("getCatalog: exit (pBreadcrumbs=" + pBreadcrumbs.toString() + ")");
    return result;
  }

  /**
   * Trim un-needed nodes from the tree.
   *
   * We assume that any node that only has a single
   * child can effectively have the child collapsed
   * into the parent node. This will stop us generating
   * a series of pages that only have a single entry.
   *
   * NOTE:  It is written as a free-standing routine so it cn be called recursively.
   *
   * @param node
   * @param removedParent
   * @return
   */
  private TreeNode removeUnNeededLevelsInTree(TreeNode node, TreeNode removedParent) {
    // if (logger.isTraceEnabled()) logger.trace("removeUnNeededLevel: node=" + node + ", removedParent=" + removedParent);
    if (removedParent != null) {
      node.setId(removedParent.getId() + currentProfile.getSplitTagsOn() + node.getId());
    }
    if (node.getData() != null) {
      // this is a leaf
      return node;
    }

    List<TreeNode> newChildren = new LinkedList<TreeNode>();
    for (TreeNode childNode : node.getChildren()) {
      if (childNode.getData() == null && childNode.getChildren().size() <= 1) {
        if (childNode.getChildren().size() == 0) {
          // useless node
          // TODO:  ITIMPI:  Feel there should be something done here if this condition can really ever occur
          int dummy = 1;        // TODO See if we really ever get here!
        } else {
          // useless level so remove it
          TreeNode newChild = removeUnNeededLevelsInTree(childNode.getChildren().get(0), childNode);
          if (newChild != null) {
            newChild.setParent(node);
            newChildren.add(newChild);
          }
        }
      } else {
        newChildren.add(removeUnNeededLevelsInTree(childNode, null));
      }
    }
    node.setChildren(newChildren);
    return node;
  }

  /**
   * Initial entry point to creating a tree list of tags
   *
   * @param pBreadcrumbs
   * @param level
   * @return
   * @throws IOException
   */
  private Element getLevelOfTreeNode(Breadcrumbs pBreadcrumbs, TreeNode level) throws IOException {
    if (logger.isDebugEnabled()) logger.debug("getLevelOfTreeNode: pBreadcrumbs=" + pBreadcrumbs + ", level=" + level);
    Element result;
    if (Helper.isNullOrEmpty(level.getChildren())) {
      Tag tag = (Tag) level.getData();
      if (tag == null) {
        if (logger.isDebugEnabled()) logger.debug("getLevelOfTreeNode: Exinull (Appears to be an empty level!)");
        return null;
      }
      // it's a leaf, consisting of a single tag : make a list of books
      if (logger.isTraceEnabled()) logger.trace("getLevelOfTreeNode: it's a leaf, consisting of a single tag : make a list of books");
      String urn = Constants.INITIAL_URN_PREFIX + getCatalogType()+ level.getGuid();
      result = getDetailedEntry(pBreadcrumbs, tag, urn, level.getId());
      TrookSpecificSearchDatabaseManager.addTag(tag, result);
    } else {
      result = getLevelOfTreeNode(pBreadcrumbs, level, 0);
    }
    if (logger.isDebugEnabled()) logger.debug("getLevelOfTreeNode: Exit level " + level);
    return result;
  }


  /**
   * Get the psgrd of entries for a given level in the tree.
   *
   * @param pBreadcrumbs
   * @param level
   * @param from
   * @return
   * @throws IOException
   */
  private Element getLevelOfTreeNode(Breadcrumbs pBreadcrumbs, TreeNode level, int from) throws IOException {

    if (logger.isDebugEnabled()) logger.debug("getLevelOfTreeNode: pBreadcrumbs=" + pBreadcrumbs + ", level=" + level + ", from=" + from);

    boolean inSubDir = ((getCatalogLevel().length() > 0) || (from != 0) || pBreadcrumbs.size() > 1);
    int pageNumber = Summarizer.getPageNumber(from + 1);
    int itemsCount = level.getChildren().size();

    String filename = getCatalogBaseFolderFileName()
//                    // TODO:  Get tag id as part of name to help with tracing source
                      + Constants.TYPE_SEPARATOR + encryptString(level.toString())
                      + Constants.PAGE_DELIM + pageNumber;
    logger.debug("getLevelOfTreeNode,int: generating " + filename);

    boolean onRoot = (level.isRoot());

    // TODO Might want to make the title include all 'parts' ?
    String title = (onRoot ? Localization.Main.getText("tags.title") : level.getId());
    String urn = Constants.INITIAL_URN_PREFIX + getCatalogType() + Constants.URN_SEPARATOR + encryptString(pBreadcrumbs.toString());
    String urlExt = CatalogManager.getCatalogFileUrl(filename + Constants.XML_EXTENSION, inSubDir);

    String summary = "";
    if (onRoot) {
      int tagsSize = getTags().size();
      if (tagsSize > 1)
        summary = Localization.Main.getText("tags.categorized", tagsSize);
      else if (tagsSize == 1)
        summary = Localization.Main.getText("tags.categorized.single");
    } else {
      // try and list the items to make the summary
      summary = Summarizer.summarizeTagLevels(level.getChildren());
    }
    int maxPages = Summarizer.getPageNumber(itemsCount);

    List<Element> result = new LinkedList<Element>();
    Element feed = FeedHelper.getFeedRootElement(pBreadcrumbs, title, urn, urlExt, true /*inSubDir*/);

    for (int i = from; i < itemsCount; i++) {
      if ((i - from) >= maxBeforePaginate) {
        Element nextLink = getLevelOfTreeNode(pBreadcrumbs,
                                            level,
                                            i);
        result.add(0, nextLink);
        break;
      } else {
        TreeNode childLevel = level.getChildren().get(i);
        Breadcrumbs breadcrumbs = Breadcrumbs.addBreadcrumb(pBreadcrumbs, title, urlExt);
        Element entry = getLevelOfTreeNode(breadcrumbs,
                                           childLevel);
        if (entry != null)
          result.add(entry);
      }
    }

    if (logger.isTraceEnabled()) logger.trace("getLevelOfTreeNode: add entry to feed");
    feed.addContent(result);


    Element entry;
    String urlInItsSubfolder = CatalogManager.getCatalogFileUrl(filename + Constants.XML_EXTENSION, inSubDir);
    entry = createPaginateLinks(feed, urlExt, pageNumber, maxPages);
    createFilesFromElement(feed,filename, HtmlManager.FeedType.Catalog, true);
    if (from == 0) {
      if (logger.isTraceEnabled()) {logger.trace("getLevelOfTreeNode:  Breadcrumbs=" + pBreadcrumbs.toString());}
      entry = FeedHelper.getCatalogEntry(title,
                                         urn,
                                         urlInItsSubfolder,
                                         summary,
                                         useExternalIcons ? getIconPrefix(inSubDir) + Icons.ICONFILE_TAGS : Icons.ICON_TAGS);
    }
    if (logger.isDebugEnabled()) logger.debug("getLevelOfTreeNode: Exit level " + level + ", from=" + from);
    return entry;
  }
}
