package com.r35157.jupiterperpsalarm.impl.ref;

import java.math.BigDecimal;

public final class AlarmConditionParser {

    private static final String RANGE_SEPARATOR = "-->";

    private AlarmConditionParser() {
    }

    public static PriceCondition parse(String conditionExpression) {
        String trimmedExpression = conditionExpression.trim();

        if (trimmedExpression.startsWith("[") || trimmedExpression.startsWith("(")) {
            return parseRange(trimmedExpression);
        }

        PriceComparisonOperator operator;
        int operatorLength;

        if (trimmedExpression.startsWith("<=")) {
            operator = PriceComparisonOperator.LESS_THAN_OR_EQUAL;
            operatorLength = 2;
        } else if (trimmedExpression.startsWith(">=")) {
            operator = PriceComparisonOperator.GREATER_THAN_OR_EQUAL;
            operatorLength = 2;
        } else if (trimmedExpression.startsWith("<")) {
            operator = PriceComparisonOperator.LESS_THAN;
            operatorLength = 1;
        } else if (trimmedExpression.startsWith(">")) {
            operator = PriceComparisonOperator.GREATER_THAN;
            operatorLength = 1;
        } else {
            throw new IllegalArgumentException(
                    "Unknown price condition operator: " + conditionExpression
            );
        }

        String targetExpression = trimmedExpression.substring(operatorLength).trim();
        if (targetExpression.isEmpty()) {
            throw new IllegalArgumentException(
                    "Missing target in price condition: " + conditionExpression
            );
        }

        BigDecimal target = AlarmTargetParser.parse(targetExpression);
        return new ComparisonPriceCondition(operator, target);
    }

    public static CrossingPriceCondition parseCrossing(String conditionExpression) {
        String trimmedExpression = conditionExpression.trim();
        int hysteresisSeparatorIndex = trimmedExpression.indexOf('/');

        if (hysteresisSeparatorIndex != trimmedExpression.lastIndexOf('/')) {
            throw new IllegalArgumentException(
                    "CROSSING condition must contain at most one '/': "
                            + conditionExpression
            );
        }

        String comparisonExpression = hysteresisSeparatorIndex < 0
                ? trimmedExpression
                : trimmedExpression.substring(0, hysteresisSeparatorIndex);

        PriceCondition parsedCondition = parse(comparisonExpression);
        if (!(parsedCondition instanceof ComparisonPriceCondition comparison)
                || (comparison.operator() != PriceComparisonOperator.LESS_THAN
                && comparison.operator() != PriceComparisonOperator.GREATER_THAN)) {
            throw new IllegalArgumentException(
                    "CROSSING requires a '<' or '>' condition: " + conditionExpression
            );
        }

        BigDecimal hysteresis = hysteresisSeparatorIndex < 0
                ? BigDecimal.ZERO
                : parseHysteresis(
                        trimmedExpression.substring(hysteresisSeparatorIndex + 1),
                        comparison.target()
                );

        return new CrossingPriceCondition(comparison, hysteresis);
    }

    private static BigDecimal parseHysteresis(
            String hysteresisExpression,
            BigDecimal target
    ) {
        String trimmedExpression = hysteresisExpression.trim();

        if (trimmedExpression.isEmpty()) {
            throw new IllegalArgumentException("Missing CROSSING hysteresis");
        }

        if (trimmedExpression.contains("+") || trimmedExpression.contains("-")) {
            throw new IllegalArgumentException(
                    "CROSSING hysteresis must be specified without '+' or '-': "
                            + hysteresisExpression
            );
        }

        boolean percentage = trimmedExpression.endsWith("%");
        String distanceExpression = percentage
                ? trimmedExpression.substring(0, trimmedExpression.length() - 1)
                : trimmedExpression;

        BigDecimal distance;
        try {
            distance = new BigDecimal(distanceExpression);
        } catch (NumberFormatException exception) {
            throw new IllegalArgumentException(
                    "Invalid CROSSING hysteresis: " + hysteresisExpression,
                    exception
            );
        }

        if (distance.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException(
                    "CROSSING hysteresis must be zero or positive: "
                            + hysteresisExpression
            );
        }

        return percentage
                ? target.multiply(distance).movePointLeft(2)
                : distance;
    }

    private static PriceCondition parseRange(String rangeExpression) {
        char lowerDelimiter = rangeExpression.charAt(0);
        char upperDelimiter = rangeExpression.charAt(rangeExpression.length() - 1);

        if (upperDelimiter != ')' && upperDelimiter != ']') {
            throw new IllegalArgumentException(
                    "Range must end with ')' or ']': " + rangeExpression
            );
        }

        String rangeBody = rangeExpression.substring(1, rangeExpression.length() - 1);
        int separatorIndex = rangeBody.indexOf(RANGE_SEPARATOR);

        if (separatorIndex < 0 || separatorIndex != rangeBody.lastIndexOf(RANGE_SEPARATOR)) {
            throw new IllegalArgumentException(
                    "Range must contain exactly one '" + RANGE_SEPARATOR + "': " + rangeExpression
            );
        }

        String lowerTargetExpression = rangeBody.substring(0, separatorIndex).trim();
        String upperTargetExpression = rangeBody
                .substring(separatorIndex + RANGE_SEPARATOR.length())
                .trim();

        if (lowerTargetExpression.isEmpty() || upperTargetExpression.isEmpty()) {
            throw new IllegalArgumentException(
                    "Range must contain both a lower and an upper target: " + rangeExpression
            );
        }

        BigDecimal lowerTarget = AlarmTargetParser.parse(lowerTargetExpression);
        BigDecimal upperTarget = AlarmTargetParser.parse(upperTargetExpression);

        return new RangePriceCondition(
                lowerTarget,
                lowerDelimiter == '[',
                upperTarget,
                upperDelimiter == ']'
        );
    }
}
