/*
 *@Copyright (c) 2016, 浙江阿拉丁电子商务股份有限公司 All Rights Reserved. 
 */
package com.heyu.api.data.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 何鑫 2017年1月18日  12:51:33
 * @类描述：金额相关计算
 * @注意：本内容仅限于浙江阿拉丁电子商务股份有限公司内部传阅，禁止外泄以及用于其他的商业目的
 */

public class BigDecimalUtil {

    public static final BigDecimal ONE_HUNDRED = new BigDecimal("100");

    protected static final Logger logger = LoggerFactory.getLogger(BigDecimalUtil.class);

    /**
     * 加法,保留小数点两位
     *
     * @param v1
     * @param v2
     * @return
     */
    public static BigDecimal add(BigDecimal v1, BigDecimal v2) {
        v1 = v1 == null ? new BigDecimal(0) : v1;
        v2 = v2 == null ? new BigDecimal(0) : v2;
        v1 = v1.add(v2).setScale(2, RoundingMode.HALF_UP);
        return v1;
    }


    /**
     * 多个加法想家
     *
     * @param array
     * @param array
     * @return
     */
    public static BigDecimal add(BigDecimal... array) {
        BigDecimal result = BigDecimal.ZERO;
        if (array == null || array.length == 0) {
            return BigDecimal.ZERO;
        }
        for (int i = 0; i < array.length; i++) {
            result = add(result, array[i]);
        }
        return result;
    }

    /**
     * 加法,保留小数点两位
     *
     * @param v1
     * @param v2
     * @return
     */
    public static BigDecimal add(double v1, double v2) {
        BigDecimal value = new BigDecimal(v1).add(new BigDecimal(v2)).setScale(2, RoundingMode.HALF_UP);
        return value;
    }

    /**
     * 减法v1-v2
     *
     * @param v1
     * @param v2
     * @return
     */
    public static BigDecimal subtract(BigDecimal v1, BigDecimal v2) {
        v1 = v1 == null ? new BigDecimal(0) : v1;
        v2 = v2 == null ? new BigDecimal(0) : v2;
        v1 = v1.subtract(v2).setScale(2, RoundingMode.HALF_UP);
        return v1;
    }

    /**
     * 减法v1-v2
     *
     * @param v1
     * @param v2
     * @return
     */
    public static BigDecimal subtract(double v1, double v2) {
        BigDecimal value = new BigDecimal(v1).subtract(new BigDecimal(v2)).setScale(2, RoundingMode.HALF_UP);
        return value;
    }

    /**
     * 乘法 v1*v2
     *
     * @param v1
     * @param v2
     * @return
     */
    public static BigDecimal multiply(double v1, double v2) {
        BigDecimal value = new BigDecimal(v1).multiply(new BigDecimal(v2)).setScale(2, RoundingMode.HALF_UP);
        return value;
    }

    /**
     * 乘法 v1*v2
     *
     * @param v1
     * @param v2
     * @return
     */
    public static BigDecimal multiply(BigDecimal v1, BigDecimal v2) {
        v1 = v1 == null ? new BigDecimal(0) : v1;
        v2 = v2 == null ? new BigDecimal(0) : v2;
        v1 = v1.multiply(v2).setScale(2, RoundingMode.HALF_UP);
        return v1;
    }

    /**
     * 多个Bigdecimal相乘
     *
     * @param array
     * @return
     */
    public static BigDecimal multiply(BigDecimal... array) {
        BigDecimal result = BigDecimal.ONE;
        if (array == null || array.length == 0) {
            return BigDecimal.ZERO;
        }
        for (int i = 0; i < array.length; i++) {
            result = multiply(result, array[i]);
        }
        return result;
    }

    /**
     * 除法 v1/v2(v2为0时未抛异常，注意不传空)
     *
     * @param v1
     * @param v2
     * @return
     */
    public static BigDecimal divide(double v1, double v2) {
        BigDecimal value = new BigDecimal(v1).divide(new BigDecimal(v2), 2, RoundingMode.HALF_UP);
        return value;
    }

