package electrosphere.engine.threads;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import electrosphere.client.terrain.foliage.FoliageModel;
import electrosphere.engine.Globals;
import electrosphere.engine.loadingthreads.LoadingThread;
import electrosphere.engine.threads.LabeledThread.ThreadLabel;
import electrosphere.entity.types.terrain.BlockChunkEntity;
import electrosphere.entity.types.terrain.TerrainChunk;
import electrosphere.server.datacell.Realm;
import electrosphere.util.CodeUtils;

/**
 * Manages all running threads
 */
public class ThreadManager {
    
    //Threadsafes the manager
    Semaphore threadLock;

    //All threads that are actively running
    private List<LabeledThread> activeThreads;

    //All loading threads that are actively running
    private List<LoadingThread> loadingThreads;

    //Used by main thread to alert other threads whether they should keep running or not
    private boolean shouldKeepRunning;


    /**
     * Initializes the thread manager
     */
    public void init(){
        threadLock = new Semaphore(1);
        activeThreads = new LinkedList<LabeledThread>();
        loadingThreads = new LinkedList<LoadingThread>();
        shouldKeepRunning = true;
    }

    /**
     * Updates what threads are being tracked
     */
    public void update(){
        threadLock.acquireUninterruptibly();
        //
        //remove loading threads
        List<Thread> threadsToRemove = new LinkedList<Thread>();
        for(LoadingThread thread : loadingThreads){
            if(thread.isDone()){
                threadsToRemove.add(thread);
            }
        }
        for(Thread thread : threadsToRemove){
            loadingThreads.remove(thread);
        }

        threadLock.release();
    }

    /**
     * Starts a new thread with tracking
     * @param thread The thread to start
     */
    public void start(ThreadLabel label, Thread thread){
        threadLock.acquireUninterruptibly();
        activeThreads.add(new LabeledThread(label, thread));
        thread.start();
        threadLock.release();
    }

    /**
     * Starts a new loading thread with tracking
     * @param thread The loading thread to start
     */
    public void start(LoadingThread thread){
        threadLock.acquireUninterruptibly();
        activeThreads.add(new LabeledThread(ThreadLabel.LOADING, thread));
        loadingThreads.add(thread);
        thread.start();
        threadLock.release();
    }

    /**
     * Checks if any loading threads are active
     * @return true if there is an active loading thread, false otherwise
     */
    public boolean isLoading(){
        return loadingThreads.size() > 0;
    }

    /**
     * Gets the list of loading threads
     * @return The list of loading threads
     */
    public List<LoadingThread> getLoadingThreads(){
        return Collections.unmodifiableList(loadingThreads);
    }

    /**
     * Tries to close all threads
     */
    public void close(){
        this.shouldKeepRunning = false;
        threadLock.acquireUninterruptibly();

        //for some reason, server must be explicitly closed
        if(Globals.server != null){
            Globals.server.close();
        }

        if(Globals.realmManager != null && Globals.realmManager.getRealms() != null){
            for(Realm realm : Globals.realmManager.getRealms()){
                if(realm.getServerWorldData() != null && realm.getServerWorldData().getServerTerrainManager() != null){
                    realm.getServerWorldData().getServerTerrainManager().closeThreads();
                }
                if(realm.getServerWorldData() != null && realm.getServerWorldData().getServerBlockManager() != null){
                    realm.getServerWorldData().getServerBlockManager().closeThreads();
                }
                if(realm.getDataCellManager() != null){
                    realm.getDataCellManager().halt();
                }
            }
        }

        /**
         * Halds all terrain chunk threads
         */
        TerrainChunk.haltThreads();
        FoliageModel.haltThreads();
        BlockChunkEntity.haltThreads();

        //
        //interrupt all threads
        for(int i = 0; i < 3; i++){
            for(LabeledThread thread : activeThreads){
                thread.getThread().interrupt();
                try {
                    thread.getThread().join(10);
                    if(thread.getThread().isAlive()){
                        String errorMessage = "Failed to interrupt thread! " + thread.getLabel();
                        System.err.println(errorMessage);
                        throw new Error(errorMessage);
                    }
                } catch (InterruptedException e) {
                    CodeUtils.todo(e, "Think about how to handle this");
                }
            }
            try {
                TimeUnit.MILLISECONDS.sleep(3);
            } catch (InterruptedException e) {
                CodeUtils.todo(e, "Handle failing to sleep while interrupting all other threads");
            }
        }
        threadLock.release();
    }

    /**
     * Checks if the thread should keep running or not
     * @return true if should keep running, false otherwise
     */
    public boolean shouldKeepRunning(){
        return this.shouldKeepRunning;
    }

    /**
     * Interrupts all thread under a label
     * @param label The label
     */
    public void interruptLabel(ThreadLabel label){
        threadLock.acquireUninterruptibly();
        //
        //interrupt threads
        for(LabeledThread thread : activeThreads){
            if(thread.getLabel() == label){
                thread.getThread().interrupt();
            }
        }
        threadLock.release();
    }

}
