package de.renew.formalism.java;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import de.renew.simulatorontology.shadow.SyntaxException;

/**
 * Checks if a Channel is valid
 */
public class ChannelCheckNode {
    /**
     * The Name
     */
    public final String _name;
    /**
     * The arity
     */
    public final int _arity;
    private final int _hash;

    // 0: unchecked, 1: check active, 2: check completed, 3: start of cycle
    private int _checkState;
    private boolean _satisfiable;
    private Set<ChannelCheckNode> _invokableChannels;

    /**
     * Constructor creates HashSet to store invokable Channels
     * @param name the Name for the CheckNone
     * @param arity the arity
     */
    public ChannelCheckNode(String name, int arity) {
        this._name = name.intern();
        this._arity = arity;
        _hash = name.hashCode() + arity * 135;

        _checkState = 0;
        _satisfiable = false;
        _invokableChannels = new HashSet<ChannelCheckNode>();
    }

    /**
     * Set-Method
     */
    public void setSatisfiable() {
        _satisfiable = true;
    }

    /**
     * Method only calls recursive checkmethod
     * @throws SyntaxException in case the check detects an error
     */
    public void check() throws SyntaxException {
        String result = checkRecursively();
        if (result != null) {
            throw new SyntaxException(result);
        }
    }

    /**
     * Creates ChannelName in (*,*,*) with arity Amount of *
     * @return ChannelName as String
     */
    public String makeChannelName() {
        StringBuffer result = new StringBuffer(_name);
        result.append('(');
        for (int i = 1; i < _arity; i++) {
            result.append("*,");
        }
        if (_arity > 0) {
            result.append('*');
        }
        result.append(')');
        return result.toString();
    }

    /**
     * Recursive CheckMethod for channels
     * Iterates through all invokableChannels (stored in the HashMap).
     * Sets checkState field.
     * @return Null if check is positive, Error description as String if check is negativ.
     * @throws SyntaxException In case of error with description as text.
     */
    public String checkRecursively() throws SyntaxException {
        if (_checkState == 2) {
            return null;
        } else if (_checkState == 1) {
            _checkState = 3;
            return "This completes a cycle.";
        }

        _checkState = 1;

        Iterator<ChannelCheckNode> iterator = _invokableChannels.iterator();
        if (iterator.hasNext()) {
            do {
                ChannelCheckNode node = iterator.next();

                if (equals(node)) {
                    throw new SyntaxException(
                        "Channel " + makeChannelName() + " can invoke itself.");
                }

                String result = node.checkRecursively();
                if (result != null) {
                    if (_name.equals("")) {
                        result = "Channel " + node.makeChannelName()
                            + " can be invoked spontaneously.\n" + result;
                    } else {
                        result = "Channel " + makeChannelName() + " can invoke channel "
                            + node.makeChannelName() + ".\n" + result;
                    }
                    if (_checkState == 3) {
                        throw new SyntaxException(result);
                    } else {
                        return result;
                    }
                }
            } while (iterator.hasNext());
        } else if (!_satisfiable) {
            throw new SyntaxException("Channel " + makeChannelName() + " cannot be satisfied.");
        }

        _checkState = 2;
        return null;
    }

    /**
     * Adds a node to the channels HashMap
     * @param node the node to add
     */
    public void addInvokableChannel(ChannelCheckNode node) {
        _invokableChannels.add(node);
    }

    @Override
    public int hashCode() {
        return _hash;
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof ChannelCheckNode)) {
            return false;
        }
        ChannelCheckNode that = (ChannelCheckNode) o;
        return _name.equals(that._name) && _arity == that._arity;
    }
}