package cn.estsh.i3plus.pojo.aps.common;

import java.util.AbstractMap;
import java.util.ArrayList;

public class DateDuration {
    enum  EV_STRING_TYPE {
        EVS_GOOD,
        EVS_HAVE_P,
    };
    class EV_STRING implements Cloneable {
        EV_STRING_TYPE type;
        String value = "";

        public EV_STRING clone() {
            EV_STRING o = null;
            try {
                o = (EV_STRING)super.clone();
            } catch (CloneNotSupportedException e) {
            }

            return o;
        }
    };

    public static final double PRECISION = 0.00001;
    private String value;
    private int time = 0;
    private double rate = 0.0;
    private boolean bValid = false;
    private int unit = 0;

    public DateDuration(int value) {
        this.time = value;
    }

    public DateDuration(String value) {
        setValue(value);
    }

    public void setValue(String value) {
        this.value = value == null ? "" : value;
        this.time = 0;
        this.rate = 0.0f;
        parse();
    }

    /**
     * 返回原始的时间区间字符串。
     * @return
     */
    public String getValue() {
        return this.value;
    }

    /**
     * 是否为有效时间区间，若用户指定了时间区间，但解析错误，其时间区间为0
     * @return
     */
    public boolean valid() {
        return this.bValid;
    }

    /**
     * 对于无效的时间区间，返回0。单位为秒。
     * @return
     */
    public int getTime() {
        return this.time;
    }

    /**
     * 设置新的时间区间。
     * @return
     */
    public void setTime(int time) {
        this.time = time;
    }

    /**
     * 对于无效的时间区间，返回0。单位为毫秒。
     * @return
     */
    public long getTimeInMillis() {
        return this.time * 1000l;
    }

    @Override
    public String toString() {
        if (this.rate > PRECISION)
            return this.getValue();

        if (this.time < 0) {
            return "";
        }
        if (this.time == 0) {
            return "0S";
        }

        long iMaxTimeUnit = 4;
        StringBuffer strExtraString = new StringBuffer();
        long extraTime = this.time;
        if (iMaxTimeUnit >= 4) {
            long i = extraTime / 86400;
            if (i > 0) {
                strExtraString.append(i).append("D");
            }
            extraTime -= i * 86400;
        }
        if (iMaxTimeUnit >= 3) {
            long i = extraTime / 3600;
            if (i > 0) {
                strExtraString.append(i).append("H");
            }
            extraTime -= i * 3600;
        }
        if (iMaxTimeUnit >= 2) {
            long i = extraTime / 60;
            if (i > 0) {
                strExtraString.append(i).append("M");
            }
            extraTime -= i * 60;
        }
        if (iMaxTimeUnit >= 1) {
            if (extraTime > 0) {
                strExtraString.append(extraTime).append("S");
            }
        }
        return strExtraString.toString();
    }

    public void divide(double val) {
        if (val < PRECISION) {
            return;
        }
        if (this.time >= 0)
            this.time /= val;
        this.rate  /=  val;
        this.value = toString();
    }

    /**
     * 对于无效的时间区间，返回0。单位为秒
     * @param amount
     * @return
     */
    public int getTime(double amount) {
        if (amount < PRECISION)
            return getTime();

        if (this.unit > 0) {
            amount = Math.ceil(amount / this.unit);
        }
        double result = (amount * this.rate) + this.time;
        return (int)Math.ceil(result);
    }

    /**
     * 对于无效的时间区间，返回0。单位为毫秒
     * @param amount
     * @return
     */
    public long getTimeInMillis(double amount) {
        return this.getTime(amount) * 1000l;
    }

    private void parse() {
        this.bValid = true;
        if (this.value.isEmpty() || this.value.equals("-1")) {
            this.time = 0;
            this.rate = 0.0f;
            this.bValid = false;
            return;
        }

        if (!valueValid()) {
            this.time = 0;
            this.rate = 0.0f;
            this.bValid = false;
            return;
        }

        ArrayList<EV_STRING> lst = new ArrayList<EV_STRING>();
        decompound(lst);

        boolean oFlg = lst.size() == 1;

        for (EV_STRING evs : lst) {
            if (evs.type == EV_STRING_TYPE.EVS_GOOD) {
                AbstractMap.SimpleEntry<Boolean, Integer> extraTime = transTimeEach(evs.value);
                if (extraTime.getKey() || oFlg) {
                    this.time += extraTime.getValue();
                } else {
                    //this.time += extraTime.getValue() * 1;
                    this.unit = extraTime.getValue();
                }
            }
            else {
                AbstractMap.SimpleEntry<Boolean, Double> extraPer = transRateEach(evs.value);
                if (extraPer.getKey()) {
                    this.rate = extraPer.getValue();
                }
            }
        }

        if (this.time < 0) {
            this.time = 0;
            this.rate = 0.0f;
            this.bValid = false;
        }
    }

    private boolean valueValid() {
        for (char ch : this.value.toCharArray()) {
            if (ch <= 'z' && ch >= 'a') {
                ch -= 32;
            }

            if (!isNumber(ch) && !isUnits(ch)) {
                return false;
            }
        }

        this.value = this.value.toUpperCase();
        return true;
    }

    private boolean isNumber(char ch) {
        return (ch <= '9' && ch >= '0') || (ch == '.');
    }