    /**
     * 除法 v1/v2(v2为0时未抛异常，注意不传空)
     *
     * @param v1
     * @param v2
     * @return
     */
    public static BigDecimal divide(BigDecimal v1, BigDecimal v2) {
        v1 = v1 == null ? new BigDecimal(0) : v1;
        v2 = v2 == null ? new BigDecimal(0) : v2;
        v1 = v1.divide(v2, 2, RoundingMode.HALF_UP);
        return v1;
    }

    /**
     * 返回比较值
     *
     * @param v1
     * @param v2
     * @return -1 v1<v2， 0 v1==v2, 1 v1>v2
     */
    public static int compareTo(final BigDecimal v1, final BigDecimal v2) {
        return v1.compareTo(v2);
    }


    public static int compare( BigDecimal v1, BigDecimal v2) {
        v1 = v1 == null ? new BigDecimal(0) : v1;
        v2 = v2 == null ? new BigDecimal(0) : v2;
        return v1.compareTo(v2);
    }


    /**
     * BigDecimal开n次方根。
     * 转载于：https://www.xuebuyuan.com/1863340.html
     * @param number 被开方数
     * @param n n次方根
     * @param scale 精度
     * @param roundingMode 舍入规则
     */
    public static BigDecimal bigRoot(BigDecimal number, int n, int scale, int roundingMode) {
        boolean negate = false;
        if (n < 0)
            throw new ArithmeticException();
        if (number.compareTo(BigDecimal.ZERO) < 0) {
            if (n % 2 == 0)
                throw new ArithmeticException();
            else {
                number = number.negate();
                negate = true;
            }
        }

        BigDecimal root;

        if (n == 0)
            root = BigDecimal.ONE;
        else if (n == 1)
            root = number;
        else {
            final BigInteger N = BigInteger.valueOf(n);
            final BigInteger N2 = BigInteger.TEN.pow(n);
            final BigInteger N3 = BigInteger.TEN.pow(n - 1);
            final BigInteger NINE = BigInteger.valueOf(9);

            BigInteger[] C = new BigInteger[n + 1];
            for (int i = 0; i <= n; i++) {
                C[i] = combination(n, i);
            }

            BigInteger integer = number.toBigInteger();
            String strInt = integer.toString();
            int lenInt = strInt.length();
            for (int i = lenInt % n; i < n && i > 0; i++)
                strInt = "0" + strInt;
            lenInt = (lenInt + n - 1) / n * n;
            BigDecimal fraction = number.subtract(number.setScale(0, BigDecimal.ROUND_DOWN));
            int lenFrac = (fraction.scale() + n - 1) / n * n;
            fraction = fraction.movePointRight(lenFrac);
            String strFrac = fraction.toPlainString();
            for (int i = strFrac.length(); i < lenFrac; i++)
                strFrac = "0" + strFrac;

            BigInteger res = BigInteger.ZERO;
            BigInteger rem = BigInteger.ZERO;
            for (int i = 0; i < lenInt / n; i++) {
                rem = rem.multiply(N2);

                BigInteger temp = new BigInteger(strInt.substring(i * n, i * n + n));
                rem = rem.add(temp);

                BigInteger j;
                if (res.compareTo(BigInteger.ZERO) != 0)
                    j = rem.divide(res.pow(n - 1).multiply(N).multiply(N3));
                else
                    j = NINE;
                BigInteger test = BigInteger.ZERO;
                temp = res.multiply(BigInteger.TEN);
                while (j.compareTo(BigInteger.ZERO) >= 0) {
                    //test = res.multiply(BigInteger.TEN);
                    //test = ((test.add(j)).pow(n)).subtract(test.pow(n));
                    test = BigInteger.ZERO;
                    if (j.compareTo(BigInteger.ZERO) > 0)
                        for (int k = 1; k <= n; k++)
                            test = test.add(j.pow(k).multiply(C[k]).multiply(temp.pow(n - k)));
                    if (test.compareTo(rem) <= 0)
                        break;
                    j = j.subtract(BigInteger.ONE);
                }

                rem = rem.subtract(test);
                res = res.multiply(BigInteger.TEN);
                res = res.add(j);
            }
            for (int i = 0; i <= scale; i++) {
                rem = rem.multiply(N2);

                if (i < lenFrac / n) {
                    BigInteger temp = new BigInteger(strFrac.substring(i * n, i * n + n));
                    rem = rem.add(temp);
                }

                BigInteger j;
                if (res.compareTo(BigInteger.ZERO) != 0) {
                    j = rem.divide(res.pow(n - 1).multiply(N).multiply(N3));
                } else
                    j = NINE;
                BigInteger test = BigInteger.ZERO;
                BigInteger temp = res.multiply(BigInteger.TEN);
                while (j.compareTo(BigInteger.ZERO) >= 0) {
                    //test = res.multiply(BigInteger.TEN);
                    //test = ((test.add(j)).pow(n)).subtract(test.pow(n));
                    test = BigInteger.ZERO;
                    if (j.compareTo(BigInteger.ZERO) > 0)
                        for (int k = 1; k <= n; k++)
                            test = test.add(j.pow(k).multiply(C[k]).multiply(temp.pow(n - k)));
                    if (test.compareTo(rem) <= 0)
                        break;
                    j = j.subtract(BigInteger.ONE);
                }

                rem = rem.subtract(test);
                res = res.multiply(BigInteger.TEN);
                res = res.add(j);
            }
            root = new BigDecimal(res).movePointLeft(scale + 1);
            if (negate)
                root = root.negate();
        }
        return root.setScale(scale, roundingMode);
    }

