/* 
-*- coding: latin-1 -*-

This file is part of RefactorErl.

RefactorErl is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

RefactorErl 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with RefactorErl.  If not, see <http://plc.inf.elte.hu/erlang/>.

The Original Code is RefactorErl.

The Initial Developer of the Original Code is Eötvös Loránd University.
Portions created  by Eötvös Loránd University and ELTE-Soft Ltd.
are Copyright 2007-2025 Eötvös Loránd University, ELTE-Soft Ltd.
and Ericsson Hungary. All Rights Reserved.
*/

package com.refactorerl.ui.logic.investigations;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangPid;
import com.ericsson.otp.erlang.OtpErlangString;
import com.ericsson.otp.erlang.OtpErlangTuple;
import com.refactorerl.ui.common.Pair;


/**
 * This class represents a RefactorErl investigation. It also provides methods
 * for manipulating the represented investigation.
 * 
 * @author Daniel Lukacs, 2014 ELTE IK
 *
 */
public class Investigation implements Cloneable {
	private String name;

	private OtpErlangTuple hash;
	private List<String> users = new ArrayList<>();

	private Map<OtpErlangPid, InvestigationNode> pids = new HashMap<>();

	public Investigation(String name, String userName, OtpErlangTuple hash) {
		this.name = name;
		this.hash = hash;

		if (userName != null)
			users.add(userName);
	}

	public Investigation(String name, String userName) {
		this(name, userName, new OtpErlangTuple(new OtpErlangString(""))); //$NON-NLS-1$
		// the tuple should be overwritten on save anyway
		users.add(userName);
	}

	protected Investigation(String name, OtpErlangTuple hash) {
		this(name, null, hash);
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Investigation [name=" + name + ", hash=" + hash + ", users=" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				+ users + "]\n"); //$NON-NLS-1$

		printTree(sb);

		return sb.toString();

	}

	protected void printTree(StringBuilder sb) {
		InvestigationNode root = getRoot();

		Stack<Pair<InvestigationNode, Integer>> stack = new Stack<>();
		stack.push(new Pair<InvestigationNode, Integer>(root, 0));

		while (!stack.empty()) {
			Pair<InvestigationNode, Integer> top = stack.pop();
			InvestigationNode node = top.key;
			int depth = top.value;
			for (int i = 0; i < depth; i++) {
				sb.append("\t"); //$NON-NLS-1$
			}
			// sb.append(node.getPid() + "/" + node.getChildPids() + "\n");
			sb.append(node + "\n"); //$NON-NLS-1$
			for (InvestigationNode ch : node.getChildren()) {
				stack.push(new Pair<InvestigationNode, Integer>(ch, depth + 1));
			}
		}
	}

	public InvestigationNode getRoot() {
		InvestigationNode root = null;
		for (InvestigationNode node : pids.values()) {
			if (node.getParent() == null) {
				root = node;
				break;
			}
		}
		if (root == null)
			return null;
		return root;
	}

	public void addUsers(Collection<String> users) {
		this.users.addAll(users);
	}

	public void addNode(InvestigationNode node) {
		pids.put(node.getPid(), node);

		final InvestigationNode parent = pids.get(node.getParentPid());
		if (parent != null) {
			node.setParent(parent);
			parent.addChild(node);
			if (!parent.childPids.contains(node.pid))
				parent.childPids.add(node.pid);
		}
		for (OtpErlangPid cpid : node.getChildPids()) {
			final InvestigationNode child = pids.get(cpid);
			if (child != null) {
				node.addChild(child);
				child.setParent(node);
				child.parentPid = node.pid;
			}
		}
	}

	public void removeNode(InvestigationNode start) {
		Stack<InvestigationNode> stack = new Stack<>();
		stack.push(start);
		while (!stack.isEmpty()) {
			InvestigationNode node = stack.pop();
			pids.remove(node.getPid());
			final InvestigationNode parent = pids.get(node.getParentPid());
			if (parent != null) {
				node.setParent(null);
				node.parentPid = null;
				parent.removeChild(node);
				parent.childPids.remove(node.pid);
			}

			for (OtpErlangPid cpid : node.getChildPids()) {
				final InvestigationNode child = pids.get(cpid);
				if (child != null) {
					node.removeChild(child);
					child.setParent(null);
					child.parentPid = null;

					stack.add(child);
				}
			}
			node.childPids = new ArrayList<>();

		}

	}

