/*
 * Forest.java
 *
 * Autor: Piotr Achinger <piotr.achinger at gmail.com>
 */
package dendrarium.trees;

import dendrarium.trees.terminals.TerminalGraph;
import java.io.Serializable;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * Obiektowa reprezentacja forest.xml.
 *
 * Zarzadza zbiorem wierzcholkow (branie korzenia lub node'a po id),
 * metadanymi (także dotyczacymi odpowiedzi dendrologa), reprezentacja
 * tekstowa zdania lub fragmentu i grafem terminali.
 *
 * Konwersja z i do XML zajmuja sie TreeXMLParser oraz TreeXMLExporter.
 *
 * Struktura lasu jako zbioru powiazanych wierzcholkow opisana w
 * klasie Node.
 *
 * UWAGA. Jezeli w lesie nie ma drzew (parser nie stworzyl analiz),
 * wowczas atrybut noTrees jest ustawiany na true i tworzony jest
 * zastepczy, sztuczny korzen drzewa bez dzieci.
 *
 * @author Piotr Achinger <piotr.achinger at gmail.com>
 */
public class Forest implements Serializable {

    /* --- METADANE --- */
    /**
     * Id zdania.
     *
     * Unikalne w całym systemie.
     */
    private String sent_id;

    /**
     * Identyfikator wersji gramatyki.
     *
     * Większa liczba to późniejsza wersja.
     */
    private int grammar_no;

    /**
     * Tresc zdania.
     * 
     * Podana verbatim w elemencie text w XML.
     */
    private String text;

    /* METADANE NIECIEKAWE Z PUNKTU WIDZENIA SYSTEMU: */
    private long trees;

    private long nodes;

    private long inferences;

    private double cputime;

    private String startnodeLabel;

    private long startnodeFrom;

    private long startnodeTo;

    /* --- STRUKTURA LASU --- */
    /**
     * Mapa id wierzcholka -> wierzcholek
     */
    private Map<Integer, Node> nodeMap = new HashMap<Integer, Node>();

    /**
     * Korzen lasu.
     */
    private NonterminalNode root;

    /**
     * Czy las jest pusty.
     *
     * Jezeli w lesie nie ma drzew (parser nie stworzyl analiz),
     * wowczas atrybut empty jest ustawiany na true i tworzony jest
     * zastepczy, sztuczny korzen drzewa bez dzieci.
     * 
     */
    private boolean empty = false;

    /* --- GRAF TERMINALI I REPREZENTACJA TEKSTOWA --- */
    private TerminalGraph terminalGraph = new TerminalGraph();

    /* --- ODPOWIEDZ DENDROLOGA --- */
    /**
     * Dane dotyczace odpowiedzi dendrologa.
     *
     * Wartosc null oznacza brak odpowiedzi.
     */
    private AnswerData answerData = null;

    /**
     * Dane dotyczace odpowiedzi pozostalych dendrologow.
     *
     * Przydatne obecnie tylko przy eksporcie.
     */
    private AnswerData extraAnswerData1 = null;

    private AnswerData extraAnswerData2 = null;

    /* === KONSTRUKCJA LASU ===
     *
     * Prawidlowo, las powinien byc tworzony w nastepujacy sposob:
     * 1) Forest forest = new Forest(... metadane ...);
     * 2) forest.addNode(node_1); ...; forest.addNode(node_k);
     *    (dodawane wezly powinny byc wczesniej ze soba powiazane)
     * 3) forest.postConstruct();
     */
    /**
     * Konstruktor przyjmujacy wylacznie metadane.
     *
     * Reszta struktury tworzona jest za pomoca addNode(node);
     */
    public Forest(String sent_id, String text, long trees, long nodes,
            long inferences, double cputime, int grammar_no,
            String startnodeLabel, long startnodeFrom, long startnodeTo) {
        this.sent_id = sent_id;
        this.text = text;
        this.trees = trees;
        this.nodes = nodes;
        this.inferences = inferences;
        this.cputime = cputime;
        this.grammar_no = grammar_no;
        this.startnodeLabel = startnodeLabel;
        this.startnodeFrom = startnodeFrom;
        this.startnodeTo = startnodeTo;
    }

    /**
     * Dodaje nowy wezel do lasu i grafu terminali (jezeli jest terminalem).
     *
     * Wezel musi byc juz powiazany z innymi, bo jest tylko dodawany
     * do mapy id->wezel.
     */
    public void addNode(Node node) {
        nodeMap.put(node.getId(), node);
        if (node instanceof TerminalNode) {
            terminalGraph.add((TerminalNode) node);
        }
    }

    /**
     * Metoda wywolywana po ostatnim addNode.
     *
     * Tworzy dane o najkrotszych sciezkach itp w grafie terminali.
     */
    public void postConstruct() {
        terminalGraph.postConstruct();
    }

    /**
     * Pobranie zbioru identyfikatorow wybranych wierzcholkow.
     *
     * Wybrane wierzcholki to najmniejszy zbior zawierajacy korzen
     * oraz w.getChosenChildren().getChildren() (zestaw wybranych dzieci
     * danego wierzcholka w) wraz z kazdym wierzcholkiem w.
     */
    public SortedSet<Integer> chosenNodes() {
        SortedSet<Integer> ret = new TreeSet<Integer>();

        addChosenNodes(ret, root);

        return ret;
    }

    private void addChosenNodes(SortedSet<Integer> set, Node node) {
        set.add(node.getId());

        if (node instanceof NonterminalNode) {
            NonterminalNode nt = (NonterminalNode) node;
            if (nt.getChosenChildren() != null) {
                for (Node child : nt.getChosenChildren().getChildren()) {
                    addChosenNodes(set, child);
                }
            }
        }
    }

    /**
     * Zwraca wezel o danym ID.
     */
    public Node nodeById(int id) {
        return nodeMap.get(id);
    }

    /* === METODY ODPOWIEDZIALNE ZA REPREZENTACJE TEKSTOWA FRAGMENTOW ZDANIA === */
    /**
     * Tekstowa reprezentacja fragmentu zdania pomiedzy dwoma wezlami
     * w grafie terminali.
     *
     * @param from identyfikator wezla poczatkowego w grafie terminali
     * @param to identyfikator wezla koncowego w grafie terminali
     * @return konkatenacja oznaczen krawedzi z najkrotszej sciezki from->to
     */
    public String wordRange(int from, int to) {
        return terminalListToString(terminalGraph.getShortestPath(from, to));
    }

    /**
     * Tekstowa reprezentacja fragmentu zdania od danego wezla w grafie
     * terminali.
     *
     * @param from identyfikator wezla poczatkowego w grafie terminali
     * @return konkatenacja oznaczen krawedzi z najkrotszej sciezki from->koniec
     */
    public String wordRangeFrom(int from) {
        return terminalListToString(terminalGraph.getShortestPathFrom(from));
    }

    /**
     * Tekstowa reprezentacja fragmentu zdania do danego wezla w grafie
     * terminali.
     *
     * @param to identyfikator wezla koncowego w grafie terminali
     * @return konkatenacja oznaczen krawedzi z najkrotszej sciezki poczatek->to
     */
    public String wordRangeTo(int to) {
        return terminalListToString(terminalGraph.getShortestPathTo(to));
    }

    /**
     * Listowa reprezentacja fragmentu zdania pomiedzy dwoma wezlami
     * w grafie terminali. Zwraca liste slow/oznaczen krawedzi w grafie
     * terminali.
     *
     * @param from identyfikator wezla poczatkowego w grafie terminali
     * @param to identyfikator wezla koncowego w grafie terminali
     * @return lista oznaczen krawedzi z najkrotszej sciezki from->to
     */
    public List<String> wordRangeAsList(int from, int to) {
        List<TerminalNode> nodeList = terminalGraph.getShortestPath(from, to);
        List<String> ret = new LinkedList();

        for (TerminalNode node : nodeList) {
            ret.add(node.getText());
        }

        return ret;
    }

    private String terminalListToString(List<TerminalNode> nodeList) {
        StringBuilder sb = new StringBuilder();

        for (TerminalNode node : nodeList) {
            sb.append(node.getText());
        }

        return sb.toString().trim();
    }

    /* === G & S === */
    public boolean isEmpty() {
        return empty;
    }

    public void setEmpty(boolean empty) {
        this.empty = empty;
    }

    public double getCputime() {
        return cputime;
    }

    public long getInferences() {
        return inferences;
    }

    public long getNodes() {
        return nodes;
    }

    public String getStartnodeLabel() {
        return startnodeLabel;
    }

    public long getStartnodeFrom() {
        return startnodeFrom;
    }

    public long getStartnodeTo() {
        return startnodeTo;
    }

    public NonterminalNode getRoot() {
        return root;
    }

    public String getSent_id() {
        return sent_id;
    }

    public int getGrammar_no() {
        return grammar_no;
    }

    public String getText() {
        return text;
    }

    public long getTrees() {
        return trees;
    }

    public void setRoot(NonterminalNode root) {
        this.root = root;
    }

    public AnswerData getAnswerData() {
        return answerData;
    }

    public void setAnswerData(AnswerData answerData) {
        this.answerData = answerData;
    }

    public List<Node> getNodeList() {
        return new LinkedList(nodeMap.values());
    }

    public Map<Integer, Node> getNodeMap() {
        return nodeMap;
    }

    public AnswerData getExtraAnswerData1() {
        return extraAnswerData1;
    }

    public void setExtraAnswerData1(AnswerData extraAnswerData1) {
        this.extraAnswerData1 = extraAnswerData1;
    }

    public AnswerData getExtraAnswerData2() {
        return extraAnswerData2;
    }

    public void setExtraAnswerData2(AnswerData extraAnswerData2) {
        this.extraAnswerData2 = extraAnswerData2;
    }

    @Override
    public String toString() {
        return "Forest[" + this.text + "\n" + this.nodeMap + "]";
    }
}
