package com.ericsson.uecontrol.core;

import android.net.TrafficStats;

import com.ericsson.uecontrol.core.util.ThroughputCalculator;
import com.ericsson.uecontrol.gui.MainActivity;

import org.apache.log4j.Logger;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

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

    private List<UeBehaviour> behaviours;
    private UeBehaviour currentlyActive;
    private boolean terminate;
    private Thread thread;

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


    public UeControlExecutor(){
        behaviours = new ArrayList<UeBehaviour>();
        downloadSpeed = new ThroughputCalculator();
        uploadSpeed = new ThroughputCalculator();
    }

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

    public void read(String file) throws Exception {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
        int size = in.readInt();
        for(int i=0; i<size; i++){
            addBehaviour((UeBehaviour) in.readObject());
        }
        in.close();
    }
    public void save(String file) throws IOException {
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(file));
        out.writeInt(behaviours.size());
        for(UeBehaviour behaviour : behaviours){
            out.writeObject(behaviour);
        }
        out.close();
    }


    public synchronized void execute(){
        if(!isRunning()) {
            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);}
    }

    private void terminateNonBlock(){
        terminate = true;
        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(10);
                }catch(InterruptedException e) {log.error(null,e);}
            }
        }
    }

    public synchronized void reset(){
        terminate();
        currentlyActive = null;
    }

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

    public void run(){
        log.info("Starting execution");
        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
            log.info("Running behaviour: " + currentlyActive.getName());
            currentlyActive.run();

            if(!terminate) {
                int index = behaviours.indexOf(currentlyActive) + 1;
                if (index < behaviours.size())
                    currentlyActive = behaviours.get(index);
                else
                    currentlyActive = null;
            }
        }
        log.info("Execution completed");
        synchronized (thread){thread.notifyAll();}
    }


    @Override
    public void handledIncomingData(long size) {
        if (!setRealDataUsage()){
            // TrafficStat unsupported so try to use estimation instead
            downloadSpeed.setHandledData(size);
        }
        if (throughputListener != null && downloadSpeed.isUpdated())
            throughputListener.throughputUpdate(downloadSpeed.getBitThroughput(), uploadSpeed.getBitThroughput());
    }
    @Override
    public void handledOutgoingData(long size) {
        if (!setRealDataUsage()) {
            // TrafficStat unsupported so try to us estimation instead
            uploadSpeed.setHandledData(size);
        }
        if (throughputListener != null && uploadSpeed.isUpdated())
            throughputListener.throughputUpdate(downloadSpeed.getBitThroughput(), uploadSpeed.getBitThroughput());
    }
    protected boolean setRealDataUsage(){
        boolean ret = false;
        long currentRxBytes = TrafficStats.getUidRxBytes(MainActivity.getUID());
        long currentTxBytes = TrafficStats.getUidTxBytes(MainActivity.getUID());
        //long currentRxBytes = TrafficStats.getTotalRxBytes();
        //long currentTxBytes = TrafficStats.getTotalTxBytes();

        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;
    }

    public void setThroughputListener(ThroughputListener listener){
        throughputListener = listener;
    }

    public List<UeBehaviour> getBehaviourList() {
        return behaviours;
    }

    public UeBehaviour getRunningBehaviour(){
        return currentlyActive;
    }

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