/*                          
 * mBench: The Open Source Micro Benchmark Tool 
 *                                             
 * Distributable under GPL license. 
 * See terms of license at gnu.org.                 
 *
 * Copyright (C) 2005 Sumisho Computer Systems Corp.
 */
package jp.co.scs.mbench;

/**
 *      Container for emulating user client.
 * 
 *      @author Tetsuro Ikeda
 *      @author Masato Koga
 */
public class BenchmarkContainer extends Thread {
    /**
     *  default labels
     */
    public static final String[] SYSTEM_LABELS = { 
        "ContainerID", "repeatNumber", "startTime", "endTime", "responseTime"};
    
    /**
     *  current state
     */
    protected String currentState = BenchmarkState.CREATED;
    
    /**
     *  flag for if this container is already initialized
     */
    protected boolean isInitialized = false;
    
    /**
     *  container ID
     */
    private int containerID = 0;
    
    /**
     *  Information of benchmark senario
     */
    private BenchmarkInfo info = null;
    
    /**
     *  benchmark component
     */
    private Benchmark benchmark = null;
    
    /**
     *  the object to synchronize each container
     */
    private Synchronizer synchronizer = null;
    
    /**
     *  start time for current benchmark iteration
     */
    private long startTime = System.currentTimeMillis();
    
    /** 
     *  end time for current benchmark iteration
     */
    private long endTime = System.currentTimeMillis();
    
    /**
     *  "log" writer instance
     */
    private LogWriter logWriter = null;
    
    /**
     *  "data" writer instance
     */
    private DataWriter dataWriter = null;
    
    /**
     *  repeat number if this container get suspended
     */
    private int threadIterationCount = 0;
    
    /**
     *  Create a instance of this class
     * 
     *  @param containerID container ID
     *  @param info Information of benchmark senario
     *  @param synchronizer the object to synchronize each container
     *  @param logWriter "log" writer instance
     *  @param dataWriter "data" writer instance
     */
    public BenchmarkContainer(int containerID, BenchmarkInfo info,
            Synchronizer synchronizer, LogWriter logWriter, DataWriter dataWriter) {
        this.containerID = containerID;
        this.info = info;
        this.synchronizer = synchronizer;
        this.logWriter = logWriter;
        this.dataWriter = dataWriter;
    }
    
    /**
     *  gets the container ID.
     * 
     *  @return container ID
     */
    public int getContainerID() {
        return this.containerID;
    }
    
    /**
     *  sets the log writer instance.
     * 
     *  @param logWriter log writer instance
     */
    public void setLogWriter(LogWriter logWriter) {
        this.logWriter = logWriter;
    }
    
    /**
     *  sets the data writer instance.
     * 
     *  @param dataWriter data writer instance.
     */
    public void setDataWriter(DataWriter dataWriter) {
        this.dataWriter = dataWriter;
    }
    
    /**
     *  gets the data writer instance.
     * 
     *  @return data writer instance
     */
    public DataWriter getDataWriter() {
        return this.dataWriter;
    }
    
    /**
     *  gets the start time of current benchmark iteration.
     * 
     *  @return start time
     */
    public long getStartTime() {
        return this.startTime;
    }
    
    /**
     *  gets the end time of current benchmark iteration.
     * 
     *  @return end time
     */
    public long getEndTime() {
        return this.endTime;
    }
    
    /**
     *  gets the flag of if this container is already initialized.
     * 
     *  @return flag for if this container is already initialized
     */
    public boolean isInitialized() {
        return this.isInitialized;
    }
    
    /**
     *  gets the current state of this container
     * 
     *  @return current state of this container
     */
    public String getCurrentState() {
        return this.currentState;
    }
    
    /**
     *  sets the flag of if this container is already initialized.
     * 
     *  @param isInitialized flag of if this container is already initialized.
     */
    public void setInitialized(boolean isInitialized) {
        this.isInitialized = isInitialized;
    }
    
    /** 
     *  creates the benchmark component instance and initialize it by using
     *  benchmark infomation.
     * 
     *  @throws BenchmarkTerminateException exception for stop whole benchmark
     */
    public void initTarget() throws BenchmarkTerminateException {
        try {
            this.currentState = BenchmarkState.INITIALIZING;
            Class benchmarkClass = Class.forName(this.info.getClassName());
            Object benchmarkObject = benchmarkClass.newInstance();
            this.benchmark = (Benchmark) benchmarkObject;
            this.benchmark.setBenchmarkName(this.info.getName());
            this.benchmark.init();
            this.currentState = BenchmarkState.INITIALIZED;
            this.isInitialized = true;
            this.logWriter.write(LogWriter.INFO,
                "[BenchmarkContainer] containerID = "
                    + this.containerID + ", initialize OK");
        } catch (ClassNotFoundException ex) {
            this.currentState = BenchmarkState.BENCHMARK_TERMINATED;
            this.logWriter.write(LogWriter.ERROR,
                "[BenchmarkContainer] containerID = "
                    + this.containerID + ", initialize ERROR \n"
                    + ex.getMessage());
            throw new BenchmarkTerminateException(ex);
        } catch (InstantiationException ex) {
            this.currentState = BenchmarkState.BENCHMARK_TERMINATED;
            this.logWriter.write(LogWriter.ERROR,
                "[BenchmarkContainer] containerID = "
                    + this.containerID + ", initialize ERROR \n"
                    + ex.getMessage());
            throw new BenchmarkTerminateException(ex);
        } catch (IllegalAccessException ex) {
            this.currentState = BenchmarkState.BENCHMARK_TERMINATED;
            this.logWriter.write(LogWriter.ERROR,
                "[BenchmarkContainer] containerID = "
                    + this.containerID + ", initialize ERROR \n"
                    + ex.getMessage());
            throw new BenchmarkTerminateException(ex);
        }
    }
    