    public static BigInteger combination(int n, int k) {
        if (k > n || n < 0 || k < 0)
            return BigInteger.ZERO;
        if (k > n / 2)
            return combination(n, n - k);
        BigInteger N1 = BigInteger.ONE;
        BigInteger N2 = BigInteger.ONE;
        BigInteger N = BigInteger.valueOf(n);
        BigInteger K = BigInteger.valueOf(k);
        for (int i = 0; i < k; i++) {
            N1 = N1.multiply(N);
            N2 = N2.multiply(K);
            N = N.subtract(BigInteger.ONE);
            K = K.subtract(BigInteger.ONE);
        }
        return N1.divide(N2);
    }

    /**迭代次数*/
    public final static int LOOPNUM=1000;
    public final static int SCALE = 10;//精度
    public final static int ROUNDINGMODE = BigDecimal.ROUND_HALF_UP;//舍入模式
    /**最小差异*/
    public final static BigDecimal MINDIF=new BigDecimal(0.00000001);

    /**
     * 计算irr费用 央行复利
     * 央行公式连接：http://www.pbc.gov.cn/goutongjiaoliu/113456/113469/4221375/index.html
     * @param firstCost 一次性收取费用，如果没有传null,0
     * @param amount 借款金额，本金
     * @param perAmout 每期还款金额
     * @param perYear 每年期数，每年还款次数 传0表示到期一次性还款，否则为每年还款次数
     * @param years 总年数
     * */
    public static BigDecimal getIrrPBC(BigDecimal firstCost, BigDecimal amount, List<BigDecimal> perAmout, int perYear, int years){
        if(BigDecimal.ZERO.equals(amount) || perAmout.size() == 0){
            return null;
        }

        if(perYear == 0){//到期一次性还本付息类产品 还款金额/本金后开years的方-1
            BigDecimal bigDecimal = perAmout.get(0).divide(amount, SCALE, ROUNDINGMODE);
            bigDecimal = bigRoot(bigDecimal, years, SCALE, ROUNDINGMODE);
            bigDecimal = bigDecimal.subtract(new BigDecimal(1));
            return bigDecimal;
        }else{
            if(firstCost != null){
                amount = amount.subtract(firstCost);
            }

            return getIrrPBC(amount, perAmout, perYear);
        }
    }


