#pragma once    

#include <chrono>
#include <map>
#include <string>

#include "File.h"  
#include "Utils.h"  
#include "Global.h" 

enum TaskState {
	Inactive, //0 
	Waiting,  //1 
	WorkspaceCreated, //2 
	WorkspaceReady, //3 
	Running,  //4 
	Canceled,  //5 
	Finished,  //6 
	FinishedAbortedByTimeout, //7  
	FinishedAbortedByUser,  //8 
	Done, //9 
	DoneWithErrors, //10 
	AbortedByTimeout, //11 
	AbortedByUser,  //12 
	Crushed,  //13 
	WrongTestFormat, //14    
	InternalError,  //15 
	Queued, //16 
	NoSuchTask, //17  
	FailedToQueue,  //18 
	AbortingByUser,  //19 
	Unknown
};

enum TestType {
	Default, //0
	Correctness, //1
	Performance, //2
};

class Task {
protected:
	int id;

	int maxtime;
	int kernels; //получение зависит от типа задачи.
	String workspace;
	TaskState state = Unknown;
	std::chrono::system_clock::time_point start_time;
	double total_time;

public:
	String printState() {
		switch (state) {
		case Inactive:
			return String("Inactive");
		case Waiting:
			return String("Waiting");
		case WorkspaceCreated:
			return String("WorkspaceCreated");
		case WorkspaceReady:
			return String("WorkspaceReady");
		case Running:
			return String("Running");
		case Canceled:
			return String("Canceled");
		case Finished:
			return String("Finished");
		case FinishedAbortedByTimeout:
			return String("FinishedAbortedByTimeout");
		case FinishedAbortedByUser:
			return String("FinishedAbortedByUser");
		case Done:
			return String("Done");
		case DoneWithErrors:
			return String("DoneWithErrors");
		case AbortedByTimeout:
			return String("AbortedByTimeout");
		case AbortedByUser:
			return String("AbortedByUser");
		case Crushed:
			return String("Crushed");
		case WrongTestFormat:
			return String("WrongTestFormat");
		case InternalError:
			return String("InternalError");
		case Queued:
			return String("Queued");
		case NoSuchTask:
			return String("NoSuchTask");
		case FailedToQueue:
			return String("FailedToQueue");
		case AbortingByUser:
			return String("AbortingByUser");
		case Unknown:
			return String("?");
		default:
			return "?";
		}
	}

	//-------------->> 
	void setStart()     { start_time = std::chrono::system_clock::now(); }	
	double getTotalTime() const { return total_time; }

	int getId() { return id; }
	int setId(String* id_s) {
		return id = strtol(id_s->getCharArray(), NULL, 10);
	}

	int getMaxtime() { return maxtime; }
	int setMaxtime(String* maxtime_s) { return maxtime = atoi(maxtime_s->getCharArray()); }

	const String& getWorkspace() { return workspace; }

	TaskState getState() { return state; }
	TaskState setState(TaskState state_in) { return state = state_in; }

	Task(Text* lines, int offset) {
		setId(lines->get(offset));
		setMaxtime(lines->get(offset + 1));
		workspace = packageWorkspace + "/" + String(id);
	}

	Task(const nlohmann::json& data) {
		this->id = Utils::getValue<int>(data, "id");
		this->maxtime = Utils::getValue<int>(data, "maxtime");

		workspace = packageWorkspace + "/" + String(id);
	}

	virtual void print() const = 0;
	//- 	 
	virtual void prepareWorkspace() {}
	virtual String getLaunchScriptText() = 0;
	virtual String getStartCommand() {
		String res = workspace + "/run";
        if (!PATH.isEmpty())
        	res = "PATH="+String::DQuotes(PATH)+ " " + res;
        if (!LD_LIBRARY_PATH.isEmpty())
             res = "LD_LIBRARY_PATH="+String::DQuotes(LD_LIBRARY_PATH)+ " " + res;
		return res;
	}
	void createWorkspace() {
		Utils::Mkdir(workspace);
	}
	void createLaunchScript() {
		String launchScriptPath = workspace + "/run";
		String launchScriptText =
			String("cd ") + String::DQuotes(workspace) + "\n" +
			getLaunchScriptText();
		File launchScriptFile = File(launchScriptPath, launchScriptText);
		Utils::Chmod(launchScriptPath);
	}

	int getKernels() const { return kernels; }

	void Start(bool dontCheck = false) {		
		if (kernels <= freeKernels || dontCheck) {
			setStart();
			system(getStartCommand().getCharArray());
			state = Running;
			//- 
			busyKernels = Utils::min(busyKernels + kernels, maxKernels);
			freeKernels = Utils::max(0, maxKernels - busyKernels);
			//- 
		}
	}

	//return 'true' if done, 'false' - if running
	virtual bool Check() {
		if (Utils::Exists(workspace + "/DONE"))
			analyseResults();
		else {
			if (Utils::Exists(workspace + "/TIMEOUT")) {
				state = AbortedByTimeout;
				//todo определить по интервалу времени на всякий случай.
			}
			else if (Utils::Exists(workspace + "/INTERRUPT")) {
				state = AbortedByUser;
			}
			else {
				auto end_time = std::chrono::system_clock::now();
				std::chrono::duration<double> diff_time = end_time - start_time;

				if (maxtime * 1.1 < diff_time.count()) {
#if DEB
					printf("SET STATUS ABORTED by timer to %ld with time %f sec / %f sec", id, diff_time.count(), maxtime * 1.1);
#endif
					state = AbortedByTimeout;
				}
			}
		}
		if (state != Running) {
			busyKernels = Utils::min(busyKernels - kernels, maxKernels);
			freeKernels = Utils::max(0, maxKernels - busyKernels);
		}
		return (state != Running);
	}

	virtual void analyseResults() {
		auto end_time = std::chrono::system_clock::now();
		std::chrono::duration<double> diff_time = end_time - start_time;
		total_time = diff_time.count();
#if DEB
		printf("%ld done with time %f\n", id, total_time);
#endif
		state = Finished;
	}
    virtual String copyResults(const String& pathRes) {
		String resultPath(packageWorkspace + "/" + pathRes + "/" + getId());
		Utils::Mkdir(resultPath);
		Utils::Copy(workspace + "/out.txt", resultPath + "/out.txt");
		Utils::Copy(workspace + "/err.txt", resultPath + "/err.txt");
		return resultPath;
    }
    void kill(){
      //--
      String("killing task "+id).println();
      String pid_path(workspace+"/PID");
      if (Utils::Exists(pid_path)){
          File pid_file(&pid_path);
          String pid = pid_file.read();
          if (!pid.isEmpty()){
             String kill_command = "kill -2 "+ pid;
             kill_command.println();
             system(kill_command.getCharArray());
          }
      }
      //--
    }		
};