    /**
     *  starts the benchmark.
     */
    public void run() {
        this.synchronizer.doSynchronize();
        this.executeTarget();
    }
    
    /**
     *  executes the benchmark.<br>
     *  <br>
     *  container ID, start time, and end time are recorded
     *  at each benchmark iteration.
     */
    public void executeTarget() {
        // if the benchmark component throws an exception,
        // container will stop the execution of benchmark.
        try {
            this.currentState = BenchmarkState.RUNNING;
            this.logWriter.write(LogWriter.INFO,
                "[BenchmarkContainer] containerID = "
                    + this.containerID + ", execute started");
            while(executeCheck()) {
                this.threadIterationCount++;
                BenchmarkManager.allIterationCount++;
                this.startTime = System.currentTimeMillis();
                // calling benchmark component.
                // if the benchmark component doesn't return, 
                // the container also stops at this step.
                String[] userData = this.benchmark.execute();
                this.endTime = System.currentTimeMillis();
                // if the container is considered as timeout by the benchmark manager,
                // going into this if-statement.
                if (!this.currentState.equals(BenchmarkState.RUNNING)) {
                    return;
                }
                
                String[] resultData = null;
                if (userData != null) {
                    resultData = new String[SYSTEM_LABELS.length + userData.length];
                    for (int j = 0; j < userData.length; j++) {
                        resultData[SYSTEM_LABELS.length + j] = userData[j];
                    }
                } else {
                    resultData = new String[SYSTEM_LABELS.length];
                }
                resultData[0] = String.valueOf(this.containerID);
                resultData[1] = String.valueOf(this.threadIterationCount);
                resultData[2] = String.valueOf(
                    this.startTime - info.getBenchmarkStartTime());
                resultData[3] = String.valueOf(
                    this.endTime - info.getBenchmarkStartTime());
                resultData[4] = String.valueOf(
                    this.endTime - this.startTime);
                
                this.dataWriter.write(resultData);
            }
            this.currentState = BenchmarkState.FINISHED;
            this.logWriter.write(LogWriter.INFO,
                "[BenchmarkContainer] containerID = "
                    + this.containerID + ", execute finished OK");
        } catch (ThreadTerminateException ex) {
            // puts the invalid time to mark this container is going to terminate
            this.endTime = -1;
            this.logWriter.write(LogWriter.ERROR,
                "[BenchmarkContainer] ThreadTerminate containerID = "
                    + this.containerID + ", iteration = "
                    + this.threadIterationCount);
            this.currentState = BenchmarkState.THREAD_TERMINATED;
        } catch (BenchmarkTerminateException ex) {
            // puts the invalid time to mark this container is going to terminate
            this.endTime = -1;
            this.logWriter.write(LogWriter.ERROR,
                "[BenchmarkContainer] BenchmarkTerminateException containerID = "
                    + this.containerID + ", iteration = "
                    + this.threadIterationCount);
            
            this.currentState = BenchmarkState.BENCHMARK_TERMINATED;
        }
    }
    
    /**
     *  Check the assumption for execute container.
     * 
     *  @return flag for execute the container
     */
    private boolean executeCheck() {
        // Check the repeat number of one thread
        if (this.threadIterationCount >= this.info.getRepeatNumber()) {
            return false;
        }
        // Check the executed transactin number of all benchmark
        if (this.info.getTransactionNumber() != 0 
                && BenchmarkManager.allIterationCount >= this.info.getTransactionNumber()){
            return false;
        }
        // Check the time of executed.
        if (this.info.getBenchmarkEndTime() != 0
                && System.currentTimeMillis() - this.info.getBenchmarkStartTime()
                    >= this.info.getBenchmarkEndTime() * 1000){
            return false;
        }
        return true;
    }
    
    /** 
     *  finalizes the benchmark component
     * 
     *  @throws BenchmarkAbortException exception for stop whole benchmark
     */
    public void cleanTarget() throws BenchmarkAbortException {
        this.currentState = BenchmarkState.CLEANING;
        this.benchmark.clean();
        this.currentState = BenchmarkState.CLEANED;
        this.logWriter.write(LogWriter.INFO,
            "[BenchmarkContainer] containerID = "
                + this.containerID + ", clean OK");
    }
}
