package eu.gity.jmeter.ddos.ntpflood;

import eu.gity.jmeter.ddos.common.Trafgen;
import eu.gity.jmeter.ddos.common.TrafgenControl;
import eu.gity.jmeter.ddos.common.TrafgenControl.TrafgenThread;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.testelement.property.PropertyIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

/**
 * This Test Element class stores properties of NTP flood DoS attack and starts trafgen generator using instance of TrafgenControl class
 *
 * @author Gity a.s.
 */
public class NtpFloodSampler extends AbstractSampler implements TestStateListener, Trafgen {

    public static final String DMAC = "NtpFloodSampler.dmac";
    public static final String SMACSINGLE = "NtpFloodSampler.smac";
    public static final String SMACMIN = "NtpFloodSampler.smacmin";
    public static final String SMACMAX = "NtpFloodSampler.smacmax";
    public static final String INCREMENTALMAC = "NtpFloodSampler.incrementalMac";
    public static final String TARGETIP = "NtpFloodSampler.targetIP";
    public static final String SOURCEIP = "NtpFloodSampler.sourceIP";
    public static final String TTL = "NtpFloodSampler.ttl";
    public static final String SOURCEPORT = "NtpFloodSampler.sourcePort";
    public static final String DPORT = "NtpFloodSampler.dPort";
    public static final String NUMBER = "NtpFloodSampler.number";
    public static final String RATE = "NtpFloodSampler.rate";
    public static final String INTERF = "NtpFloodSampler.interface";
    //Following properties are calculated for direct use to trafgen config and are not synced back to GUI
    public static final String SMACTG = "NtpFloodSampler.smacTG";
    public static final String SOURCEPORTTG = "NtpFloodSampler.sourcePortTG";
    public static final String SOURCEIPTG = "NtpFloodSampler.sourceIPTG";
    public static final String TARGETIPTG = "NtpFloodSampler.targetIPTG";
    public static final String DESTPORTTG = "NtpFloodSampler.destPortTG";
    public static final String TTLTG = "NtpFloodSampler.ttlTG";
    //This property is set from DDoSThreadGroup and is just forwarded to TrafgenControl
    public static final String DYNAMICRATE = "NtpFloodSampler.dynamicRate";
    public static final String configFileSuffix = "NtpFlood.cfg";
    private static final long serialVersionUID = 240L;
    private static final Logger log = LoggerFactory.getLogger(NtpFloodSampler.class);
    private transient TrafgenControl trafgenControl;

    /**
     * Constructs trafgenControl object if not done yet
     */
    void checkInit() {
        if (trafgenControl == null) {
            trafgenControl = new TrafgenControl(configFileSuffix, this);
        }
    }

    /**
     * Syncing of the information to GUI do disable/enable specific options
     *
     * @return true if dynamicRate will be used
     */
    boolean isDynamicRate() {
        return trafgenControl.isDynamicRate();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setDynamicRate(boolean dynamic) {
        this.setProperty(DYNAMICRATE, dynamic);
        trafgenControl.setDynamicRate(dynamic);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SampleResult sample(Entry e) {
        if (this.getPropertyAsString(INTERF).isEmpty()) {
            return null; //This error is already reported to log in DDoSUtils class
        }

        SampleResult res = new SampleResult();
        res.setSampleLabel(getName());
        res.sampleStart();
        res.setDataType(SampleResult.TEXT);
        int result = trafgenControl.runTrafgen();
        switch (result) {
            case -3:    //Trafgen has finished (static rate)
                log.debug("Trafgen has finished generating predefined amount of packets. Stopping JMeter thread.");
                res.setResponseOK();
                this.getThreadContext().getThread().stop();
                break;
            case -2:    //Rate is constant;
                res.setResponseOK();
                break;
            case -1:
                log.error("There was problem with Trafgen configuration file - see details above.");
                res.setResponseMessage("There was problem with Trafgen configuration file - see log for details.");
                break;
            case 0:
                log.debug("Rate set to 0 - terminating generator");
                res.setResponseOK();
                break;
            default:
                log.debug("Packet generation rate changed to " + result);
                res.setResponseOK();
        }
        res.sampleEnd();

        try {
            //Prevents generation of excessive amounts of samples
            TimeUnit.MILLISECONDS.sleep(50);
        } catch (InterruptedException e1) {
            //Never mind
        }
        return res;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void stopTrafgen() {
        trafgenControl.terminateTrafgen(false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void checkPacketCount(String tgName, long duration) {
        trafgenControl.verifyPacketCount(tgName, duration, this.getPropertyAsLong(RATE), this.getPropertyAsLong(NUMBER));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void changeRate(int rateChange) {
        trafgenControl.changeRate(rateChange);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void testStarted() {
        checkInit();
        trafgenControl.generateConfig();
        trafgenControl.setDynamicRate(this.getPropertyAsBoolean(DYNAMICRATE));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void testStarted(String host) {
        testStarted();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void testEnded() {
        TrafgenThread tt = trafgenControl.getTrafgenThread();
        if (tt == null) {
            return;
        }
        try {
            tt.join();
        } catch (InterruptedException e) {
            log.error("The sampler was interrupted while waiting for trafgen thread to finish:\n" + e.getStackTrace().toString());
        }
        if (!tt.getResult()) {
            log.error("A problem occured while generating traffic");
        }
        trafgenControl.reset();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void testEnded(String host) {
        testEnded();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object clone() {
        try {
            TestElement clonedElement = this.getClass().newInstance();
            PropertyIterator iter = propertyIterator();
            while (iter.hasNext()) {
                clonedElement.setProperty(iter.next().clone());
            }
            clonedElement.setRunningVersion(isRunningVersion());
            //trafgenControl is a reference to object so changes in trafgenControl are reflected back to original instance
            ((NtpFloodSampler) clonedElement).trafgenControl = this.trafgenControl;

            return clonedElement;
        } catch (InstantiationException | IllegalAccessException e) {
            throw new AssertionError(e); // clone should never return null
        }
    }
}
