// Dstl (c) Crown Copyright 2017
package uk.gov.dstl.baleen.uima.utils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.apache.uima.jcas.tcas.Annotation;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

import uk.gov.dstl.baleen.types.structure.Structure;
import uk.gov.dstl.baleen.uima.utils.select.AbstractNode;
import uk.gov.dstl.baleen.uima.utils.select.Node;

/**
 * A node in the structure hierarchy of the document
 *
 * <p>All nodes except the root node contain a structure annotation and gives access to the parent
 * and child structure nodes.
 */
public class AnnotationNode<T extends Annotation> extends AbstractNode<T> {

  private static final Set<String> nonAttributes =
      ImmutableSet.of("getElementClass", "getElementId", "getTypeIndexID");

  /** lazily created attribute map */
  private Map<String, String> attributes;

  /**
   * Constructor for the Structure Node.
   *
   * @param parent the parent node
   * @param annotation the structure
   */
  protected AnnotationNode(AnnotationNode<T> parent, T annotation) {
    super(parent, annotation);
  }

  /**
   * Add child node, to be used during construction of the hierarchy
   *
   * @param node the child node
   */
  protected void addChild(AnnotationNode<T> node) {
    super.addChild(node);
  }

  @Override
  public String getTypeName() {
    if (getItem() == null) {
      return "Root";
    } else {
      return getItem().getType().getShortName();
    }
  }

  @Override
  public String text() {
    if (getItem() == null) {
      return "";
    } else {
      return getItem().getCoveredText();
    }
  }

  @Override
  public Map<String, String> attributes() {
    if (attributes != null) {
      return attributes;
    }

    attributes = new HashMap<>();
    T annotation = getItem();
    if (annotation != null) {

      if (annotation instanceof Structure) {
        addAttribute(attributes, "class", ((Structure) annotation).getElementClass());
        addAttribute(attributes, "id", ((Structure) annotation).getElementId());
      }

      for (Method method : annotation.getClass().getDeclaredMethods()) {
        String name = method.getName();
        if (name.startsWith("get")
            && method.getParameterTypes().length == 0
            && !nonAttributes.contains(name)) {
          String key = name.substring(3).toLowerCase();
          try {
            addAttribute(attributes, key, method.invoke(annotation));
          } catch (IllegalAccessException
              | IllegalArgumentException
              | InvocationTargetException e) {
            throw new RuntimeException(e);
          }
        }
      }
    }
    return attributes;
  }

  /**
   * Add attribute to the map if not null
   *
   * @param attributes the attributes map
   * @param key the attribute key
   * @param value the value
   */
  private void addAttribute(Map<String, String> attributes, String key, Object value) {
    if (value != null) {
      attributes.put(key, value.toString());
    }
  }

  @Override
  public String ownText() {
    StringBuilder sb = new StringBuilder(text());
    List<Node<T>> reversed = ImmutableList.copyOf(getChildren()).reverse();
    for (Node<T> s : reversed) {
      sb.replace(getBegin(s) - getBegin(this), getEnd(s) - getBegin(this), "");
    }
    return sb.toString();
  }

  @Override
  public List<String> getClasses() {
    T annotation = getItem();
    if (annotation instanceof Structure) {
      String classes = ((Structure) annotation).getElementClass();
      if (StringUtils.isNotBlank(classes)) {
        return Arrays.asList(classes.split("\\s")).stream()
            .map(String::toLowerCase)
            .collect(Collectors.toList());
      }
    }
    return ImmutableList.of();
  }

  @Override
  public String id() {
    T annotation = getItem();
    if (annotation instanceof Structure) {
      String id = ((Structure) annotation).getElementId();
      if (id != null) {
        return id;
      }
    }
    return "";
  }

  /**
   * Get the begin offset of the contained structure node (or 0).
   *
   * @return the begin
   */
  public static int getBegin(Node<? extends Annotation> node) {
    return node.getItem() == null ? 0 : node.getItem().getBegin();
  }

  /**
   * Get the end offset of the contained structure node (or int max).
   *
   * @return the end
   */
  public static int getEnd(Node<? extends Annotation> node) {
    return node.getItem() == null ? Integer.MAX_VALUE : node.getItem().getEnd();
  }
}
