package jp.sourceforge.glad.math.internal;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;

public class BigDecimalState implements NumberState {

    static BigDecimalState getInstance() {
        return instance;
    }

    void init(NumberContext context, BigDecimal value) {
        context.set(value, this);
    }

    public byte byteValue(NumberContext context) {
        return toBigDecimal(context).byteValue();
    }

    public byte byteValueExact(NumberContext context) {
        return toBigDecimal(context).byteValueExact();
    }

    public short shortValue(NumberContext context) {
        return toBigDecimal(context).shortValue();
    }

    public short shortValueExact(NumberContext context) {
        return toBigDecimal(context).shortValueExact();
    }

    public int intValue(NumberContext context) {
        return toBigDecimal(context).intValue();
    }

    public int intValueExact(NumberContext context) {
        return toBigDecimal(context).intValueExact();
    }

    public long longValue(NumberContext context) {
        return toBigDecimal(context).longValue();
    }

    public long longValueExact(NumberContext context) {
        return toBigDecimal(context).longValueExact();
    }

    public BigInteger toBigInteger(NumberContext context) {
        return toBigDecimal(context).toBigInteger();
    }

    public BigInteger toBigIntegerExact(NumberContext context) {
        return toBigDecimal(context).toBigIntegerExact();
    }

    public BigDecimal toBigDecimal(NumberContext context) {
        return (BigDecimal) context.getValue();
    }

    BigDecimal toBigDecimal(long value, MathContext mc) {
        return new BigDecimal(value, mc);
    }

    BigDecimal toBigDecimal(double value, MathContext mc) {
        return new BigDecimal(value, mc);
    }

    BigDecimal toBigDecimal(BigInteger value, MathContext mc) {
        return new BigDecimal(value, mc);
    }

    BigDecimal toBigDecimal(String value, MathContext mc) {
        return new BigDecimal(value, mc);
    }

    // ---- signum

    public int signum(NumberContext context) {
        return context.toBigDecimal().signum();
    }

    // ---- abs

    public void abs(NumberContext context) {
        context.set(context.toBigDecimal()
                .abs(context.getMathContext()), this);
    }

    // ---- plus

    public void plus(NumberContext context) {
        context.set(context.toBigDecimal()
                .plus(context.getMathContext()), this);
    }

    // ---- negate

    public void negate(NumberContext context) {
        context.set(context.toBigDecimal()
                .negate(context.getMathContext()), this);
    }

    // ---- round

    public void round(NumberContext context) {
        context.set(context.toBigDecimal()
                .round(context.getMathContext()), this);
    }

    // ---- setScale

    public void setScale(NumberContext context, int scale) {
        context.set(context.toBigDecimal().setScale(scale), this);
    }

    public void setScale(NumberContext context, int scale, RoundingMode rm) {
        context.set(context.toBigDecimal().setScale(scale, rm), this);
    }

    // ---- stripTrailingZeros

    public void stripTrailingZeros(NumberContext context) {
        context.set(context.toBigDecimal().stripTrailingZeros(), this);
    }

    // ---- add

    public void add(NumberContext context, int value) {
        add(context, toBigDecimal(value, context.getMathContext()));
    }

    public void add(NumberContext context, long value) {
        add(context, toBigDecimal(value, context.getMathContext()));
    }

    public void add(NumberContext context, BigInteger value) {
        add(context, toBigDecimal(value, context.getMathContext()));
    }

    public void add(NumberContext context, BigDecimal value) {
        context.set(context.toBigDecimal()
                .add(value, context.getMathContext()), this);
    }

    // ---- substract

    public void subtract(NumberContext context, int value) {
        subtract(context, toBigDecimal(value, context.getMathContext()));
    }

    public void subtract(NumberContext context, long value) {
        subtract(context, toBigDecimal(value, context.getMathContext()));
    }

    public void subtract(NumberContext context, BigInteger value) {
        subtract(context, toBigDecimal(value, context.getMathContext()));
    }

