/* UtilLib - Copyright (c) 2008-2015 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.primitives;

import java.io.Serializable;
import java.math.BigDecimal;
import java.text.DecimalFormatSymbols;
import net.mapeadores.util.json.JSONString;
import net.mapeadores.util.text.StringUtils;


/**
 *
 * @author Vincent Calame
 */
public final class Decimal implements Serializable, JSONString {

    private static final long serialVersionUID = 1L;
    private final long partieEntiere;
    private final int partieDecimale;
    private final byte zeroLength;

    public Decimal(long partieEntiere, byte zeroLength, int partieDecimale) {
        if (partieEntiere < 0) {
            if (partieDecimale > 0) {
                partieDecimale = -partieDecimale;
            }
        } else if (partieEntiere > 0) {
            if (partieDecimale < 0) {
                partieDecimale = -partieDecimale;
            }
        }
        if (zeroLength < 0) {
            zeroLength = (byte) (-zeroLength);
        }
        this.partieEntiere = partieEntiere;
        this.zeroLength = zeroLength;
        this.partieDecimale = partieDecimale;
    }

    public Decimal(double d) {
        this.partieEntiere = (long) d;
        double d2 = Math.abs(d - partieEntiere);
        if (d2 == 0) {
            this.partieDecimale = 0;
            this.zeroLength = 0;
        } else {
            int zl = 0;
            while (true) {
                double d3 = d2 * 10;
                if (d3 >= 1) {
                    break;
                }
                d2 = d3;
                zl++;
            }
            this.zeroLength = (byte) zl;
            int partE = (int) Math.round(d2 * 1000000);
            if (partE == 1000000) {
                partE = 999999;
            }
            while (true) {
                int pe2 = partE / 10;
                if ((pe2 * 10) == partE) {
                    partE = pe2;
                } else {
                    break;
                }
            }
            if (partieEntiere < 0) {
                partE = -partE;
            }
            this.partieDecimale = partE;
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj == this) {
            return true;
        }
        if (obj.getClass() != this.getClass()) {
            return false;
        }
        Decimal other = (Decimal) obj;
        if (other.partieEntiere != this.partieEntiere) {
            return false;
        }
        if (other.partieDecimale != this.partieDecimale) {
            return false;
        }
        if (other.zeroLength != this.zeroLength) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        return toString().hashCode();
    }

    public long getPartieEntiere() {
        return partieEntiere;
    }

    public double toDouble() {
        return Double.parseDouble(this.toString());
    }

    public float toFloat() {
        return Float.parseFloat(this.toString());
    }

    /**
     * Négative si le nombre est négatif.
     */
    public int getPartieDecimale() {
        return partieDecimale;
    }

    @Override
    public String toString() {
        return toString(partieEntiere, zeroLength, partieDecimale);
    }

    @Override
    public String toJSONString() {
        return toString(partieEntiere, zeroLength, partieDecimale);
    }

    public String toString(DecimalFormatSymbols symbols) {
        return toString(symbols.getDecimalSeparator(), symbols.getGroupingSeparator());
    }

    public String toString(char decimalSeparator, char groupingSeparator) {
        StringBuilder buf = new StringBuilder();
        append(buf, partieEntiere, groupingSeparator, false, partieDecimale);
        if ((partieDecimale != 0) || (zeroLength != 0)) {
            buf.append(decimalSeparator);
            appendDecimal(buf, zeroLength, partieDecimale);
        }
        return buf.toString();
    }

    public String toStringWithBlank(char decimalSeparator) {
        StringBuilder buf = new StringBuilder();
        append(buf, partieEntiere, ' ', true, partieDecimale);
        if ((partieDecimale != 0) || (zeroLength != 0)) {
            buf.append(decimalSeparator);
            appendDecimal(buf, zeroLength, partieDecimale);
            buf.append(' ');
        }
        return buf.toString();
    }

    public byte getZeroLength() {
        return zeroLength;
    }

    public String getPartieEntiereString() {
        if (partieEntiere == 0) {
            if (partieDecimale < 0) {
                return "-0";
            } else {
                return "0";
            }
        }
        return Long.toString(partieEntiere);
    }

    public String getPartieDecimaleString() {
        return getPartieDecimaleString(zeroLength, partieDecimale);
    }

    private static void append(StringBuilder buf, long l, char groupingSeparator, boolean atLast, int partieDecimale) {
        if (l == Long.MIN_VALUE) {
            buf.append("- 9 223 372 036 854 775 808");
            return;
        }
        if (l < 0) {
            l = -l;
            buf.append("- ");
        } else if ((l == 0) && (partieDecimale < 0)) {
            buf.append("- ");
        }
        String s = Long.toString(l);
        int length = s.length();
        if (length <= 3) {
            buf.append(s);
            if (atLast) {
                buf.append(groupingSeparator);
            }
            return;
        }
        int reste = length % 3;
        if (reste > 0) {
            buf.append(s.charAt(0));
            if (reste > 1) {
                buf.append(s.charAt(1));
            }
            buf.append(groupingSeparator);
        }
        int p = 0;
        for (int i = reste; i < length; i++) {
            buf.append(s.charAt(i));
            p++;
            if (p == 3) {
                if ((i < (length - 1)) || (atLast)) {
                    buf.append(groupingSeparator);
                }
                p = 0;
            }
        }
    }

    public static String toString(long partieEntiere, byte zeroLength, int partieDecimale) {
        if ((partieDecimale == 0) && (zeroLength == 0)) {
            return Long.toString(partieEntiere);
        }
        StringBuilder buf = new StringBuilder();
        if ((partieEntiere == 0) && (partieDecimale < 0)) {
            buf.append("-0");
        } else {
            buf.append(Long.toString(partieEntiere));
        }
        buf.append('.');
        appendDecimal(buf, zeroLength, partieDecimale);
        return buf.toString();
    }

    public static String getPartieDecimaleString(byte zeroLength, int partieDecimale) {
        StringBuilder buf = new StringBuilder();
        appendDecimal(buf, zeroLength, partieDecimale);
        return buf.toString();
    }

    public static float toFloat(byte zeroLength, int partieDecimale) {
        StringBuilder buf = new StringBuilder();
        buf.append("0.");
        appendDecimal(buf, zeroLength, partieDecimale);
        return Float.parseFloat(buf.toString());
    }

    private static void appendDecimal(StringBuilder buf, byte zeroLength, int partieDecimale) {
        for (int i = 0; i < zeroLength; i++) {
            buf.append('0');
        }
        if (partieDecimale != 0) {
            buf.append(Integer.toString(Math.abs(partieDecimale)));
        }
    }

    public Decimal abs() {
        if ((partieEntiere < 0) || (partieDecimale < 0)) {
            return new Decimal(-partieEntiere, zeroLength, -partieDecimale);
        } else {
            return this;
        }
    }

    public boolean isZero() {
        return ((partieEntiere == 0) && (partieDecimale == 0));
    }

    public boolean isPositif() {
        return ((partieEntiere >= 0) && (partieDecimale >= 0));
    }

    public Decimal getOpposite() {
        return new Decimal(-partieEntiere, zeroLength, -partieDecimale);
    }

    public BigDecimal toBigDecimal() {
        return new BigDecimal(toString());
    }

    public static Decimal fromBigDecimal(BigDecimal bigDecimal) {
        return StringUtils.parseDecimal(bigDecimal.toPlainString());
    }

}
