/*
Copyright 2005-2013 Samuel Gesche

This file is part of ArcEnCiel.

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

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

You should have received a copy of the GNU General Public License
along with ArcEnCiel.  If not, see <http://www.gnu.org/licenses/>.
*/

package ihm.townto;

import data.ListeSynonymes;

import java.util.Vector;
import java.util.Set;
import java.util.HashSet;
import ihm.Charte;

/**
 * @author Samuel GESCHE
 * @version 4.0.1
 */
public class MultiPVGraph extends Graph {

    /**
     * Creates a graph with no nodes nor links.
     * @since Hippodamos (v3.0)
     */
    public MultiPVGraph() {
        super();
        setActive(new ListeSynonymes("", ""), "");
    }

    /**
     * Sets the active node to have the specified name.
     * @param concept the name of the node at the center of the graph.
     * @param description the description of the node at the center of the graph.
     * @since Hippodamos (v3.0)
     */
    public void setActive(ListeSynonymes concept, String description) {
        super.setActive(concept.toString(), description);
        activeConcept = new MultiPVNode(concept, description, 0);
    }

    /**
     * Returns the active concept list in this graph.
     * @return the active concept list in this graph.
     * @since Hippodamos (v3.0)
     */
    public ListeSynonymes getActiveConceptList() {
        return ((MultiPVNode)activeConcept).getListe();
    }


    /**
     * Tries to add a link to the graph. The procedure for adding a link is as
     * follows :
     * <ul><li> try to assign a node to the specified names.</li>
     * <li> If a node cannot be found, try to create a new one. This operation
     *      will success or fail following these two rules :
     * <ul><li> There cannot be more than getMaxNumberofNodes() nodes in the
     *          second crown.</li>
     * <li> All nodes of the first crown (that is, nodes
     *      that are connected to the active one) can be entered.</li></ul></li>
     * <li> If the nodes finally exist, add the link between them.</li></ul>
     * Similar to addLink(linkFrom, 0, linkTo, 0, linkName, linkType).
     * @param linkFrom the name of the node at the origin of the link.
     * @param descFrom the description of the node at the origin of the link.
     * @param linkTo the name of the node at the end of the link
     * @param descTo the description of the node at the end of the link
     * @param linkName the name of the link (for instance "is in"
     * @param linkType the special constraints that will be applied to the link
     * (unsupported for now)
     * @throws LinkFailureException if the link has not be added.
     * @since Hippodamos (v3.0)
     */
    public void addLink(ListeSynonymes linkFrom, String descFrom, ListeSynonymes linkTo, String descTo/*,
                        ListeSynonymes linkName, ListeSynonymes linkType*/) throws LinkFailureException {
        addLink(linkFrom, descFrom, 0, linkTo, descTo, 0/*, linkName, linkType*/);
    }