    public void subtract(NumberContext context, BigDecimal value) {
        context.set(context.toBigDecimal()
                .subtract(value, context.getMathContext()), this);
    }

    // ---- multiply

    public void multiply(NumberContext context, int value) {
        multiply(context, toBigDecimal(value, context.getMathContext()));
    }

    public void multiply(NumberContext context, long value) {
        multiply(context, toBigDecimal(value, context.getMathContext()));
    }

    public void multiply(NumberContext context, BigInteger value) {
        multiply(context, toBigDecimal(value, context.getMathContext()));
    }

    public void multiply(NumberContext context, BigDecimal value) {
        context.set(context.toBigDecimal()
                .multiply(value, context.getMathContext()), this);
    }

    // ---- divide

    public void divide(NumberContext context, int value) {
        divide(context, toBigDecimal(value, context.getMathContext()));
    }

    public void divide(NumberContext context, long value) {
        divide(context, toBigDecimal(value, context.getMathContext()));
    }

    public void divide(NumberContext context, BigInteger value) {
        divide(context, toBigDecimal(value, context.getMathContext()));
    }

    public void divide(NumberContext context, BigDecimal value) {
        context.set(context.toBigDecimal()
                .divide(value, context.getMathContext()), this);
    }

    public void divide(
            NumberContext context, BigDecimal value, RoundingMode rm) {
        context.set(context.toBigDecimal().divide(value, rm), this);
    }

    public void divide(NumberContext context,
            BigDecimal value, int scale, RoundingMode rm) {
        context.set(context.toBigDecimal().divide(value, scale, rm), this);
    }

    // ---- divideToIntegralValue

    public void divideToIntegralValue(
            NumberContext context, BigDecimal value) {
        context.set(context.toBigDecimal()
                .divideToIntegralValue(value, context.getMathContext()), this);
    }

    // ---- remainder

    public void remainder(NumberContext context, int value) {
        remainder(context, toBigDecimal(value, context.getMathContext()));
    }

    public void remainder(NumberContext context, long value) {
        remainder(context, toBigDecimal(value, context.getMathContext()));
    }

    public void remainder(NumberContext context, BigInteger value) {
        remainder(context, toBigDecimal(value, context.getMathContext()));
    }

    public void remainder(NumberContext context, BigDecimal value) {
        context.set(context.toBigDecimal()
                .remainder(value, context.getMathContext()), this);
    }

    // ---- divideAndRemainder

    // ---- min

    // ---- max

    // ---- pow

    public void pow(NumberContext context, int n) {
        context.set(context.toBigDecimal()
                .pow(n, context.getMathContext()), this);
    }

    // ---- movePointLeft

    public void movePointLeft(NumberContext context, int n) {
        context.set(context.toBigDecimal().movePointLeft(n), this);
    }

    // ---- movePointRight

    public void movePointRight(NumberContext context, int n) {
        context.set(context.toBigDecimal().movePointRight(n), this);
    }

    // ---- scaleByPowerOfTen

    public void scaleByPowerOfTen(NumberContext context, int n) {
        context.set(context.toBigDecimal().scaleByPowerOfTen(n), this);
    }

    // ---- toString

    public String toString(NumberContext context) {
        return context.toBigDecimal().toPlainString();
    }

    // ---- equals

    // ---- compareTo

    public int compareTo(NumberContext context, int other) {
        return compareTo(context,
                toBigDecimal(other, context.getMathContext()));
    }

    public int compareTo(NumberContext context, long other) {
        return compareTo(context,
                toBigDecimal(other, context.getMathContext()));
    }

    public int compareTo(NumberContext context, BigInteger other) {
        return compareTo(context,
                toBigDecimal(other, context.getMathContext()));
    }

    public int compareTo(NumberContext context, BigDecimal other) {
        return context.toBigDecimal().compareTo(other);
    }

    // ---- singleton

    static final BigDecimalState instance = new BigDecimalState();

}
