/*
 * Copyright (C) 2016-2022 ActionTech.
 * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
 */

package com.actiontech.dble.route.function;

import com.actiontech.dble.config.model.rule.RuleAlgorithm;
import com.actiontech.dble.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * PartitionByDate
 *
 * @author lxy
 */
public class PartitionByDate extends AbstractPartitionAlgorithm implements RuleAlgorithm {
    private static final long serialVersionUID = 4966421543458534122L;

    private static final Logger LOGGER = LoggerFactory.getLogger(PartitionByDate.class);

    private String sBeginDate;
    private String sEndDate;
    private String sPartionDay;
    private String dateFormat;

    private long beginDate;
    private long partitionTime;
    private long endDate;
    private int nCount;
    private int defaultNode = -1;
    private transient ThreadLocal<SimpleDateFormat> formatter;
    private static final long ONE_DAY = 86400000;
    private int hashCode = -1;

    @Override
    public void init() {
        try {
            partitionTime = Integer.parseInt(sPartionDay) * ONE_DAY;

            beginDate = new SimpleDateFormat(dateFormat).parse(sBeginDate).getTime();

            if (!StringUtil.isEmpty(sEndDate)) {
                endDate = new SimpleDateFormat(dateFormat).parse(sEndDate).getTime();
                nCount = (int) ((endDate - beginDate) / partitionTime) + 1;
            }
            formatter = new ThreadLocal<SimpleDateFormat>() {
                @Override
                protected SimpleDateFormat initialValue() {
                    return new SimpleDateFormat(dateFormat);
                }
            };
        } catch (ParseException e) {
            throw new java.lang.IllegalArgumentException(e);
        }

        initHashCode();
    }


    @Override
    public void selfCheck() {
        StringBuffer sb = new StringBuffer();

        if (sBeginDate == null || "".equals(sBeginDate)) {
            sb.append("sBeginDate can not be null\n");
        } else {
            try {
                new SimpleDateFormat(dateFormat).parse(sBeginDate).getTime();
            } catch (Exception e) {
                sb.append("pause beginDate error\n");
            }
        }

        if (dateFormat == null || "".equals(dateFormat)) {
            sb.append("dateFormat can not be null\n");
        } else {
            if (!StringUtil.isEmpty(sEndDate)) {
                try {
                    new SimpleDateFormat(dateFormat).parse(sEndDate).getTime();
                } catch (Exception e) {
                    sb.append("pause endDate error\n");
                }
            }
        }

        if (sb.length() > 0) {
            sb.setLength(sb.length() - 1);
            throw new RuntimeException(sb.toString());
        }
    }

    @Override
    public Integer calculate(String columnValue) {
        try {
            if (columnValue == null || "null".equalsIgnoreCase(columnValue)) {
                if (defaultNode >= 0) {
                    return defaultNode;
                }
                return null;
            }
            long targetTime = formatter.get().parse(columnValue).getTime();
            if (targetTime < beginDate) {
                return (defaultNode >= 0) ? defaultNode : null;
            }
            int targetPartition = (int) ((targetTime - beginDate) / partitionTime);

            if (targetTime > endDate && nCount != 0) {
                targetPartition = targetPartition % nCount;
            }
            return targetPartition;

        } catch (ParseException e) {
            throw new IllegalArgumentException("columnValue:" + columnValue + " Please check if the format satisfied.", e);
        }
    }

    @Override
    public Integer[] calculateRange(String beginValue, String endValue) {
        SimpleDateFormat format = new SimpleDateFormat(this.dateFormat);
        try {
            Date begin = format.parse(beginValue);
            Date end = format.parse(endValue);
            Calendar cal = Calendar.getInstance();
            List<Integer> list = new ArrayList<>();
            while (begin.getTime() <= end.getTime()) {
                Integer nodeValue = this.calculate(format.format(begin));
                if (Collections.frequency(list, nodeValue) < 1) list.add(nodeValue);
                cal.setTime(begin);
                cal.add(Calendar.DATE, 1);
                begin = cal.getTime();
            }

            Integer[] nodeArray = new Integer[list.size()];
            for (int i = 0; i < list.size(); i++) {
                nodeArray[i] = list.get(i);
            }

            return nodeArray;
        } catch (ParseException e) {
            LOGGER.info("error", e);
            return new Integer[0];
        }
    }

    @Override
    public int getPartitionNum() {
        int count = this.nCount;
        return count > 0 ? count : -1;
    }

    public void setsBeginDate(String sBeginDate) {
        this.sBeginDate = sBeginDate;
        propertiesMap.put("sBeginDate", sBeginDate);
    }

    public void setsPartionDay(String sPartionDay) {
        this.sPartionDay = sPartionDay;
        propertiesMap.put("sPartionDay", sPartionDay);
    }

    public void setDateFormat(String dateFormat) {
        this.dateFormat = dateFormat;
        propertiesMap.put("dateFormat", dateFormat);
    }

    public void setsEndDate(String sEndDate) {
        this.sEndDate = sEndDate;
        propertiesMap.put("sEndDate", sEndDate);
    }

    public void setDefaultNode(int defaultNode) {
        this.defaultNode = defaultNode;
        propertiesMap.put("defaultNode", String.valueOf(defaultNode));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        PartitionByDate other = (PartitionByDate) o;

        return StringUtil.equals(other.sBeginDate, sBeginDate) &&
                StringUtil.equals(other.sPartionDay, sPartionDay) &&
                StringUtil.equals(other.dateFormat, dateFormat) &&
                StringUtil.equals(other.sEndDate, sEndDate) &&
                other.defaultNode == defaultNode;
    }

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

    private void initHashCode() {
        long tmpCode = beginDate;
        tmpCode *= partitionTime;
        if (defaultNode != 0) {
            tmpCode *= defaultNode;
        }
        if (!StringUtil.isEmpty(sEndDate)) {
            tmpCode *= endDate;
        }
        hashCode = (int) tmpCode;
    }
}
