/*
 * Copyright (c) 2013-2016 Chris Newland.
 * Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD
 * Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki
 */
package org.adoptopenjdk.jitwatch.model;

import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_CLOSE_ANGLE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_DOUBLE_QUOTE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_EQUALS;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_NEWLINE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_OPEN_ANGLE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_SLASH;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_SPACE;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.adoptopenjdk.jitwatch.util.StringUtil;

public class Tag
{
	private String name;
	private String attributeString;
	private List<Tag> children = new ArrayList<>();
	private Tag parent = null;
	private boolean selfClosing = false;
	private boolean isFragment = false;
	private String textContent = null;
	
	private static final String INDENT = "  ";

	public Tag(String name, String attributeString, boolean selfClosing)
	{
		this.name = name;
		this.attributeString = attributeString;
		this.selfClosing = selfClosing;
	}

	public void addTextContent(String text)
	{
		if (textContent == null)
		{
			textContent = text;
		}
		else
		{
			textContent += text;
		}
	}

	public String getTextContent()
	{
		return textContent;
	}

	public void addChild(Tag child)
	{
		child.setParent(this);
		children.add(child);
	}

	public List<Tag> getChildren()
	{
		return Collections.unmodifiableList(children);
	}

	public boolean isSelfClosing()
	{
		return selfClosing;
	}
	
	public String getClosingTag()
	{		
		StringBuilder builder = new StringBuilder();
		builder.append(C_OPEN_ANGLE).append(C_SLASH).append(name).append(C_CLOSE_ANGLE);
		
		return builder.toString();
	}

	public Tag getFirstNamedChild(String name)
	{
		List<Tag> namedChildren = getNamedChildren(name);

		if (namedChildren.size() > 0)
		{
			return namedChildren.get(0);
		}
		else
		{
			return null;
		}
	}

	public List<Tag> getNamedChildren(String name)
	{
		List<Tag> result = new ArrayList<>();

		for (Tag child : children)
		{
			if (child.getName().equals(name))
			{
				result.add(child);
			}
		}

		return result;
	}

	public List<Tag> getNamedChildrenWithAttribute(String tagName, String attrName, String attrValue)
	{	
		List<Tag> result = new ArrayList<>();

		for (Tag child : children)
		{
			if (child.getName().equals(tagName))
			{
				Map<String, String> attributes = child.getAttributes();
				
				if (attrValue != null && attrValue.equals(attributes.get(attrName)))
				{
					result.add(child);
				}
			}
		}

		return result;
	}

	public Tag getParent()
	{
		return parent;
	}

	public void setParent(Tag parent)
	{
		this.parent = parent;
	}

	public String getName()
	{
		return name;
	}

	public Map<String, String> getAttributes()
	{
		return StringUtil.attributeStringToMap(attributeString);
	}

	private int getDepth(Tag tag)
	{
		if (tag.getParent() != null)
		{
			return 1 + getDepth(tag.getParent());
		}
		else
		{
			return 0;
		}
	}

	@Override
	public String toString()
	{
		return toString(true);
	}

	public String toString(boolean showChildren)
	{
		StringBuilder builder = new StringBuilder();

		int myDepth = getDepth(this);

		for (int i = 0; i < myDepth; i++)
		{
			builder.append(INDENT);
		}

		builder.append(C_OPEN_ANGLE).append(name);
		
		Map<String,String> attrs = getAttributes();

		if (attrs.size() > 0)
		{
			for (Map.Entry<String, String> entry : attrs.entrySet())
			{
				builder.append(C_SPACE).append(entry.getKey()).append(C_EQUALS).append(C_DOUBLE_QUOTE);
				builder.append(entry.getValue()).append(C_DOUBLE_QUOTE);
			}
		}

		if (selfClosing)
		{
			builder.append(C_SLASH).append(C_CLOSE_ANGLE).append(C_NEWLINE);
		}
		else
		{
			if (showChildren && children.size() > 0)
			{
				builder.append(C_CLOSE_ANGLE).append(C_NEWLINE);

				for (Tag child : children)
				{
					builder.append(child.toString());
				}
			}
			else
			{
				builder.append(C_CLOSE_ANGLE).append(C_NEWLINE);

				if (textContent != null)
				{
					for (int i = 0; i < myDepth; i++)
					{
						builder.append(INDENT);
					}

					builder.append(textContent).append(C_NEWLINE);
				}
			}

			for (int i = 0; i < myDepth; i++)
			{
				builder.append(INDENT);
			}

			builder.append(C_OPEN_ANGLE).append(C_SLASH);
			builder.append(name).append(C_CLOSE_ANGLE).append(C_NEWLINE);
		}

		return builder.toString();
	}

    @Override
    public boolean equals(Object o) {
        if (this == o)
		{
			return true;
		}
        if (o == null || getClass() != o.getClass())
		{
			return false;
		}

        Tag tag = (Tag) o;

        if (selfClosing != tag.selfClosing)
		{
			return false;
		}
        if (attributeString != null ? !attributeString.equals(tag.attributeString) : tag.attributeString != null)
		{
			return false;
		}
        if (children != null ? !children.equals(tag.children) : tag.children != null)
		{
			return false;
		}
        if (name != null ? !name.equals(tag.name) : tag.name != null)
		{
			return false;
		}
        if (parent != null ? !parent.equals(tag.parent) : tag.parent != null)
		{
			return false;
		}
        if (textContent != null ? !textContent.equals(tag.textContent) : tag.textContent != null)
		{
			return false;
		}

        return true;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + (attributeString != null ? attributeString.hashCode() : 0);
        result = 31 * result + (parent != null ? parent.hashCode() : 0);
        result = 31 * result + (selfClosing ? 1 : 0);
        result = 31 * result + (textContent != null ? textContent.hashCode() : 0);
        return result;
    }

	public boolean isFragment()
	{
		return isFragment;
	}

	public void setFragment(boolean isFragment)
	{
		this.isFragment = isFragment;
	}
}
