/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.math3.dfp;

/** Subclass of {@link Dfp} which hides the radix-10000 artifacts of the superclass.
 * This should give outward appearances of being a decimal number with DIGITS*4-3
 * decimal digits. This class can be subclassed to appear to be an arbitrary number
 * of decimal digits less than DIGITS*4-3.
 * @version $Id: DfpDec.java 1449529 2013-02-24 19:13:17Z luc $
 * @since 2.2
 */
public class DfpDec extends Dfp {

    /** Makes an instance with a value of zero.
     * @param factory factory linked to this instance
     */
    protected DfpDec(final DfpField factory) {
        super(factory);
    }

    /** Create an instance from a byte value.
     * @param factory factory linked to this instance
     * @param x value to convert to an instance
     */
    protected DfpDec(final DfpField factory, byte x) {
        super(factory, x);
    }

    /** Create an instance from an int value.
     * @param factory factory linked to this instance
     * @param x value to convert to an instance
     */
    protected DfpDec(final DfpField factory, int x) {
        super(factory, x);
    }

    /** Create an instance from a long value.
     * @param factory factory linked to this instance
     * @param x value to convert to an instance
     */
    protected DfpDec(final DfpField factory, long x) {
        super(factory, x);
    }

    /** Create an instance from a double value.
     * @param factory factory linked to this instance
     * @param x value to convert to an instance
     */
    protected DfpDec(final DfpField factory, double x) {
        super(factory, x);
        round(0);
    }

    /** Copy constructor.
     * @param d instance to copy
     */
    public DfpDec(final Dfp d) {
        super(d);
        round(0);
    }

    /** Create an instance from a String representation.
     * @param factory factory linked to this instance
     * @param s string representation of the instance
     */
    protected DfpDec(final DfpField factory, final String s) {
        super(factory, s);
        round(0);
    }

    /** Creates an instance with a non-finite value.
     * @param factory factory linked to this instance
     * @param sign sign of the Dfp to create
     * @param nans code of the value, must be one of {@link #INFINITE},
     * {@link #SNAN},  {@link #QNAN}
     */
    protected DfpDec(final DfpField factory, final byte sign, final byte nans) {
        super(factory, sign, nans);
    }

    /** {@inheritDoc} */
    @Override
    public Dfp newInstance() {
        return new DfpDec(getField());
    }

    /** {@inheritDoc} */
    @Override
    public Dfp newInstance(final byte x) {
        return new DfpDec(getField(), x);
    }

    /** {@inheritDoc} */
    @Override
    public Dfp newInstance(final int x) {
        return new DfpDec(getField(), x);
    }

    /** {@inheritDoc} */
    @Override
    public Dfp newInstance(final long x) {
        return new DfpDec(getField(), x);
    }

    /** {@inheritDoc} */
    @Override
    public Dfp newInstance(final double x) {
        return new DfpDec(getField(), x);
    }

    /** {@inheritDoc} */
    @Override
    public Dfp newInstance(final Dfp d) {

        // make sure we don't mix number with different precision
        if (getField().getRadixDigits() != d.getField().getRadixDigits()) {
            getField().setIEEEFlagsBits(DfpField.FLAG_INVALID);
            final Dfp result = newInstance(getZero());
            result.nans = QNAN;
            return dotrap(DfpField.FLAG_INVALID, "newInstance", d, result);
        }

        return new DfpDec(d);

    }

    /** {@inheritDoc} */
    @Override
    public Dfp newInstance(final String s) {
        return new DfpDec(getField(), s);
    }

    /** {@inheritDoc} */
    @Override
    public Dfp newInstance(final byte sign, final byte nans) {
        return new DfpDec(getField(), sign, nans);
    }

    /** Get the number of decimal digits this class is going to represent.
     * Default implementation returns {@link #getRadixDigits()}*4-3. Subclasses can
     * override this to return something less.
     * @return number of decimal digits this class is going to represent
     */
    protected int getDecimalDigits() {
        return getRadixDigits() * 4 - 3;
    }