    /**
     * 计算每期的IRR值 央行复利
     * @param amount 借款金额，本金
     * @param perAmout 每期金额
     * */
    public static BigDecimal getIrrPBC(BigDecimal amount, List<BigDecimal> perAmout, int perYear){

        BigDecimal minValue = BigDecimal.ZERO;
        BigDecimal maxValue = BigDecimal.ONE;
        BigDecimal testValue = BigDecimal.ZERO;
        int n = LOOPNUM;
        while(n > 0){
            testValue = minValue.add(maxValue).divide(new BigDecimal(2), SCALE, ROUNDINGMODE);
            BigDecimal npv=NPVPBC(perAmout, testValue, perYear);
            if(amount.subtract(npv).abs().compareTo(MINDIF) < 0){
                break;
            }else if(amount.abs().compareTo(npv) > 0){
                maxValue = testValue;
            }else{
                minValue = testValue;
            }
            n--;
        }
        return testValue;


    }




    /**
     * 计算总值IRR总之 央行复利
     * @param flowInArr 每期费用
     * @param rate irr率
     * */
    public static BigDecimal NPVPBC(List<BigDecimal> flowInArr,BigDecimal rate, int perYear){
        BigDecimal npv = BigDecimal.ZERO;
        for(int i = 0;i < flowInArr.size();i++){
            BigDecimal bigDecimal = BigDecimal.ONE.add(rate);
            bigDecimal = bigDecimal.pow(i + 1);
            bigDecimal = bigRoot(bigDecimal, perYear, SCALE, ROUNDINGMODE);
            npv = npv.add(flowInArr.get(i).divide(bigDecimal, SCALE, ROUNDINGMODE));
        }
        return npv;
    }


    /**
     * 计算IRR值 单利
     * @param amount 借款金额，本金
     * @param perAmout 每期金额
     * */
    public static BigDecimal getIrr(BigDecimal amount, List<BigDecimal> perAmout){
        amount = BigDecimal.ZERO.subtract(amount);
        List<BigDecimal> cashFlow = new ArrayList<>();
        cashFlow.addAll(perAmout);
        cashFlow.add(0, amount);
        BigDecimal minValue= BigDecimal.ZERO;
        BigDecimal maxValue= BigDecimal.ONE;
        BigDecimal testValue= BigDecimal.ZERO;
        int n = LOOPNUM;
        while(n > 0){
            testValue = minValue.add(maxValue).divide(new BigDecimal(2), SCALE, ROUNDINGMODE);
            BigDecimal npv=NPV(cashFlow, testValue);
            if(amount.add(npv).abs().compareTo(MINDIF) < 0){
                break;
            }else if(amount.abs().compareTo(npv) > 0){
                maxValue = testValue;
            }else{
                minValue = testValue;
            }
            n--;
        }
        return testValue.multiply(new BigDecimal(12));
    }
    /**
     * 计算总值IRR总之 单利
     * @param flowInArr 每期费用
     * @param rate irr率
     * */
    public static BigDecimal NPV(List<BigDecimal> flowInArr,BigDecimal rate){
        BigDecimal npv = BigDecimal.ZERO;
        for(int i = 1;i < flowInArr.size();i++){
            npv = npv.add(flowInArr.get(i).divide(BigDecimal.ONE.add(rate).pow(i), SCALE, ROUNDINGMODE));
        }
        return npv;
    }


    /**
     * @param value
     * @param scale 保留几位有效果数字
     * @return
     */
    public static BigDecimal getScale(BigDecimal value, int scale){
        if(value == null){
            return BigDecimal.ZERO;
        }
        return  value.setScale(scale, BigDecimal.ROUND_HALF_UP);
    }


    public static BigDecimal subtractNoNegative(BigDecimal v1, BigDecimal v2) {
        v1 = v1 == null ? new BigDecimal(0) : v1;
        v2 = v2 == null ? new BigDecimal(0) : v2;
        v1 = v1.subtract(v2).setScale(2, RoundingMode.HALF_UP);

        return v1.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO: v1;
    }
}