    /**
     * Tries to add a link to the graph. The procedure for adding a link is as
     * follows :
     * <ul><li> try to assign a node to the specified names.</li>
     * <li> If a node cannot be found, try to create a new one. This operation
     *      will success or fail following these two rules :
     * <ul><li> There cannot be more than getMaxNumberofNodes() nodes in the
     *          second crown.</li>
     * <li> All nodes of the first crown (that is, nodes
     *      that are connected to the active one) can be entered.</li></ul></li>
     * <li> If the nodes finally exist, add the link between them.</li></ul>
     * @param linkFrom the name of the node at the origin of the link.
     * @param descFrom the description of the node at the origin of the link.
     * @param countFrom the total number of relations using the first concept
     * (including this one).
     * @param linkTo the name of the node at the end of the link
     * @param descTo the description of the node at the end of the link
     * @param countTo the total number of relations using the second concept
     * (including this one).
     * @param linkName the name of the link (for instance "is in"
     * @param linkType the special constraints that will be applied to the link
     * (unsupported for now)
     * @throws LinkFailureException if the link has not be added.
     * @since Hippodamos (v3.0)
     */
    public void addLink(ListeSynonymes linkFrom, String descFrom, int countFrom,
                        ListeSynonymes linkTo, String descTo,
                        int countTo) throws
            LinkFailureException {

        MultiPVNode nodeFrom = new MultiPVNode(
            new ListeSynonymes("", ""), "", -1);
        MultiPVNode nodeTo = new MultiPVNode(
            new ListeSynonymes("", ""), "", -1);

        // Recherche d'ids connus (un nom = un id)
        if (linkFrom.equals(((MultiPVNode)activeConcept).getListe())) {
            nodeFrom = (MultiPVNode)activeConcept; // l'id du concept actif est 0
        }
        if (linkTo.equals(((MultiPVNode)activeConcept).getListe())) {
            nodeTo = (MultiPVNode)activeConcept;
        }
        for (int i = 0; i < firstCrown.size(); i++) {
            MultiPVNode s = (MultiPVNode) (firstCrown.elementAt(i));
            if (linkFrom.equals(s.getListe())) {
                nodeFrom = s;
            }
            if (linkTo.equals(s.getListe())) {
                nodeTo = s;
            }
        }
        for (int i = 0; i < secondCrown.size(); i++) {
            MultiPVNode s = (MultiPVNode) (secondCrown.elementAt(i));
            if (linkFrom.equals(s.getListe())) {
                nodeFrom = s;
            }
            if (linkTo.equals(s.getListe())) {
                nodeTo = s;
            }
        }

        // verifying current ids and assigning new
        if (nodeFrom.getId() == -1 && nodeTo.getId() == -1) {
            throw new LinkFailureException(
                    Charte.getMessage("Graph_Error_NoConcept") + " " + linkFrom +
                    " " + Charte.getMessage("Graph_Error_NoConcept2") + " " +
                    linkTo + ".");
        }
        if (nodeFrom.getId() == nodeTo.getId()) {
            throw new LinkFailureException(
                    Charte.getMessage("Graph_Error_SameConcept") + " " +
                    linkFrom + " " +
                    Charte.getMessage("Graph_Error_SameConcept2") + " " +
                    linkTo + ".");
        }

        int nFrom = nodeFrom.getId();
        int nTo = nodeTo.getId();
        if (nFrom == -1) {
            if (nTo <= 9) {
                nFrom = maxIdCrown1 * 10 + nTo;
                maxIdCrown1++;
                nodeFrom = new MultiPVNode(linkFrom, descFrom, nFrom, countFrom);
                addToFirstCrown(nodeFrom);
            }
            if (nTo >= 10 && nTo <= 999) {
                nFrom = maxIdCrown2 * 1000 + nTo;
                maxIdCrown2++;
                nodeFrom = new MultiPVNode(linkFrom, descFrom, nFrom, countFrom);
                addToSecondCrown(nodeFrom);
            }
            if (nTo >= 1000 && nTo <= 99999) {
                    // on ne fait rien : pas de troisième couronne
                    throw new LinkFailureException(
                            Charte.getMessage("Graph_Error_ThirdCrown"));
            }
        }
        if (nTo == -1) {
            if (nFrom <= 9) {
                nTo = maxIdCrown1 * 10 + nFrom;
                maxIdCrown1++;
                nodeTo = new MultiPVNode(linkTo, descTo, nTo, countTo);
                addToFirstCrown(nodeTo);
            }
            if (nFrom >= 10 && nFrom <= 999) {
                nTo = maxIdCrown2 * 1000 + nFrom;
                maxIdCrown2++;
                nodeTo = new MultiPVNode(linkTo, descTo, nTo, countTo);
                addToSecondCrown(nodeTo);
            }
            if (nFrom >= 1000 && nFrom <= 99999) {
                    // on ne fait rien : pas de troisième couronne
                    throw new LinkFailureException(
                            Charte.getMessage("Graph_Error_ThirdCrown"));
            }
        }

        if(nFrom != -1 && nTo != -1){
            if(nFrom <= 9 && nTo >= 1000){
                System.err.println(nodeTo.name);
                for(int i=0; i<links.size(); i++){
                    Link l = (Link)(links.elementAt(i));
                    if(l.idFrom==nodeTo.id){
                        l.idFrom = maxIdCrown1 * 10;
                    }
                    if(l.idTo==nodeTo.id){
                        l.idTo = maxIdCrown1 * 10;
                    }
                }
                nodeTo.id = maxIdCrown1 * 10;
                maxIdCrown1++;
                secondCrown.remove(nodeTo);
                addToFirstCrown(nodeTo);
            }
            if(nTo <= 9 && nFrom >= 1000){
                System.err.println(nodeFrom.name);
                for(int i=0; i<links.size(); i++){
                    Link l = (Link)(links.elementAt(i));
                    if(l.idFrom==nodeFrom.id){
                        l.idFrom = maxIdCrown1 * 10;
                    }
                    if(l.idTo==nodeFrom.id){
                        l.idTo = maxIdCrown1 * 10;
                    }
                }
                nodeFrom.id = maxIdCrown1 * 10;
                maxIdCrown1++;
                secondCrown.remove(nodeFrom);
                addToFirstCrown(nodeFrom);
            }
        }


        // creating the link
        addToLinks(new MultiPVLink(nodeFrom, nodeTo));
    }