    /** {@inheritDoc} */
    @Override
    protected int round(int in) {

        int msb = mant[mant.length-1];
        if (msb == 0) {
            // special case -- this == zero
            return 0;
        }

        int cmaxdigits = mant.length * 4;
        int lsbthreshold = 1000;
        while (lsbthreshold > msb) {
            lsbthreshold /= 10;
            cmaxdigits --;
        }


        final int digits = getDecimalDigits();
        final int lsbshift = cmaxdigits - digits;
        final int lsd = lsbshift / 4;

        lsbthreshold = 1;
        for (int i = 0; i < lsbshift % 4; i++) {
            lsbthreshold *= 10;
        }

        final int lsb = mant[lsd];

        if (lsbthreshold <= 1 && digits == 4 * mant.length - 3) {
            return super.round(in);
        }

        int discarded = in;  // not looking at this after this point
        final int n;
        if (lsbthreshold == 1) {
            // look to the next digit for rounding
            n = (mant[lsd-1] / 1000) % 10;
            mant[lsd-1] %= 1000;
            discarded |= mant[lsd-1];
        } else {
            n = (lsb * 10 / lsbthreshold) % 10;
            discarded |= lsb % (lsbthreshold/10);
        }

        for (int i = 0; i < lsd; i++) {
            discarded |= mant[i];    // need to know if there are any discarded bits
            mant[i] = 0;
        }

        mant[lsd] = lsb / lsbthreshold * lsbthreshold;

        final boolean inc;
        switch (getField().getRoundingMode()) {
            case ROUND_DOWN:
                inc = false;
                break;

            case ROUND_UP:
                inc = (n != 0) || (discarded != 0); // round up if n!=0
                break;

            case ROUND_HALF_UP:
                inc = n >= 5;  // round half up
                break;

            case ROUND_HALF_DOWN:
                inc = n > 5;  // round half down
                break;

            case ROUND_HALF_EVEN:
                inc = (n > 5) ||
                      (n == 5 && discarded != 0) ||
                      (n == 5 && discarded == 0 && ((lsb / lsbthreshold) & 1) == 1);  // round half-even
                break;

            case ROUND_HALF_ODD:
                inc = (n > 5) ||
                      (n == 5 && discarded != 0) ||
                      (n == 5 && discarded == 0 && ((lsb / lsbthreshold) & 1) == 0);  // round half-odd
                break;

            case ROUND_CEIL:
                inc = (sign == 1) && (n != 0 || discarded != 0);  // round ceil
                break;

            case ROUND_FLOOR:
            default:
                inc = (sign == -1) && (n != 0 || discarded != 0);  // round floor
                break;
        }

        if (inc) {
            // increment if necessary
            int rh = lsbthreshold;
            for (int i = lsd; i < mant.length; i++) {
                final int r = mant[i] + rh;
                rh = r / RADIX;
                mant[i] = r % RADIX;
            }

            if (rh != 0) {
                shiftRight();
                mant[mant.length-1]=rh;
            }
        }

        // Check for exceptional cases and raise signals if necessary
        if (exp < MIN_EXP) {
            // Gradual Underflow
            getField().setIEEEFlagsBits(DfpField.FLAG_UNDERFLOW);
            return DfpField.FLAG_UNDERFLOW;
        }

        if (exp > MAX_EXP) {
            // Overflow
            getField().setIEEEFlagsBits(DfpField.FLAG_OVERFLOW);
            return DfpField.FLAG_OVERFLOW;
        }

        if (n != 0 || discarded != 0) {
            // Inexact
            getField().setIEEEFlagsBits(DfpField.FLAG_INEXACT);
            return DfpField.FLAG_INEXACT;
        }
        return 0;
    }

    /** {@inheritDoc} */
    @Override
    public Dfp nextAfter(Dfp x) {

        final String trapName = "nextAfter";

        // make sure we don't mix number with different precision
        if (getField().getRadixDigits() != x.getField().getRadixDigits()) {
            getField().setIEEEFlagsBits(DfpField.FLAG_INVALID);
            final Dfp result = newInstance(getZero());
            result.nans = QNAN;
            return dotrap(DfpField.FLAG_INVALID, trapName, x, result);
        }

        boolean up = false;
        Dfp result;
        Dfp inc;

        // if this is greater than x
        if (this.lessThan(x)) {
            up = true;
        }

        if (equals(x)) {
            return newInstance(x);
        }

        if (lessThan(getZero())) {
            up = !up;
        }

        if (up) {
            inc = power10(intLog10() - getDecimalDigits() + 1);
            inc = copysign(inc, this);

            if (this.equals(getZero())) {
                inc = power10K(MIN_EXP-mant.length-1);
            }

            if (inc.equals(getZero())) {
                result = copysign(newInstance(getZero()), this);
            } else {
                result = add(inc);
            }
        } else {
            inc = power10(intLog10());
            inc = copysign(inc, this);

            if (this.equals(inc)) {
                inc = inc.divide(power10(getDecimalDigits()));
            } else {
                inc = inc.divide(power10(getDecimalDigits() - 1));
            }

            if (this.equals(getZero())) {
                inc = power10K(MIN_EXP-mant.length-1);
            }

            if (inc.equals(getZero())) {
                result = copysign(newInstance(getZero()), this);
            } else {
                result = subtract(inc);
            }
        }

        if (result.classify() == INFINITE && this.classify() != INFINITE) {
            getField().setIEEEFlagsBits(DfpField.FLAG_INEXACT);
            result = dotrap(DfpField.FLAG_INEXACT, trapName, x, result);
        }

        if (result.equals(getZero()) && this.equals(getZero()) == false) {
            getField().setIEEEFlagsBits(DfpField.FLAG_INEXACT);
            result = dotrap(DfpField.FLAG_INEXACT, trapName, x, result);
        }

        return result;
    }

}
