package com.bofeng.service;

import com.bofeng.dao.IdRuleMapper;
import com.bofeng.entity.IdRule;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.runtime.function.AbstractFunction;
import com.googlecode.aviator.runtime.function.FunctionUtils;
import com.googlecode.aviator.runtime.type.AviatorObject;
import com.googlecode.aviator.runtime.type.AviatorString;
import com.yvan.platform.Conv;
import lombok.SneakyThrows;
import lombok.val;
import org.apache.commons.dbutils.OutParameter;
import org.apache.commons.dbutils.QueryRunner;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.sql.Types;
import java.util.Map;

@Service
@Transactional(readOnly = true)
public class IdService {

    private static final String SEQ_NEVER = "终身序号";
    private static final String SEQ_DALLY = "日序号";
    private static final String SEQ_MONTHLY = "月序号";

    @Autowired
    private DataSource dataSource;

    @Autowired
    private IdRuleMapper idRuleMapper;

    static {
        AviatorEvaluator.addFunction(new SN());
    }

    @SneakyThrows
    public long next(String idCode) {
        QueryRunner run = new QueryRunner(dataSource);

        OutParameter<Long> op = new OutParameter<>(Types.BIGINT, Long.class);
        run.execute("call id_incr(?,?)", idCode, op);
        return op.getValue();
    }

    public long current(String idName) {
        return idRuleMapper.selectIdCurrentValue(idName);
    }

    public long getDbTime() {
        return idRuleMapper.getDbTime();
    }

    @Cacheable(cacheNames = "id_rule", key = "#idCode")
    public IdRule selectExpressById(String idCode) {
        return idRuleMapper.selectExpress(idCode);
    }

    public Map<String, Object> createEnv() {
        DateTime now = DateTime.now();

        Map<String, Object> env = Maps.newHashMap();
        env.put("YY", Conv.NS(now.getYear()).substring(2));
        env.put("YYYY", Conv.NS(now.getYear()));
        env.put("MM", Strings.padStart(Conv.NS(now.getMonthOfYear()), 2, '0'));
        env.put("DD", Strings.padStart(Conv.NS(now.getDayOfMonth()), 2, '0'));
        env.put("hh", Strings.padStart(Conv.NS(now.getHourOfDay()), 2, '0'));
        env.put("mm", Strings.padStart(Conv.NS(now.getMinuteOfHour()), 2, '0'));
        env.put("ss", Strings.padStart(Conv.NS(now.getSecondOfMinute()), 2, '0'));

        return env;
    }

    public String execute(String idName) {
        IdRule idRule = selectExpressById(idName);

        val env = createEnv();

        String idCode;
        if (SEQ_DALLY.equals(idRule.getIdType())) {
            idCode = idName + ":" + env.get("YYYY") + env.get("MM") + env.get("DD");

        } else if (SEQ_MONTHLY.equals(idRule.getIdType())) {
            idCode = idName + ":" + env.get("YYYY") + env.get("MM");

        } else {
            idCode = idName;
        }
        env.put("idCode", idCode);
        env.put("IdService", this);

        Object ret = AviatorEvaluator.execute(idRule.getIdExpress(), env, true);
        return Conv.NS(ret);
    }

    static class SN extends AbstractFunction {
        @Override
        public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {

            IdService idService = (IdService) env.get("IdService");
            String idCode = (String) env.get("idCode");

            Number r1 = FunctionUtils.getNumberValue(arg1, env);
            char r2 = FunctionUtils.getStringValue(arg2, env).charAt(0);
            long value = idService.next(idCode);
            return new AviatorString(Strings.padStart(Conv.NS(value), r1.intValue(), r2));
        }

        @Override
        public String getName() {
            return "SN";
        }
    }

}
