/* UtilLib - Copyright (c) 2013-2018 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.mapeadores.util.logicaloperation;

import java.util.AbstractList;
import net.mapeadores.util.exceptions.SwitchException;


/**
 *
 * @author Vincent Calame
 */
public final class LogicalOperationUtils {

    private LogicalOperationUtils() {
    }

    public static SubOperand toSubOperand(Operand[] array, short operator) {
        if (array.length < 2) {
            throw new IllegalArgumentException("array.length < 2");
        }
        return new ArraySubOperand(array, operator);
    }

    public static SimpleOperand toSimpleOperand(boolean acceptMode, String operandString, String scope, String content) {
        return new InternalSimpleOperand(acceptMode, operandString, scope, content);
    }

    public static String toInformatiqueString(Operand operand) {
        StringBuilder buf = new StringBuilder();
        appendInformatiqueOperand(buf, operand, true);
        return buf.toString();
    }

    private static void appendInformatiqueOperand(StringBuilder buf, Operand operand, boolean root) {
        if (operand instanceof SimpleOperand) {
            SimpleOperand simpleOperand = (SimpleOperand) operand;
            if (!simpleOperand.isAcceptMode()) {
                buf.append('!');
            }
            String operandString = ((SimpleOperand) operand).getOperandString();
            int length = operandString.length();
            for (int i = 0; i < length; i++) {
                char carac = operandString.charAt(i);
                switch (carac) {
                    case ')':
                    case '(':
                    case '\\':
                    case '&':
                    case '|':
                        buf.append('\\');
                    default:
                        buf.append(carac);
                }
            }
        } else if (operand instanceof SubOperand) {
            SubOperand subOperand = (SubOperand) operand;
            String separator = getSeparator(subOperand.getOperator());
            if (!root) {
                buf.append('(');
            }
            int operandLength = subOperand.size();
            for (int i = 0; i < operandLength; i++) {
                if (i > 0) {
                    buf.append(separator);
                }
                appendInformatiqueOperand(buf, subOperand.get(i), false);
            }
            if (!root) {
                buf.append(')');
            }
        } else {
            buf.append(operand.toString());
        }
    }

    public static String toEnsembleString(Operand operand) {
        StringBuilder buf = new StringBuilder();
        appendEnsembleOperand(buf, operand, true);
        return buf.toString();
    }

    public static short inverseOperator(short operator) {
        if (operator == LogicalOperationConstants.UNION_OPERATOR) {
            return LogicalOperationConstants.INTERSECTION_OPERATOR;
        } else {
            return LogicalOperationConstants.UNION_OPERATOR;
        }
    }

    private static void appendEnsembleOperand(StringBuilder buf, Operand operand, boolean root) {
        if (operand instanceof SimpleOperand) {
            SimpleOperand simpleOperand = (SimpleOperand) operand;
            if (!simpleOperand.isAcceptMode()) {
                buf.append('\u00AC');
            }
            buf.append(((SimpleOperand) operand).getOperandString());
        } else if (operand instanceof SubOperand) {
            SubOperand subOperand = (SubOperand) operand;
            buf.append(getOperator(subOperand.getOperator()));
            buf.append('(');
            int operandLength = subOperand.size();
            for (int i = 0; i < operandLength; i++) {
                if (i > 0) {
                    buf.append(", ");
                }
                appendEnsembleOperand(buf, subOperand.get(i), false);
            }
            buf.append(')');
        } else {
            String s = operand.toString();
            int length = s.length();
            if (length > 0) {
                if (s.charAt(0) == '!') {
                    buf.append('\u00AC');
                    buf.append(s, 1, length);
                } else {
                    buf.append(s);
                }
            }
        }
    }

    public static String getSeparator(short operator) {
        switch (operator) {
            case LogicalOperationConstants.INTERSECTION_OPERATOR:
                return " && ";
            case LogicalOperationConstants.UNION_OPERATOR:
                return " || ";
            default:
                throw new SwitchException("Unknown operator = " + operator);
        }
    }

    public static String getOperator(short operator) {
        switch (operator) {
            case LogicalOperationConstants.INTERSECTION_OPERATOR:
                return "\u22C2";
            case LogicalOperationConstants.UNION_OPERATOR:
                return "\u22C3";
            default:
                throw new SwitchException("Unknown operator = " + operator);
        }
    }


    private static class ArraySubOperand extends AbstractList<Operand> implements SubOperand {

        private final Operand[] array;
        private final short operator;

        private ArraySubOperand(Operand[] array, short operator) {
            this.array = array;
            this.operator = operator;
        }

        @Override
        public short getOperator() {
            return operator;
        }

        @Override
        public int size() {
            return array.length;
        }

        @Override
        public Operand get(int i) {
            return array[i];
        }

    }


    private static class InternalSimpleOperand implements SimpleOperand {

        private final boolean acceptMode;
        private final String operandString;
        private final String scope;
        private final String body;

        private InternalSimpleOperand(boolean acceptMode, String operandString, String scope, String body) {
            this.acceptMode = acceptMode;
            this.operandString = operandString;
            this.scope = scope;
            this.body = body;
        }

        @Override
        public boolean isAcceptMode() {
            return acceptMode;
        }

        @Override
        public String getOperandString() {
            return operandString;
        }

        @Override
        public String getScope() {
            return scope;
        }

        @Override
        public String getBody() {
            return body;
        }

    }

}
