/*
 * Copyright 1999-2018 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.csp.sentinel.slots.system;

import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.slots.block.AbstractRule;

/**
 * <p>
 * Sentinel System Rule makes the inbound traffic and capacity meet. It takes
 * average RT, QPS and thread count of requests into account. And it also
 * provides a measurement of system's load, but only available on Linux.
 * </p>
 * <p>
 * We recommend to coordinate {@link #highestSystemLoad}, {@link #qps}, {@link #avgRt}
 * and {@link #maxThread} to make sure your system run in safety level.
 * </p>
 * <p>
 * To set the threshold appropriately, performance test may be needed.
 * </p>
 *
 * @author jialiang.linjl
 * @see SystemRuleManager
 */
public class SystemRule extends AbstractRule {

    /**
     * negative value means no threshold checking.
     */
    private double highestSystemLoad = -1;
    private double qps = -1;
    private long avgRt = -1;
    private long maxThread = -1;

    public double getQps() {
        return qps;
    }

    /**
     * Set max total QPS. In a high concurrency condition, real passed QPS may be greater than max QPS set.
     * The real passed QPS will nearly satisfy the following formula:<br/>
     *
     * <pre>real passed QPS = QPS set + concurrent thread number</pre>
     *
     * @param qps max total QOS, values <= 0 are special for clearing the threshold.
     */
    public void setQps(double qps) {
        this.qps = qps;
    }

    public long getMaxThread() {
        return maxThread;
    }

    /**
     * Set max PARALLEL working thread. When concurrent thread number is greater than {@code maxThread} only
     * maxThread will run in parallel.
     *
     * @param maxThread max parallel thread number, values <= 0 are special for clearing the threshold.
     */
    public void setMaxThread(long maxThread) {
        this.maxThread = maxThread;
    }

    public long getAvgRt() {
        return avgRt;
    }

    /**
     * Set max average RT(response time) of all passed requests.
     *
     * @param avgRt max average response time, values <= 0 are special for clearing the threshold.
     */
    public void setAvgRt(long avgRt) {
        this.avgRt = avgRt;
    }

    public double getHighestSystemLoad() {
        return highestSystemLoad;
    }

    /**
     * <p>
     * Set highest load. The load is not same as Linux system load, which is not sensitive enough.
     * To calculate the load, both Linux system load, current global response time and global QPS will be considered,
     * which means that we need to coordinate with {@link #setAvgRt(long)} and {@link #setQps(double)}
     * </p>
     * <p>
     * Note that this parameter is only available on Unix like system.
     * </p>
     *
     * @param highestSystemLoad highest system load, values <= 0 are special for clearing the threshold.
     * @see SystemRuleManager
     */
    public void setHighestSystemLoad(double highestSystemLoad) {
        this.highestSystemLoad = highestSystemLoad;
    }

    @Override
    public boolean passCheck(Context context, DefaultNode node, int count, Object... args) {
        return true;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof SystemRule)) {
            return false;
        }
        if (!super.equals(o)) {
            return false;
        }

        SystemRule that = (SystemRule)o;

        if (Double.compare(that.highestSystemLoad, highestSystemLoad) != 0) {
            return false;
        }

        if (Double.compare(that.qps, qps) != 0) {
            return false;
        }

        if (avgRt != that.avgRt) {
            return false;
        }
        return maxThread == that.maxThread;
    }

    @Override
    public int hashCode() {
        int result = super.hashCode();
        long temp;
        temp = Double.doubleToLongBits(highestSystemLoad);
        result = 31 * result + (int)(temp ^ (temp >>> 32));

        temp = Double.doubleToLongBits(qps);
        result = 31 * result + (int)(temp ^ (temp >>> 32));

        result = 31 * result + (int)(avgRt ^ (avgRt >>> 32));
        result = 31 * result + (int)(maxThread ^ (maxThread >>> 32));
        return result;
    }

    @Override
    public String toString() {
        return "SystemRule{" +
            "highestSystemLoad=" + highestSystemLoad +
            ", qps=" + qps +
            ", avgRt=" + avgRt +
            ", maxThread=" + maxThread +
            "}";
    }
}
