/*
 * Copyright (C) 2003-2011 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.exoplatform.social.common.xmlprocessor.model;

import java.util.LinkedList;

/**
 * Model of XML node tree
 *
 * @author Ly Minh Phuong - http://phuonglm.net
 */
public class Node {
  private Node parentNode = null;
  private String title = "";
  private Attributes attributes = new Attributes();
  private String content = "";
  private LinkedList<Node> childNodes = new LinkedList<Node>();

  /**
   * Gets parent Node of current Node. If current Node is root, parent Node == null;
   *
   * @return parent node
   */
  public Node getParentNode() {
    return parentNode;
  }

  /**
   * Sets the parent Node of currentNode
   *
   * @param parentNode
   */
  public void setParentNode(Node parentNode) {
    this.parentNode = parentNode;
  }

  /**
   * Gets title of Node, node's title ak the tag name.
   *
   * @return the node title
   */
  public String getTitle() {
    return title;
  }

  /**
   * Sets title of Node, node's title ak the tag name.
   *
   * @param nodeTitle
   */
  public void setTitle(String nodeTitle) {
    this.title = nodeTitle;
  }

  /**
   * Returns Attributes of Node
   *
   * @return the attributes
   */
  public Attributes getAttributes() {
    return attributes;
  }

  /**
   * Sets attributes of Node.
   *
   * @param attributes
   */
  public void setAttributes(Attributes attributes) {
    this.attributes = attributes;
  }

  /**
   * Gets the Content of Node, if Node have Content it mean that is text Node.
   *
   * @return the content node
   */
  public String getContent() {
    return content;
  }

  /**
   * Sets the Content of Node, if Node have Content it mean that is text Node.
   *
   * @param content
   */
  public void setContent(String content) {
    this.content = content;
  }

  /**
   * Gets the list child nodes of current Node.
   *
   * @return child nodes
   */
  public LinkedList<Node> getChildNodes() {
    return childNodes;
  }

  /**
   * Sets the list child nodes of current Node.
   *
   * @param childNodes the child nodes
   */
  public void setChildNodes(LinkedList<Node> childNodes) {
    this.childNodes = childNodes;
  }

  /**
   * Adds child Node to this Node.
   *
   * @param childNode the child node
   */
  public void addChildNode(Node childNode) {
    this.childNodes.add(childNode);
  }

  /**
   * Adds Attribute to current Node.
   *
   * @param key   attribute key
   * @param value attribute value
   */
  public void addAttribute(String key, String value) {
    this.attributes.put(key, value);
  }
  
  /**
   * Check if node is TextNode
   * @return
   * @since 1.2.2
   */
  public boolean isTextNode(){
    return(attributes.size() == 0 && childNodes.size() == 0 && !content.isEmpty() && title.isEmpty());
  }
  
  /**
   * Check if node is RootNode
   * @return
   * @since 1.2.2
   */
  public boolean isRootNode(){
    return(parentNode == null && attributes.size() == 0 && content.isEmpty() && title.isEmpty());
  }
  
  /**
   * Check if node is SelfCloseNode
   * @return
   * @since 1.2.2
   */
  public boolean isSelfClosedNode(){
    return childNodes.size() == 0 && content.isEmpty() && ("br".equals(title.toLowerCase()) || "img".equals(title.toLowerCase()));
  }
  
  /**
   * insert Node to DOM tree after refNode
   * @param refNode
   * @param nodeToInsert
   * @since 1.2.2
   */
  public void insertAfter(Node refNode, Node nodeToInsert){
    if(refNode != null && nodeToInsert != null){
      insertAfter(childNodes.indexOf(refNode), nodeToInsert);
    }
  }
  
  /**
   * Insert Node to DOM tree after the node at position
   * @param position
   * @param node
   * @since 1.2.2
   */
  public void insertAfter(int position, Node node){
    if((position + 1) <= childNodes.size() && node != null){
      childNodes.add(position + 1, node);
    }
  }
  /**
   * Convert Node to XML String including all sub-Node
   * @return XML String
   */
  @Override
  public String toString() {
    StringBuilder xmlString = new StringBuilder("");

    if (isTextNode()) {
      xmlString.append(this.content);
    } else {
      if (this.parentNode != null) {

        xmlString.append("<" + this.title);

        xmlString.append(attributes.toString());

        if (isSelfClosedNode()) {
          xmlString.append(" /");
        }
        xmlString.append(">");
      }
      for (Node childNode : childNodes) {
        xmlString.append(childNode.toString());
      }

      if (this.parentNode != null && !isSelfClosedNode()) {
        xmlString.append("</" + this.title + ">");
      }
    }
    return xmlString.toString();
  }

  /**
   * Returns the Open Tag of this Node, if this node is textNode so this will equal blank string.
   *
   * @return
   */
  private String toOpenString() {
    StringBuilder xmlString = new StringBuilder("");

    if (isTextNode()) {
      xmlString.append(this.content);
    } else {
      if (this.parentNode != null) {

        xmlString.append("<" + this.title);

        xmlString.append(attributes.toString());

        if (isSelfClosedNode()) {
          xmlString.append(" /");
        }
        xmlString.append(">");
      }
    }
    return xmlString.toString();
  }

  /**
   * Returns the Close Tag of this Node, if this node is textNode so this will equal blank string.
   *
   * @return
   */
  private String toCloseString() {
    StringBuilder xmlString = new StringBuilder("");

    if (isTextNode()) {
      xmlString.append(this.content);
    } else {
      if (this.parentNode != null && !isSelfClosedNode()) {
        xmlString.append("</" + this.title + ">");
      }
    }
    return xmlString.toString();
  }

  /**
   * Converts this Tag Node to Content Node, add Close Tag at the end of child nodes and move all child Nodes of this
   * Node to parent Node.
   */
  public void convertToContent() {
    if (parentNode != null) {
      int thisPostion = parentNode.getChildNodes().indexOf(this);
      String content = this.toOpenString();
      String closeTag = this.toCloseString();
      this.title = "";
      this.attributes.clear();

      this.content = content;
      if (!closeTag.isEmpty()) {
        Node closeContentNode = new Node();
        closeContentNode.setContent(closeTag);
        childNodes.addLast(closeContentNode);
        moveAllChildNodesToOtherNode(parentNode, thisPostion);
      }
    }
  }


  /**
   * Moves all child Node of this Node to destNode and insert it after insertPosition;
   *
   * @param destNode
   * @param insertPosition
   */
  private void moveAllChildNodesToOtherNode(Node destNode, int insertPosition) {
    if (destNode != null) {
      LinkedList<Node> parentChildNodes = destNode.getChildNodes();
      int postShift = insertPosition;
      for (Node childNode : childNodes) {
        postShift++;
        childNode.setParentNode(destNode);
        parentChildNodes.add(postShift, childNode);
      }
      childNodes.clear();
    }
  }
}