    /**
     * Returns the active node.
     * @return a MultiPVGraphNode describing the active node.
     * @since Hippodamos (v3.0)
     */
    public MultiPVGraphNode getMultiPVActive() {
        return new MultiPVGraphNode((MultiPVNode)activeConcept);
    }

    /**
     * Returns the nodes of the first crown.
     * @return an array of MultiPVGraphNode describing the nodes of the first crown.
     * @since Hippodamos (v3.0)
     */
    public MultiPVGraphNode[] getMultiPVSortedFirstCrown() {
        MultiPVNode[] n = new MultiPVNode[firstCrown.size()];
        firstCrown.toArray(n);
        sortFirst(n);
        MultiPVGraphNode[] result = new MultiPVGraphNode[n.length];
        for (int i = 0; i < n.length; i++) {
            Set linkSet = new HashSet(); // avoids double links (if any)
            boolean ok = true;
            for (int j = 0; j < links.size(); j++) {
                MultiPVLink link = (MultiPVLink) (links.elementAt(j));
                if (link.getFrom() == n[i].getId() ||
                    link.getTo() == n[i].getId()) {
                    if (link.getFrom() == 0 || link.getTo() == 0) { // avoids double links
                        if (ok) {
                            ok = false;
                            linkSet.add(link);
                        }
                    } else {
                        linkSet.add(link);
                    }
                }
            }
            result[i] = new MultiPVGraphNode(n[i], linkSet.size());
        }
        return result;
    }

    /**
     * Returns the nodes of the second crown.
     * @return an array of MultiPVGraphNode describing the nodes of the second crown.
     * @since Hippodamos (v3.0)
     */
    public MultiPVGraphNode[] getMultiPVSortedSecondCrown() {
        MultiPVNode[] nc1 = new MultiPVNode[firstCrown.size()];
        MultiPVNode[] nc2 = new MultiPVNode[secondCrown.size()];
        firstCrown.toArray(nc1);
        secondCrown.toArray(nc2);
        sortFirst(nc1);
        nc2 = sortMultiPVSecond(nc1, nc2);
        MultiPVGraphNode[] result = new MultiPVGraphNode[nc2.length];
        for (int i = 0; i < nc2.length; i++) {
            Set linkSet = new HashSet(); // avoids double links (if any)
            boolean ok = true;
            for (int j = 0; j < links.size(); j++) {
                MultiPVLink link = (MultiPVLink) (links.elementAt(j));
                if (link.getFrom() == nc2[i].getId() ||
                    link.getTo() == nc2[i].getId()) {
                    if (link.getFrom() == 0 || link.getTo() == 0) { // avoids double links
                        if (ok) {
                            ok = false;
                            linkSet.add(link);
                        }
                    } else {
                        linkSet.add(link);
                    }
                }
            }
            result[i] = new MultiPVGraphNode(nc2[i], linkSet.size());
        }
        return result;
    }

    /**
     * Returns the links of the graph.
     * @return an array of MultiPVGraphLink describing the links.
     * @since Hippodamos (v3.0)
     */
    public MultiPVGraphLink[] getMultiPVLinks() {
        MultiPVLink[] l = new MultiPVLink[links.size()];
        links.toArray(l);
        MultiPVGraphLink[] result = new MultiPVGraphLink[links.size()];
        for (int i = 0; i < result.length; i++) {
            result[i] = new MultiPVGraphLink(l[i]);
        }
        return result;
    }

