package com.ericsson.uecontrol.core;

import android.net.TrafficStats;
import android.os.Handler;

import com.ericsson.uecontrol.core.util.AbstractElementAdapter;
import com.ericsson.uecontrol.core.util.CSVWriter;
import com.ericsson.uecontrol.core.util.ThroughputCalculator;
import com.ericsson.uecontrol.gui.MainActivity;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

import org.apache.log4j.Logger;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by ezivkoc on 2014-07-15.
 */
public class UeControlExecutor implements Runnable{
    private static final Logger log = Logger.getLogger(UeControlExecutor.class);

    private ArrayList<UeBehaviour> behaviours;
    private UeBehaviour currentlyActive;
    private boolean terminate;
    private Thread thread;
    private String csvPath;
    private CSVWriter csvLogger;

    private Handler handler;
    private ThroughputUpdateRunnable throughputUpdateRunnable;
    private ThroughputListener throughputListener;
    private ExecutionListener execListener;


    public UeControlExecutor(){
        behaviours = new ArrayList<UeBehaviour>();
        handler = new Handler();
        throughputUpdateRunnable = new ThroughputUpdateRunnable();
    }

    public void addBehaviour(UeBehaviour b){
        behaviours.add(b);
    }

    public void read(String file) throws Exception {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(UeBehaviour.class, new AbstractElementAdapter<UeBehaviour>())
                .create();
        FileReader in = new FileReader(file);
        Type type = new TypeToken<ArrayList<UeBehaviour>>(){}.getType();
        ArrayList<UeBehaviour> newBehaviours = gson.fromJson(in, type);
        for(UeBehaviour behaviour : newBehaviours){
            this.addBehaviour(behaviour);
        }
        in.close();
    }
    public void save(String file) throws IOException {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(UeBehaviour.class, new AbstractElementAdapter<UeBehaviour>())
                .create();
        FileWriter out = new FileWriter(file);
        Type type = new TypeToken<ArrayList<UeBehaviour>>(){}.getType();
        log.debug("Saving behaviours: "+gson.toJson(behaviours, type));
        gson.toJson(behaviours, type, out);
        out.close();
    }



    public synchronized void execute(){
        if(!isRunning()) {
            // Start throughput reader scheduler
            handler.postDelayed(throughputUpdateRunnable, 100);

            // Start Execution
            terminate = false;
            thread = new Thread(this);
            thread.start();
            synchronized (thread){
                try { thread.wait(); }catch(InterruptedException e) {log.error(null,e);}
            }
        }
        // Sleep for some time to get correct status
        try{ Thread.sleep(20); }catch(Exception e){log.error(null,e);}
    }

    public void terminateNonBlock(){
        terminate = true;
        handler.removeCallbacks(throughputUpdateRunnable);
        if(currentlyActive != null)
            currentlyActive.terminate();
    }
    public synchronized void terminate(){
        terminateNonBlock();

        // Wait for the execution to finnish
        if(isRunning()){
            synchronized (thread){
                try {
                    thread.wait();
                    while(isRunning()) // Just to make sure it has stopped
                        Thread.sleep(100);
                }catch(InterruptedException e) {log.error(null,e);}
            }
        }
    }

    public synchronized void reset(){
        currentlyActive = null;
        for(int i=0; i<behaviours.size(); i++){
            behaviours.get(i).reset();
        }
    }



    public boolean isRunning(){
        return thread != null && thread.isAlive();
    }

