package jscl;

import jscl.math.JsclInteger;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashSet;
import java.util.Set;

import static java.util.Arrays.asList;

public enum NumeralBase {

    dec(10, 3) {

        private final Set<Character> characters = new HashSet<>(asList('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'));

        @Nonnull
        @Override
        public Double toDouble(@Nonnull String doubleString) {
            return Double.valueOf(doubleString);
        }

        @Nonnull
        @Override
        public String getJsclPrefix() {
            return "0d:";
        }

        @Nonnull
        @Override
        public Set<Character> getAcceptableCharacters() {
            return characters;
        }
    },

    hex(16, 2) {

        private final Set<Character> characters = new HashSet<>(asList('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'));

        @Nonnull
        @Override
        public String getJsclPrefix() {
            return "0x:";
        }

        @Nonnull
        @Override
        public Set<Character> getAcceptableCharacters() {
            return characters;
        }
    },

    oct(8, 4) {

        private final Set<Character> characters = new HashSet<>(asList('0', '1', '2', '3', '4', '5', '6', '7'));

        @Nonnull
        @Override
        public String getJsclPrefix() {
            return "0o:";
        }

        @Nonnull
        @Override
        public Set<Character> getAcceptableCharacters() {
            return characters;
        }
    },

    bin(2, 4) {

        private final Set<Character> characters = new HashSet<>(asList('0', '1'));

        @Nonnull
        @Override
        public String getJsclPrefix() {
            return "0b:";
        }

        @Nonnull
        @Override
        public Set<Character> getAcceptableCharacters() {
            return characters;
        }
    };

    public final int radix;
    public final int groupingSize;

    NumeralBase(int radix, int groupingSize) {
        this.radix = radix;
        this.groupingSize = groupingSize;
    }

    @Nullable
    public static NumeralBase getByPrefix(@Nonnull String prefix) {
        for (NumeralBase nb : NumeralBase.values()) {
            if (prefix.equals(nb.getJsclPrefix())) {
                return nb;
            }
        }

        return null;
    }

    @Nonnull
    protected static String toString(@Nonnull Double value, int radix, int fractionDigits) {
        final BigDecimal mult = BigDecimal.valueOf(radix).pow(fractionDigits);
        final BigDecimal bd = BigDecimal.valueOf(value).multiply(mult);

        final BigInteger bi = bd.toBigInteger();
        final StringBuilder result = new StringBuilder(bi.toString(radix));

        while (result.length() < fractionDigits + 1) {  // +1 for leading zero
            result.insert(0, "0");
        }
        result.insert(result.length() - fractionDigits, ".");

        return result.toString().toUpperCase();
    }

    @Nonnull
    public Double toDouble(@Nonnull String doubleString) throws NumberFormatException {
        return Double.longBitsToDouble(Long.valueOf(doubleString, radix));
    }

    @Nonnull
    public Integer toInteger(@Nonnull String integerString) throws NumberFormatException {
        return Integer.valueOf(integerString, radix);
    }

    @Nonnull
    public JsclInteger toJsclInteger(@Nonnull String integerString) throws NumberFormatException {
        return new JsclInteger(toBigInteger(integerString));
    }

    @Nonnull
    public BigInteger toBigInteger(@Nonnull String value) throws NumberFormatException {
        return new BigInteger(value, radix);
    }

    public String toString(@Nonnull BigInteger value) {
        return value.toString(radix).toUpperCase();
    }

    public String toString(@Nonnull Integer value) {
        return Integer.toString(value, radix).toUpperCase();
    }

    @Nonnull
    public abstract String getJsclPrefix();

    @Nonnull
    public abstract Set<Character> getAcceptableCharacters();

    public int getGroupingSize() {
        return groupingSize;
    }

    @Nonnull
    public String toString(@Nonnull Double value, int fractionDigits) {
        return toString(value, radix, fractionDigits);
    }
}
