/*
 * Copyright 1999-2023 Alibaba Group Holding Ltd.
 *
 * Licensed 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 com.alibaba.nacos.plugin.control.tps.barrier;

import com.alibaba.nacos.plugin.control.tps.MonitorType;
import com.alibaba.nacos.plugin.control.tps.TpsMetrics;
import com.alibaba.nacos.plugin.control.tps.request.BarrierCheckRequest;
import com.alibaba.nacos.plugin.control.tps.response.TpsCheckResponse;
import com.alibaba.nacos.plugin.control.tps.response.TpsResultCode;
import com.alibaba.nacos.plugin.control.tps.rule.RuleDetail;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * simple count rule barrier.
 *
 * @author shiyiyue
 */
public abstract class SimpleCountRuleBarrier extends RuleBarrier {
    
    RateCounter rateCounter;
    
    public SimpleCountRuleBarrier(String pointName, String ruleName, TimeUnit period) {
        super.setPointName(pointName);
        super.setPeriod(period);
        super.setRuleName(ruleName);
        this.rateCounter = createSimpleCounter(ruleName, period);
    }
    
    /**
     * create rate count.
     *
     * @param name   name.
     * @param period period.
     * @return
     */
    public abstract RateCounter createSimpleCounter(String name, TimeUnit period);
    
    public void reCreateRaterCounter(String name, TimeUnit period) {
        this.rateCounter = createSimpleCounter(name, period);
    }
    
    @Override
    public TpsCheckResponse applyTps(BarrierCheckRequest barrierCheckRequest) {
        if (MonitorType.INTERCEPT.getType().equals(getMonitorType())) {
            long maxCount = getMaxCount();
            boolean accepted =  rateCounter.tryAdd(barrierCheckRequest.getTimestamp(), barrierCheckRequest.getCount(), maxCount);
            return accepted ? new TpsCheckResponse(true, TpsResultCode.PASS_BY_POINT, "success") :
                    new TpsCheckResponse(false, TpsResultCode.DENY_BY_POINT, "tps over limit :" + maxCount);
        } else {
            rateCounter.add(barrierCheckRequest.getTimestamp(), barrierCheckRequest.getCount());
            return new TpsCheckResponse(true, TpsResultCode.PASS_BY_POINT, "success");
        }
    }
    
    long trimTimeStamp(long timeStamp) {
        if (this.getPeriod() == TimeUnit.SECONDS) {
            timeStamp = RateCounter.getTrimMillsOfSecond(timeStamp);
        } else if (this.getPeriod() == TimeUnit.MINUTES) {
            timeStamp = RateCounter.getTrimMillsOfMinute(timeStamp);
        } else if (this.getPeriod() == TimeUnit.HOURS) {
            timeStamp = RateCounter.getTrimMillsOfHour(timeStamp);
        } else {
            //second default
            timeStamp = RateCounter.getTrimMillsOfSecond(timeStamp);
        }
        return timeStamp;
    }
    
    @Override
    public TpsMetrics getMetrics(long timeStamp) {
        timeStamp = trimTimeStamp(timeStamp);
        
        TpsMetrics tpsMetrics = new TpsMetrics("", "", timeStamp, super.getPeriod());
        long totalPass = rateCounter.getCount(timeStamp);
        if (totalPass <= 0) {
            return null;
        }
        tpsMetrics.setCounter(new TpsMetrics.Counter(totalPass, 0));
        return tpsMetrics;
        
    }
    
    /**
     * apply rule detail.
     *
     * @param ruleDetail ruleDetail.
     */
    public void applyRuleDetail(RuleDetail ruleDetail) {
        
        if (!Objects.equals(this.getPeriod(), ruleDetail.getPeriod())) {
            this.setMaxCount(ruleDetail.getMaxCount());
            this.setMonitorType(ruleDetail.getMonitorType());
            this.setPeriod(ruleDetail.getPeriod());
            reCreateRaterCounter(ruleDetail.getRuleName(), this.getPeriod());
        } else {
            this.setMaxCount(ruleDetail.getMaxCount());
            this.setMonitorType(ruleDetail.getMonitorType());
        }
    }
}
