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


/**
 * La partie décimale n'a jamais plus de six chiffres. La partie entière est
 * comprise entre 180 et -179
 *
 * @author Vincent Calame
 */
public final class DegreDecimal implements Serializable, Comparable<DegreDecimal> {

    private static final long serialVersionUID = 1L;
    private static final BigDecimal moins360 = new BigDecimal(-360);
    private static final BigDecimal plus360 = new BigDecimal(360);
    private final int partieEntiere;
    private final int partieDecimale;
    private final byte zeroLength;

    private DegreDecimal(DecimalChecker decimalChecker) {
        this.partieEntiere = decimalChecker.partieEntiere;
        this.zeroLength = decimalChecker.zeroLength;
        this.partieDecimale = decimalChecker.partieDecimale;
    }

    private DegreDecimal(DegreSexagesimal degreSexagesimal) {
        int degre = degreSexagesimal.getDegre();
        int seconde = ((degreSexagesimal.getMinute()) * 60) + degreSexagesimal.getSeconde();
        int mm = Math.round((((float) seconde) * 2500) / 9);
        if (mm == 0) {
            this.zeroLength = 6;
        } else {
            String s = String.valueOf(mm);
            this.zeroLength = (byte) (6 - s.length());
        }
        if (degreSexagesimal.isPositif()) {
            this.partieEntiere = -degre;
            this.partieDecimale = -mm;
        } else {
            this.partieEntiere = degre;
            this.partieDecimale = mm;
        }
    }

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

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

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

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

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

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

    public int getPartieEntiere() {
        return partieEntiere;
    }

    public int getPartieDecimale() {
        return partieDecimale;
    }

    public byte getZeroLength() {
        return zeroLength;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj == this) {
            return true;
        }
        if (obj.getClass() != this.getClass()) {
            return false;
        }
        DegreDecimal other = (DegreDecimal) 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();
    }

    @Override
    public int compareTo(DegreDecimal other) {
        int pE = this.partieEntiere;
        if (pE < other.partieEntiere) {
            return - 1;
        }
        if (pE > other.partieEntiere) {
            return 1;
        }
        if (pE < 0) {
            return -comparePartieDecimale(this.zeroLength, this.partieDecimale, other.zeroLength, other.partieDecimale);
        } else {
            return comparePartieDecimale(this.zeroLength, this.partieDecimale, other.zeroLength, other.partieDecimale);
        }
    }

    public DegreSexagesimal toDegreSexagesimal() {
        return DegreSexagesimal.fromDegreDecimal(this);
    }

    public static DegreDecimal newInstance(int 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);
        }
        DecimalChecker checker = new DecimalChecker(modulo(partieEntiere), zeroLength, partieDecimale);
        checker.check();
        return new DegreDecimal(checker);
    }

    public static DegreDecimal newInstance(Decimal decimal) {
        DecimalChecker checker = new DecimalChecker(modulo(decimal.getPartieEntiere()), decimal.getZeroLength(), decimal.getPartieDecimale());
        checker.check();
        return new DegreDecimal(checker);
    }

    public static DegreDecimal newInstance(DegreSexagesimal degreSexagesimal) {
        return new DegreDecimal(degreSexagesimal);
    }

    private static int modulo(long partieEntiere) {
        long modulo = (partieEntiere) / 360;
        int result = (int) (partieEntiere - (modulo * 360));
        return result;
    }

    private static int comparePartieDecimale(byte zeroLength1, int decimal1, byte zeroLength2, int decimal2) {
        if (zeroLength1 > zeroLength2) {
            return -1;
        }
        if (zeroLength1 < zeroLength2) {
            return 1;
        }
        if (decimal1 == decimal2) {
            return 0;
        }
        String string1 = Integer.toString(Math.abs(decimal1));
        String string2 = Integer.toString(Math.abs(decimal2));
        int comp = string1.compareTo(string2);
        if (comp < 0) {
            return -1;
        } else if (comp > 0) {
            return 1;
        } else {
            return 0;
        }
    }


    private static class DecimalChecker {

        private int partieEntiere;
        private int partieDecimale;
        private byte zeroLength;

        DecimalChecker(int partieEntiere, byte zeroLength, int partieDecimale) {
            this.partieEntiere = partieEntiere;
            this.zeroLength = zeroLength;
            this.partieDecimale = partieDecimale;
        }

        private void check() {
            checkDecimale();
            checkDegre();
        }

        private void checkDegre() {
            if (Math.abs(partieEntiere) < 180) {
                return;
            }
            if ((partieEntiere == 180) && (partieDecimale == 0)) {
                return;
            }
            BigDecimal bigDecimal = (new Decimal(partieEntiere, zeroLength, partieDecimale)).toBigDecimal();
            if (partieEntiere > 180) {
                bigDecimal = bigDecimal.add(moins360);
            } else {
                bigDecimal = bigDecimal.add(plus360);
            }
            Decimal newDecimal = Decimal.fromBigDecimal(bigDecimal);
            this.partieEntiere = (int) newDecimal.getPartieEntiere();
            this.partieDecimale = newDecimal.getPartieDecimale();
            this.zeroLength = newDecimal.getZeroLength();
        }

        private void checkDecimale() {
            if (partieDecimale == 0) {
                zeroLength = 0;
                return;
            }
            if (zeroLength > 6) {
                zeroLength = 0;
                partieDecimale = 0;
            }
            int dec = Math.abs(partieDecimale);
            boolean negatif = (partieDecimale < 0);
            while (true) {
                if (dec % 10 == 0) {
                    dec = dec / 10;
                } else {
                    break;
                }
            }
            String s = String.valueOf(dec);
            if (zeroLength == 6) {
                if (s.charAt(0) > '5') {
                    zeroLength = 5;
                    partieDecimale = (negatif) ? -1 : 1;
                } else {
                    zeroLength = 0;
                    partieDecimale = 0;
                }
                return;
            }
            int p = 6 - zeroLength;
            if (s.length() <= p) {
                return;
            }
            dec = Integer.parseInt(s.substring(0, p));
            if (s.charAt(p) > '5') {
                boolean neufS = true;
                for (int i = 0; i < p; i++) {
                    if (s.charAt(i) != '9') {
                        neufS = false;
                    }
                }
                if (!neufS) {
                    dec = dec + 1;
                } else if (zeroLength > 0) {
                    zeroLength--;
                    dec = 1;
                } else {
                    dec = 0;
                    if (partieEntiere > 0) {
                        partieEntiere = partieEntiere + 1;
                    } else if (partieEntiere < 0) {
                        partieEntiere = partieEntiere - 1;
                    } else {
                        if (negatif) {
                            partieEntiere = -1;
                        } else {
                            partieEntiere = 1;
                        }
                    }
                }
            }
            partieDecimale = (negatif) ? -dec : dec;
        }

    }


}
