package jscl.text;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import jscl.NumeralBase;
import jscl.math.Generic;
import jscl.math.NumericWrapper;
import jscl.math.numeric.Real;
import jscl.text.msg.Messages;

public class DoubleParser implements Parser<NumericWrapper> {

    public static final Parser<NumericWrapper> parser = new DoubleParser();

    private static final List<Parser<Double>> parsers = Arrays.asList(
            Singularity.parser,
            FloatingPointLiteral.parser);

    private DoubleParser() {
    }

    @Nonnull
    public NumericWrapper parse(@Nonnull Parameters p, Generic previousSumElement) throws ParseException {
        final Parser<Double> multiTryParser = new MultiTryParser<Double>(new ArrayList<Parser<? extends Double>>(parsers));
        return new NumericWrapper(Real.valueOf(multiTryParser.parse(p, previousSumElement)));
    }
}

class Singularity implements Parser<Double> {

    public static final Parser<Double> parser = new Singularity();

    private Singularity() {
    }

    @Nonnull
    public Double parse(@Nonnull Parameters p, Generic previousSumElement) throws ParseException {
        int pos0 = p.position.intValue();

        double result = 0d;

        String s = Identifier.parser.parse(p, previousSumElement);
        if (s.equals("NaN")) {
            result = Double.NaN;
        } else if (s.equals("Infinity") || s.equals("∞")) {
            result = Double.POSITIVE_INFINITY;
        } else {
            ParserUtils.throwParseException(p, pos0, Messages.msg_10, "NaN", "∞");
        }

        return result;
    }
}

class FloatingPointLiteral implements Parser<Double> {

    public static final Parser<Double> parser = new FloatingPointLiteral();

    private FloatingPointLiteral() {
    }

    public Double parse(@Nonnull Parameters p, Generic previousSumElement) throws ParseException {
        int pos0 = p.position.intValue();

        final NumeralBase nb = NumeralBaseParser.parser.parse(p, previousSumElement);


        boolean digits = false;
        boolean point = false;
        boolean exponent = false;

        final Digits digitsParser = new Digits(nb);

        final StringBuilder result = new StringBuilder();
        try {
            result.append(digitsParser.parse(p, previousSumElement));
            digits = true;
        } catch (ParseException e) {
            p.exceptionsPool.release(e);
        }

        try {
            DecimalPoint.parser.parse(p, previousSumElement);
            result.append(".");
            point = true;
        } catch (ParseException e) {
            if (!digits) {
                p.position.setValue(pos0);
                throw e;
            } else {
                p.exceptionsPool.release(e);
            }
        }

        if (point && nb != NumeralBase.dec) {
            ParserUtils.throwParseException(p, pos0, Messages.msg_15);
        }

        try {
            result.append(digitsParser.parse(p, previousSumElement));
        } catch (ParseException e) {
            if (!digits) {
                p.position.setValue(pos0);
                throw e;
            } else {
                p.exceptionsPool.release(e);
            }
        }

        try {
            result.append(ExponentPart.parser.parse(p, previousSumElement));
            exponent = true;
        } catch (ParseException e) {
            if (!point) {
                p.position.setValue(pos0);
                throw e;
            } else {
                p.exceptionsPool.release(e);
            }
        }

        if (exponent && nb != NumeralBase.dec) {
            ParserUtils.throwParseException(p, pos0, Messages.msg_15);
        }

        final String doubleString = result.toString();
        try {
            return nb.toDouble(doubleString);
        } catch (NumberFormatException e) {
            throw p.exceptionsPool.obtain(p.position.intValue(), p.expression, Messages.msg_8, Collections.singletonList(doubleString));
        }
    }
}

class DecimalPoint implements Parser<Void> {

    public static final Parser<Void> parser = new DecimalPoint();

    private DecimalPoint() {
    }

    @Nullable
    public Void parse(@Nonnull Parameters p, Generic previousSumElement) throws ParseException {
        int pos0 = p.position.intValue();

        ParserUtils.skipWhitespaces(p);

        ParserUtils.tryToParse(p, pos0, '.');

        return null;
    }
}

class ExponentPart implements Parser<String> {

    public static final Parser<String> parser = new ExponentPart();

    private ExponentPart() {
    }

    @Nonnull
    public String parse(@Nonnull Parameters p, Generic previousSumElement) throws ParseException {
        int pos0 = p.position.intValue();

        ParserUtils.skipWhitespaces(p);

        final StringBuilder result;
        if (p.position.intValue() < p.expression.length() && (p.expression.charAt(p.position.intValue()) == 'e' || p.expression.charAt(p.position.intValue()) == 'E')) {
            result = new StringBuilder();
            result.append(p.expression.charAt(p.position.intValue()));
            p.position.increment();
        } else {
            throw ParserUtils.makeParseException(p, pos0, Messages.msg_10, 'e', 'E');
        }

        try {
            result.append(SignedInteger.parser.parse(p, previousSumElement));
        } catch (ParseException e) {
            p.position.setValue(pos0);
            throw e;
        }

        return result.toString();
    }
}

class SignedInteger implements Parser<String> {

    public static final Parser<String> parser = new SignedInteger();

    private SignedInteger() {
    }

    @Nonnull
    public String parse(@Nonnull Parameters p, Generic previousSumElement) throws ParseException {
        final int pos0 = p.position.intValue();


        ParserUtils.skipWhitespaces(p);

        final StringBuilder result = new StringBuilder();
        final int pos1 = p.position.intValue();
        if (pos1 < p.expression.length() && (p.expression.charAt(pos1) == '+' || MinusParser.isMinus(p.expression.charAt(pos1)))) {
            final char c = p.expression.charAt(pos1);
            p.position.increment();
            result.append(c);
        }

        try {
            result.append(IntegerParser.parser.parse(p, previousSumElement).intValue());
        } catch (ParseException e) {
            p.position.setValue(pos0);
            throw e;
        }

        return result.toString();
    }
}