    public void run(){
        log.info("Starting execution");
        if(execListener != null)
            execListener.executionStarted();
        if(csvPath != null)
            csvLogger = new CSVWriter(csvPath);
        while(!terminate) {
            if(behaviours.isEmpty()) {
                currentlyActive = null;
                terminateNonBlock();
                synchronized (thread){thread.notifyAll();}
                break;
            }
            else if (currentlyActive == null)
                currentlyActive = behaviours.get(0);

            // Notify threads that started execution that we are now running
            synchronized (thread){
                currentlyActive.preRun();
                thread.notifyAll();
            }

            // Run behaviour
            UeBehaviour execBehaviour = currentlyActive;
            log.info("Running behaviour: " + execBehaviour.getName());
            execBehaviour.run(this);

            // Before deciding on the next behaviour check if currentlyActive
            // has been changed by the previous behaviour
            if(!terminate && execBehaviour == currentlyActive) {
                int index = behaviours.indexOf(currentlyActive) + 1;
                if (index < behaviours.size())
                    currentlyActive = behaviours.get(index);
                else
                    currentlyActive = null;
            }
        }
        if(csvLogger != null){
            csvLogger.flush();
            csvLogger = null;
        }
        log.info("Execution completed");
        if(execListener != null)
            execListener.executionStopped();
        synchronized (thread){thread.notifyAll();}
    }

    public void setNextBehaviour(int i) {
        if(0 <= i && i < behaviours.size())
            currentlyActive = behaviours.get(i);
    }

    /**
     * Set the log path or null to disable logging
     */
    public void setLogPath(String path){
        csvPath = path;
    }
    public void setThroughputListener(ThroughputListener listener){
        throughputListener = listener;
    }
    public void setExecutionListener(ExecutionListener listener){
        execListener = listener;
    }
    public void setDeviceBasedThroughput(boolean enable){
        throughputUpdateRunnable.deviceBasedThroughput = enable;
    }

    public List<UeBehaviour> getBehaviourList() {
        return behaviours;
    }
    public UeBehaviour getRunningBehaviour(){
        return currentlyActive;
    }
    public CSVWriter getCsvLogger() {
        return csvLogger;
    }



    public static interface ExecutionListener {
        public void executionStarted();
        public void executionStopped();
    }


    public static interface ThroughputListener{
        public void throughputUpdate(double downThroughput, double upThroughput);
    }

    private class ThroughputUpdateRunnable implements Runnable{
        protected boolean deviceBasedThroughput;

        private long previousRxBytes = TrafficStats.UNSUPPORTED;
        private long previousTxBytes = TrafficStats.UNSUPPORTED;
        private ThroughputCalculator downloadSpeed;
        private ThroughputCalculator uploadSpeed;


        public ThroughputUpdateRunnable(){
            downloadSpeed = new ThroughputCalculator();
            uploadSpeed = new ThroughputCalculator();
            deviceBasedThroughput = false;
        }

        public void run(){
            if (!setRealDataUsage()) {
                uploadSpeed.setHandledData(0);
                downloadSpeed.setHandledData(0);
            }
            if (uploadSpeed.isUpdated()) {
                if(csvLogger != null)
                    csvLogger.write(getRunningBehaviour().getName(),
                            downloadSpeed.getBitThroughput(),
                            uploadSpeed.getBitThroughput());
                if (throughputListener != null)
                    throughputListener.throughputUpdate(
                            downloadSpeed.getBitThroughput(),
                            uploadSpeed.getBitThroughput());
            }
            // Rescedule this handler
            if(!terminate)
                handler.postDelayed(this, 100);
        }

        protected boolean setRealDataUsage(){
            boolean ret = false;
            long currentRxBytes = 0;
            long currentTxBytes = 0;
            if(deviceBasedThroughput) {
                currentRxBytes = TrafficStats.getTotalRxBytes();
                currentTxBytes = TrafficStats.getTotalTxBytes();
            }
            else{
                currentRxBytes = TrafficStats.getUidRxBytes(MainActivity.getUID());
                currentTxBytes = TrafficStats.getUidTxBytes(MainActivity.getUID());
            }

            if (currentRxBytes != TrafficStats.UNSUPPORTED && currentTxBytes != TrafficStats.UNSUPPORTED
                    && previousRxBytes != TrafficStats.UNSUPPORTED && previousTxBytes != TrafficStats.UNSUPPORTED){
                downloadSpeed.setHandledData(currentRxBytes - previousRxBytes);
                uploadSpeed.setHandledData(currentTxBytes-previousTxBytes);
                ret = true;
            }
            previousRxBytes = currentRxBytes;
            previousTxBytes = currentTxBytes;
            return ret;
        }
    }
}