	/** New pids can be requested by generatePid() method of ReferlProxy. */
	public InvestigationNode addMemo(OtpErlangPid newPid, InvestigationNode parent, String text,
			Pair<Integer, Integer> screenPosition) {
		InvestigationNode memo = new InvestigationNode(newPid, parent.getPid(), new ArrayList<OtpErlangPid>(),
				"Memo", true, true, //$NON-NLS-1$
				new OtpErlangAtom("memo"), text, "Memo", null, 0, null, 0, //$NON-NLS-1$ //$NON-NLS-2$
				null, screenPosition != null ? screenPosition : new Pair<Integer, Integer>(0, 0));

		parent.childPids.add(newPid);

		addNode(memo);
		return memo;
	}

	
	// NOTE clones are actually not needed for this application (the editor
	// creates a clone anyway)
	public static Investigation createInvestigationWithNode(String name, String userName, InvestigationNode node) {
		Investigation inv = new Investigation(name, userName);
		InvestigationNode newNode;
		try {

			newNode = (InvestigationNode) node.clone(); 

			newNode.childPids = new ArrayList<>();
			newNode.parentPid = null;
			newNode.children = new ArrayList<>();
			newNode.parent = null;

			inv.addNode(newNode);

			return inv;

		} catch (CloneNotSupportedException e) {
			return null; // clone IS supported
		}

	}

	// NOTE clones are actually not needed for this application (the editor
	// creates a clone anyway)
	public static Investigation createInvestigationWithSubtree(String name, String userName, InvestigationNode root) {
		try {

			Investigation inv = new Investigation(name, userName);
			InvestigationNode newRoot;

			newRoot = (InvestigationNode) root.clone();
			newRoot.parentPid = null;
			newRoot.parent = null;

			final Stack<InvestigationNode> stack = new Stack<>();
			stack.push(newRoot);
			while (!stack.isEmpty()) {
				InvestigationNode newNode = stack.pop();

				for (int i = newNode.children.size() - 1; i >= 0; --i) {
					stack.push((InvestigationNode) newNode.children.get(i).clone());
				}
				// for (InvestigationNode child : newNode.children) {
				// stack.push((InvestigationNode) child
				// .clone());
				// }
				newNode.children = new ArrayList<>();
				inv.addNode(newNode);

			}
			StringBuilder sb = new StringBuilder();
			inv.printTree(sb);
			System.out.println(sb.toString());
			return inv;

		} catch (CloneNotSupportedException e) {
			return null; // clone IS supported
		}

	}

	public Collection<InvestigationNode> getNodes() {
		return pids.values();
	}

	public OtpErlangTuple getHash() {

		return hash;
	}

	public void setHash(OtpErlangTuple hash) {
		this.hash = hash;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public List<String> getUsers() {
		return users;
	}

	// untested
	@Override
	public Object clone() throws CloneNotSupportedException {
		Investigation clone = new Investigation(name, hash);
		clone.addUsers(users);

		InvestigationNode root = getRoot();
		if (root == null)
			return clone;

		Stack<InvestigationNode> stack = new Stack<>();
		stack.push(root);

		while (!stack.empty()) {
			InvestigationNode node = stack.pop();
			InvestigationNode nodeClone = new InvestigationNode(node);
			clone.addNode(nodeClone);

			for (int i = node.getChildren().size() - 1; i >= 0; --i) {
				final InvestigationNode ch = node.getChildren().get(i);
				InvestigationNode childClone = new InvestigationNode(ch);
				stack.push(childClone);
			}

		}

		return clone;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((hash == null) ? 0 : hash.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + ((pids == null) ? 0 : pids.hashCode());
		result = prime * result + ((users == null) ? 0 : users.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Investigation other = (Investigation) obj;
		if (hash == null) {
			if (other.hash != null)
				return false;
		} else if (!hash.equals(other.hash))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (pids == null) {
			if (other.pids != null)
				return false;
		} else if (!pids.equals(other.pids))
			return false;
		if (users == null) {
			if (other.users != null)
				return false;
		} else if (!users.equals(other.users))
			return false;
		return true;
	}

}
