/*
 * Copyright (C) 2003-2010 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.wiki.rendering.macro.children;

import org.apache.commons.lang.StringUtils;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.wiki.mow.api.Page;
import org.exoplatform.wiki.mow.api.Wiki;
import org.exoplatform.wiki.rendering.builder.ReferenceBuilder;
import org.exoplatform.wiki.rendering.context.MarkupContextManager;
import org.exoplatform.wiki.rendering.macro.ExcerptUtils;
import org.exoplatform.wiki.service.WikiContext;
import org.exoplatform.wiki.service.WikiPageParams;
import org.exoplatform.wiki.service.WikiService;
import org.exoplatform.wiki.tree.TreeNode;
import org.exoplatform.wiki.tree.utils.TreeUtils;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.context.Execution;
import org.xwiki.context.ExecutionContext;
import org.xwiki.rendering.block.*;
import org.xwiki.rendering.listener.reference.DocumentResourceReference;
import org.xwiki.rendering.listener.reference.ResourceType;
import org.xwiki.rendering.macro.AbstractMacro;
import org.xwiki.rendering.macro.MacroExecutionException;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.rendering.transformation.MacroTransformationContext;

import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

@Component("children")
public class ChildrenMacro extends AbstractMacro<ChildrenMacroParameters> {
  private static final Log    log         = ExoLogger.getLogger(ChildrenMacro.class);

  /**
   * The description of the macro
   */
  private static final String DESCRIPTION = "Display children and descendants of a specific page";
  
  /**
   * Used to get the current syntax parser.
   */
  @Inject
  private ComponentManager    componentManager;
  
  /**
   * Used to get the current context
   */
  @Inject
  private Execution           execution;
  
  /**
   * Used to get the build context for document
   */
  @Inject
  private MarkupContextManager markupContextManager;
  
  private boolean excerpt;
  
  public ChildrenMacro() {
    super("Children", DESCRIPTION, ChildrenMacroParameters.class);
    setDefaultCategory(DEFAULT_CATEGORY_NAVIGATION);
  }

  @Override
  public List<Block> execute(ChildrenMacroParameters parameters, String content, MacroTransformationContext context) throws MacroExecutionException {
    boolean descendant = parameters.isDescendant();
    excerpt = parameters.isExcerpt();
    String documentName = parameters.getParent();
    String childrenNum = parameters.getChildrenNum();
    String depth = parameters.getDepth();
    
    WikiPageParams params = markupContextManager.getMarkupContext(documentName, ResourceType.DOCUMENT);
    if (StringUtils.EMPTY.equals(documentName)) {
      ExecutionContext ec = execution.getContext();
      if (ec != null) {
        WikiContext wikiContext = (WikiContext) ec.getProperty(WikiContext.WIKICONTEXT);
        params = wikiContext;
      }
    }
    Block root;
    try {
      WikiService wikiService = ExoContainerContext.getCurrentContainer().getComponentInstanceOfType(WikiService.class);
            
      // Check if root page exist
      Page wikiPage = wikiService.getPageOfWikiByName(params.getType(), params.getOwner(), params.getPageName());
      if (wikiPage == null) {
        // if root page was renamed then find it
        wikiPage = wikiService.getRelatedPage(params.getType(), params.getOwner(), params.getPageName());
        if (wikiPage != null) {
          Wiki wiki = wikiService.getWikiByTypeAndOwner(wikiPage.getWikiType(), wikiPage.getWikiOwner());
          params = new WikiPageParams(wiki.getType(), wiki.getOwner(), wikiPage.getName());
        }
      }
      
      root = generateTree(params, descendant, childrenNum, depth, context);
      WikiContext wikiContext = getWikiContext();
      wikiService.addPageLink(new WikiPageParams(wikiContext.getType(), wikiContext.getOwner(), wikiContext.getPageName()),
                                        new WikiPageParams(params.getType(), params.getOwner(), params.getPageName()));
      return Collections.singletonList(root);
    } catch (Exception e) {
      log.debug("Failed to ", e);
      return Collections.emptyList();
    }
  }

  private Block generateTree(WikiPageParams params, boolean descendant, String childrenNum, String depth, MacroTransformationContext context) throws Exception {
    Block block = new GroupBlock();
    HashMap<String, Object> treeContext = new HashMap<String, Object>();
    treeContext.put(TreeNode.SHOW_DESCENDANT, descendant);
    treeContext.put(TreeNode.CHILDREN_NUMBER, childrenNum);
    treeContext.put(TreeNode.DEPTH, depth);
    TreeNode node = TreeUtils.getDescendants(params, treeContext);
    addBlock(block, node,context);
    return block;
  }

  public ListItemBlock trankformToBlock(TreeNode node, MacroTransformationContext context) throws Exception {
    WikiService wikiService = (WikiService) ExoContainerContext.getCurrentContainer().getComponentInstanceOfType(WikiService.class);
    List<Block> blocks = new ArrayList<Block>();
    
    WikiPageParams params = TreeUtils.getPageParamsFromPath(node.getPath());
    Page page = wikiService.getPageOfWikiByName(params.getType(), params.getOwner(), params.getPageName());
    DocumentResourceReference link = new DocumentResourceReference(getReferenceBuilder(context).build(params));
    List<Block> content = new ArrayList<Block>();
    content.add(new WordBlock(page.getTitle()));

    LinkBlock linkBlock = new LinkBlock(content, link, true);
    blocks.add(linkBlock);
    if (excerpt) {
      String excerpts = ExcerptUtils.getExcerpts(params);
      if (!StringUtils.EMPTY.equals(excerpts)) {
        blocks.add(new RawBlock(excerpts, Syntax.XHTML_1_0));
      }
    }
    return new ListItemBlock(blocks);
  }

  public void addBlock(Block block, TreeNode node, MacroTransformationContext context) throws Exception {
    List<TreeNode> children = node.getChildren();
    Block childrenBlock = new BulletedListBlock(Collections.<Block> emptyList());
    int size = children.size();
    for (int i = 0; i < size; i++) {
      Block listBlock = trankformToBlock(children.get(i), context);
      addBlock(listBlock, children.get(i), context);
      childrenBlock.addChild(listBlock);
    }
    block.addChild(childrenBlock);
  }

  @Override
  public boolean supportsInlineMode() {
    return true;
  }

  private ReferenceBuilder getReferenceBuilder(MacroTransformationContext context) throws MacroExecutionException {
    try {
      return componentManager.getInstance(ReferenceBuilder.class, context.getSyntax().toIdString());
    } catch (ComponentLookupException e) {
      throw new MacroExecutionException(String.format("Failed to find reference builder for syntax %s", context.getSyntax().toIdString()), e);
    }
  }
  
  private WikiContext getWikiContext() {
    ExecutionContext ec = execution.getContext();
    WikiContext wikiContext = null;
    if (ec != null) {
      wikiContext = (WikiContext) ec.getProperty(WikiContext.WIKICONTEXT);
    }
    return wikiContext;
  }
}