    private boolean isUnits(char ch) {
        return (ch == 'D' || ch == 'H'
                || ch == 'M' || ch == 'S' || ch == '+'||ch == '-'||ch == 'P');
    }

    private void decompound(ArrayList<EV_STRING> lst) {
        int len = this.value.length();
        int iStr = 0;
        EV_STRING evs = new EV_STRING();
        evs.type = EV_STRING_TYPE.EVS_GOOD;
        while(iStr < len) {
            char ch = this.value.charAt(iStr);
            if (ch == '+') {
                lst.add(evs.clone());
                evs.value = "";
                evs.type = EV_STRING_TYPE.EVS_GOOD;
                iStr++;
                continue;
            } else if (ch == '-') {
                int iPre = iStr;
                if (iPre != 0) {
                    iPre--;
                    char cp = this.value.charAt(iPre);
                    if (!(cp == 'D' || cp == 'H'
                            || cp == 'M' || cp == 'S')) {
                        evs.value = evs.value + 'S';
                    }
                }
            } else if (ch == 'P') {
                int iTempIndex = 0;
                StringBuffer temp1 = new StringBuffer();
                StringBuffer temp2 = new StringBuffer();
                int iTemp = evs.value.length() - 1;
                for (;iTemp >= 0;iTemp--) {
                    char cTemp = evs.value.charAt(iTemp);
                    if (cTemp == 'D' || cTemp == 'H'
                            || cTemp == 'M' || cTemp == 'S') {
                        iTempIndex++;
                    } if (iTempIndex >= 2) {
                        temp1.insert(0,cTemp);
                    } else {
                        temp2.insert(0,cTemp);
                    }
                }
                if (!temp1.toString().isEmpty()) {
                    EV_STRING evs1 = new EV_STRING();
                    evs1.type = EV_STRING_TYPE.EVS_GOOD;
                    evs1.value = temp1.toString();
                    lst.add(evs1);
                }
                evs.value = temp2.toString();
                evs.type = EV_STRING_TYPE.EVS_HAVE_P;
                evs.value = evs.value + ch;
                iStr++;
                if (iStr != len) {
                    char cp = this.value.charAt(iStr);
                    if (cp == 'D' || cp == 'H'
                            || cp == 'M' || cp == 'S') {
                        evs.value = evs.value + cp;
                        iStr++;
                    }
                }

                lst.add(evs.clone());
                evs.value = "";
                evs.type = EV_STRING_TYPE.EVS_GOOD;
                continue;
            }
            evs.value = evs.value + ch;
            iStr++;
        }
        if (!evs.value.isEmpty()) {
            lst.add(evs);
        }
    }

    private AbstractMap.SimpleEntry<Boolean, Integer> transTimeEach(String extraValue) {
        if (extraValue.isEmpty())
            return new AbstractMap.SimpleEntry<Boolean, Integer>(false, 0);

        int extraTime = 0;
        StringBuffer temp = new StringBuffer();
        boolean flg = false;
        for (int i = 0; i < extraValue.length(); i++) {
            char ch = extraValue.charAt(i);
            int rate = timeUnit(ch);
            if (rate > 0) {
                double iC = Double.parseDouble(temp.toString());
                extraTime += (int)(iC*rate);
                temp.setLength(0);
                flg = true;
            } else {
                temp.append(ch);
            }
        }
        if (!temp.toString().isEmpty())
            extraTime += Double.parseDouble(temp.toString());
        return new AbstractMap.SimpleEntry<Boolean, Integer>(flg, extraTime);
    }
    private AbstractMap.SimpleEntry<Boolean, Double> transRateEach(String extraValue) {
        if (extraValue.isEmpty())
            return new AbstractMap.SimpleEntry<Boolean, Double>(false, 0.0);

        double extraPer = 0.0;
        StringBuffer temp = new StringBuffer();
        boolean flg = false;
        for (int i = 0; i < extraValue.length(); i++) {
            char ch = extraValue.charAt(i);
            switch(ch) {
                case 'D': extraPer = flg ?  (1 / Double.parseDouble(temp.toString())) * 86400  : Double.parseDouble(temp.toString()) * 86400; return new AbstractMap.SimpleEntry<Boolean, Double>(true, extraPer);
                case 'H': extraPer = flg ?  (1 / Double.parseDouble(temp.toString())) * 3600: Double.parseDouble(temp.toString()) * 3600; return new AbstractMap.SimpleEntry<Boolean, Double>(true, extraPer);
                case 'M': extraPer = flg ?  (1 / Double.parseDouble(temp.toString())) * 60 : Double.parseDouble(temp.toString()) * 60; return new AbstractMap.SimpleEntry<Boolean, Double>(true, extraPer);
                case 'S': extraPer = flg ?  (1 / Double.parseDouble(temp.toString())) * 1 : Double.parseDouble(temp.toString()) * 1; return new AbstractMap.SimpleEntry<Boolean, Double>(true, extraPer);
                case 'P': flg = true;break;
                default  : temp.append(ch);
            }
        }
        return new AbstractMap.SimpleEntry<>(true, extraPer);
    }

    private int timeUnit(char ch) {
        switch(ch) {
            case 'D': return 86400;
            case 'H': return 3600;
            case 'M': return 60;
            case 'S': return 1;
        }
        return 0;
    }
}