    /**
     * Sorts the nodes of the second crown by node of the first crown they are
     * linked with.
     * Basically the same as the sortSecond() method in Graph, with MultiPVNode
     * as output.
     * @param sortedFirstNodes an array containing the first crown nodes, sorted.
     * @param secondNodes the second crown nodes.
     * @return an array containing the sorted second crown nodes.
     * @since Hippodamos (v3.0)
     */
    protected static MultiPVNode[] sortMultiPVSecond(MultiPVNode[] sortedFirstNodes,
                                     MultiPVNode[] secondNodes) {
        Vector result = new Vector();
        for (int i = 0; i < sortedFirstNodes.length; i++) {
            int idToFind = sortedFirstNodes[i].getId();
            for (int j = 0; j < secondNodes.length; j++) {
                int idFound = secondNodes[j].getId() % 1000;
                if (idFound == idToFind) {
                    result.addElement(secondNodes[j]);
                }
            }
        }
        MultiPVNode[] nres = new MultiPVNode[result.size()];
        result.toArray(nres);
        return nres;
    }

    class MultiPVGraphNode extends GraphNode {

        protected ListeSynonymes liste;

        /**
         * Creates a GraphNode from a Node.
         * @param node the Node from which to create the GraphNode.
         * @since Hippodamos (v3.0)
         */
        protected MultiPVGraphNode(MultiPVNode node) {
            this(node, 0);
        }

        /**
         * Creates a GraphNode from a Node.
         * @param node the Node from which to create the GraphNode.
         * @param drawnRelations the number of the relations involving the concept
         * of the node which are actually drawn on the graph.
         * @since Hippodamos (v3.0)
         */
        protected MultiPVGraphNode(MultiPVNode node, int drawnRelations) {
            super(node, drawnRelations);
            liste = node.getListe();
        }

        /**
         * Returns the synonyms list that will be displayed with the node.
         * @return the synonyms list that will be displayed with the node.
         * @since Hippodamos (v3.0)
         */
        public ListeSynonymes getListe() {
            return liste;
        }

    }

    class MultiPVGraphLink extends GraphLink {
        /*protected ListeSynonymes linkListe;*/

        protected MultiPVGraphLink(MultiPVLink link) {
            super(link);
            /*linkListe = link.getNameList();*/
        }

        /**
         * Returns the synonyms list that will be displayed with the link.
         * @return the synonyms list that will be displayed with the link.
         * @since Hippodamos (v3.0)
         */
        /*public ListeSynonymes getListe() {
            return linkListe;
        }*/
    }


    protected class MultiPVLink extends Link {
        /*private ListeSynonymes linkName;
        private ListeSynonymes linkType;*/

        /**
         * Creates a Link with the given attributes.
         * @param from the Node at the origin of the link.
         * @param to the Node at the end of the link.
         * @param name the name of the link.
         * @param type the type of the link.
         * @since Hippodamos (v3.0)
         */
        public MultiPVLink(MultiPVNode from, MultiPVNode to/*,
                           ListeSynonymes name, ListeSynonymes type*/) {
            super(from, to, "");
            /*linkName = name;
            linkType = type;*/
        }

        /**
         * Returns the name of this Link.
         * @return the name of this Link.
         * @since Hippodamos (v3.0)
         */
        /*public ListeSynonymes getNameList() {
            return linkName;
        }*/

        /**
         * Returns the type of this Link.
         * @return the type of this Link.
         * @since Hippodamos (v3.0)
         */
        /*public ListeSynonymes getTypeList() {
            return linkType;
        }*/
    }

    protected class MultiPVNode extends Node {
        protected ListeSynonymes liste;

        /**
         * Creates a Node with the specified attributes.
         * @param name the name of the Node.
         * @param description the description of the Node.
         * @param id the id of the Node.
         * @since Hippodamos (v3.0)
         */
        public MultiPVNode(ListeSynonymes name, String description, int id) {
            this(name, description, id, 0);
        }

        /**
         * Creates a Node with the specified attributes.
         * @param name the name of the Node.
         * @param description the description of the Node.
         * @param id the id of the Node.
         * @param relations the number of relations using this Node.
         * @since Hippodamos (v3.0)
         */
        public MultiPVNode(ListeSynonymes name, String description, int id,
                           int relations) {
            super(name.toString(), description, id, relations);
            this.liste = name;
        }

        /**
         * Returns the synonyms list of this Node.
         * @return the synonyms list of this Node.
         * @since Hippodamos (v3.0)
         */
        public ListeSynonymes getListe() {
            return liste;
        }
    }

}
