/**
 *  
 */
package core.services;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import core.services.ApiService.OKResponseObject;
import core.services.QuestionService.QuestionObjectComplete;
import models.Question;
import models.Slide;
import models.Test;
import models.Test.TestStatus;
import models.Test.TestType;
import models.User;
import play.Logger;
import utils.StringUtil;

/**
 *
 * @author luca
 */
public class TestService {

	private static enum TestServiceSingleton {
		INSTANCE;

		TestService singleton = new TestService();

		public TestService getSingleton() {
			return singleton;
		}
	}

	public static TestService instance() {
		return TestService.TestServiceSingleton.INSTANCE.getSingleton();
	}

	private static ApiService apiService = ApiService.instance();
	private static OptionService optionService = OptionService.instance();
	private static QuestionService questionService = QuestionService.instance();
	private static UserService userService = UserService.instance();

	public static class TestObjectSmall {
		public String TestId;
		public String TestName;
		public String authorId;
		public Boolean toEdit;
		public Boolean deleted;
		public String state;
		public String revId;
		public Map<String, String> options;
		public Map<String, String> questions;
		public String creationDate;
		public String revisionDate;
	}

	public static class TestObjectComplete {

		public String TestId;

		public String TestName;

		public String authorId;

		public Boolean deleted;

		public String state;

		public String revId;

		public Boolean toEdit;

		public List<QuestionObjectComplete> questions;

		public Map<String, String> options;

	}

	public static class CreateTestRequest {
		public TestObjectComplete test;
	}

	public static class CreateTestResponse extends OKResponseObject {
		public TestObjectComplete response;
	}

	public static class UpdateTestResponse extends OKResponseObject {
		public TestObjectSmall response;
	}

	public String createTest(TestType type) {
		Test test = new Test();
		test.setAuthor(userService.getCurrentUserLogged());
		test.setRevisionDate(new Date());
		test.save();
		test.setTestName("NEW_TEST_" + test.getUuid());
		test.setType(type);
		test.save();
		TestObjectComplete testObjectComplete = buildTestObjectCompleteFromTest(test, false);
		CreateTestResponse res = new CreateTestResponse();
		res.response = testObjectComplete;
		String ret = res.toJson();
		return ret;

	}


	public String updateTest(String testId) {
		if (StringUtil.isNil(testId)) {
			return apiService.buildWrongEndpointErrorResponse();
		}

		Test test = Test.findById(testId);
		if (test == null) {
			return apiService.buildEntityDoesNotExistsErrorResponse();
		}

		if (!userService.hasCurrentUserLoggedAdminRole()) {
			if (test.getDeleted()) {
				return apiService.buildEntityDoesNotExistsErrorResponse();
			}
		}

		if (!userService.hasCurrentUserLoggedAdminRole()) {
			User currentUserLogged = userService.getCurrentUserLogged();
			if (!currentUserLogged.equals(test.getAuthor())) {
				return apiService.buildNotAuthorizedResponse();
			}
		}

		String json = apiService.getCurrentJson();
		CreateTestRequest req;
		TestObjectComplete testToUpdate;
		try {
			req = (CreateTestRequest) apiService.buildObjectFromJson(json, CreateTestRequest.class);
			if (req == null) {
				return apiService.buildNotWellFormedJsonErrorResponse();
			}
			testToUpdate = req.test;
			if (testToUpdate == null) {
				return apiService.buildNotWellFormedJsonErrorResponse();
			}
		} catch (Throwable ex) {
			Logger.error(ex, "Error...");
			return apiService.buildNotWellFormedJsonErrorResponse();
		}

		// potrebbero arrivare stati non presenti nella enum
		TestStatus testStatus = TestStatus.tryBuildTestStatusFromName(testToUpdate.state);
		if (testStatus != null) {
			test.setState(testStatus);
		} else {
			return apiService.buildWrongTestStatusFieldErrorResponse();
		}

		if (testToUpdate.deleted != null) {
			test.setDeleted(testToUpdate.deleted);
		}

		if (!StringUtil.isNil(testToUpdate.TestName)) {
			test.setTestName(testToUpdate.TestName);
		}

		if (testToUpdate.options != null) {
			optionService.updateOptions(test, testToUpdate.options);
		}

		// DISABILITATO PERCHE' DALL'INTERFACCIA UTENTE NON C'E MODO DI EDITARE
		// LE QUESTIONS
		// ALL'ATTO DELLA MODIFICA DI UN TEST (SE SI RIATTIVA OCCORRE TESTARE E,
		// SOPRATTUTTO,
		// EFFETTUARE LA VALIDAZIONE DI OGNI SOTTOOGGETTO [Es. SlideObject,
		// etc.]!)
		// if (testToUpdate.questions != null) {
		// questionService.updateQuestions(test, testToUpdate.questions);
		// }

		// TODO creare una nuova entity con new Date a revision date e mettere a
		// deleted la precedente + legare i vari test con un id comune
		// (transazionale)
		test.setRevisionDate(new Date());
		test.save();

		UpdateTestResponse res = new UpdateTestResponse();
		res.response = buildTestObjectSmallFromTest(test);
		String ret = res.toJson();
		return ret;
	}

	public static class TestListResponse extends OKResponseObject {
		public List<TestObjectSmall> response;
	}

	public String testList(TestType type) {
		return testList(type, "testName", "ASC");
	}
	
	public String testList(TestType type, String orderby, String ordertype) {
		Logger.info("test list:" + type.name() + " " + orderby);
		List<Test> testObjs = new LinkedList<Test>();
		if (userService.hasCurrentUserLoggedAdminRole()) {
			testObjs = Test.findAllOrdered(type, orderby, ordertype);
		} else {
			testObjs = Test.findAllByDeletedOrdered(false, type, orderby, ordertype);
		}
		List<TestObjectSmall> testObjsSmall = buildTestObjectSmallListFromTestList(testObjs);
		TestListResponse res = new TestListResponse();
		res.response = testObjsSmall;
		String ret = res.toJson();
		return ret;
	}

	public List<TestObjectSmall> buildTestObjectSmallListFromTestList(List<Test> tests) {
		List<TestObjectSmall> testObjsSmall = new LinkedList<TestObjectSmall>();
		if (tests != null && !tests.isEmpty()) {
			for (Test test : tests) {
				TestObjectSmall testObjSmall = buildTestObjectSmallFromTest(test);
				testObjsSmall.add(testObjSmall);
			}
		}
		return testObjsSmall;
	}

	public static class GetTestObjectSmallResponse extends OKResponseObject {
		public TestObjectSmall response;
	}

	public static class GetTestObjectCompleteResponse extends OKResponseObject {
		public TestObjectComplete response;
	}

	public String getTest(String testId, Boolean complete, Boolean isPlay) {
		if(StringUtil.isNil(isPlay))
			isPlay = false;
		if (StringUtil.isNil(testId)) {
			return apiService.buildMandatoryParamsErrorResponse();
		}

		Test test = Test.findById(testId);
		if (test == null) {
			return apiService.buildEntityDoesNotExistsErrorResponse();
		}

		if (!userService.hasCurrentUserLoggedAdminRole()) {
			if (test.isDeleted()) {
				return apiService.buildEntityDoesNotExistsErrorResponse();
			}
		}

		if (complete == null) {
			return apiService.buildMandatoryParamsErrorResponse();
		}

		String ret;
		if (complete) {
			GetTestObjectCompleteResponse res = new GetTestObjectCompleteResponse();
			res.response = buildTestObjectCompleteFromTest(test, isPlay);
			ret = res.toJson();
		} else {
			GetTestObjectSmallResponse res = new GetTestObjectSmallResponse();
			res.response = buildTestObjectSmallFromTest(test);
			ret = res.toJson();
		}
		return ret;
	}

	public TestObjectSmall buildTestObjectSmallFromTest(Test test) {
		TestObjectSmall ret = new TestObjectSmall();
		ret.authorId = test.getAuthor().getUuid();
		ret.TestId = test.getUuid();
		ret.TestName = test.getTestName();
		if (userService.hasCurrentUserLoggedAdminRole()) {
			ret.toEdit = true;
		} else {
			User currentUserLogged = userService.getCurrentUserLogged();
			if (currentUserLogged.equals(test.getAuthor())) {
				ret.toEdit = true;
			} else {
				ret.toEdit = false;
			}
		}
		ret.deleted = test.isDeleted();
		ret.state = test.getState().name();
		ret.options = optionService.buildOptionsMapFromOptionsList(test.getOptions());
		ret.questions = questionService.buildOptionsMapFromQuestionsList(test.getQuestions());
		ret.creationDate = StringUtil.date(test.getCreationDate(), Test.CREATION_DATE_FORMAT);
		ret.revisionDate = StringUtil.date(test.getRevisionDate(), Test.CREATION_DATE_FORMAT);
		ret.revId = test.buildRevisionDateFormatted();
		return ret;
	}

	public TestObjectComplete buildTestObjectCompleteFromTest(Test test, Boolean isPlay) {
		TestObjectComplete ret = new TestObjectComplete();
		if (test != null) {
			ret.authorId = test.getAuthor().getUuid();
			ret.deleted = test.isDeleted();
			ret.options = optionService.buildOptionsMapFromOptionsList(test.getOptions());
			ret.questions = questionService.buildQuestionObjectCompleteListFromQuestionList(test.getQuestions(), isPlay);
			ret.revId = test.buildRevisionDateFormatted();
			ret.state = test.getState().name();
			ret.TestId = test.getUuid();
			ret.TestName = test.getTestName();
			if (userService.hasCurrentUserLoggedAdminRole()) {
				ret.toEdit = true;
			} else {
				User currentUserLogged = userService.getCurrentUserLogged();
				if (currentUserLogged.equals(test.getAuthor())) {
					ret.toEdit = true;
				} else {
					ret.toEdit = false;
				}
			}
		}
		return ret;
	}

	public static class CloneTestRequest {
		public String testId;
	}

	public static class CloneTestResponse extends OKResponseObject {
		public TestObjectSmall response;
	}

	public static class DeleteTestResponse extends OKResponseObject {
		public Object response = new Object();
	}

	public String deleteTest(String testId) {
		if (StringUtil.isNil(testId)) {
			return apiService.buildWrongEndpointErrorResponse();
		}

		Test test = Test.findById(testId);
		if (test == null) {
			return apiService.buildEntityDoesNotExistsErrorResponse();
		}

		if (!userService.hasCurrentUserLoggedAdminRole()) {
			User currentUserLogged = userService.getCurrentUserLogged();
			if (!currentUserLogged.equals(test.getAuthor())) {
				return apiService.buildNotAuthorizedResponse();
			}
		}

		if (test.isDeleted()) {
			return apiService.buildEntityAlreadyDeletedErrorResponse();
		}

		test.setDeleted(true);
		test.save();

		DeleteTestResponse res = new DeleteTestResponse();

		String ret = res.toJson();
		return ret;
	}

	public String cloneTest() {

		String json = apiService.getCurrentJson();
		CloneTestRequest req;
		try {
			req = (CloneTestRequest) apiService.buildObjectFromJson(json, CloneTestRequest.class);
			if (req == null) {
				return apiService.buildNotWellFormedJsonErrorResponse();
			}
		} catch (Throwable ex) {
			Logger.error(ex, "Error...");
			return apiService.buildNotWellFormedJsonErrorResponse();
		}

		if (StringUtil.isNil(req.testId)) {
			return apiService.buildNotWellFormedJsonErrorResponse();
		}

		Test toClone = Test.findById(req.testId);
		if (toClone == null) {
			return apiService.buildEntityDoesNotExistsErrorResponse();
		}

		try {
			Test cloned = toClone.clone();
			if (cloned == null) {
				return apiService.buildGenericErrorResponse();
			}
			CloneTestResponse res = new CloneTestResponse();
			TestObjectSmall testObjectSmall = buildTestObjectSmallFromTest(cloned);
			res.response = testObjectSmall;

			String ret = res.toJson();
			return ret;
		} catch (Exception ex) {
			Logger.error(ex, "Exception during Test clonation (test: %s)", req.testId);
			return apiService.buildGenericErrorResponse();
		}

	}

	public static class GetStandaloneTestRequest {

	}

	public static class GetStandaloneTestResponse extends OKResponseObject {

	}

	// TODO
	public GetStandaloneTestResponse getStandaloneTest(GetStandaloneTestRequest req) {
		return null;
	}

	public String sortQuestions() {
		String json = apiService.getCurrentJson();
		try {
			TestOrderQuestionRequest req = (TestOrderQuestionRequest) apiService.buildObjectFromJson(json, TestOrderQuestionRequest.class);
			if (req == null) {
				return apiService.buildNotWellFormedJsonErrorResponse();
			}
			String testToUpdate = req.testId;
			if (testToUpdate == null) {
				return apiService.buildNotWellFormedJsonErrorResponse();
			}
			
			List<Question> tosave = new ArrayList<Question>();
			for(int i = 0; i<req.questions.size(); i++) {
				Question q = Question.findById(req.questions.get(i).question);
				if(q == null)
					return apiService.buildNotWellFormedJsonErrorResponse();
				q.setOrder(req.questions.get(i).order);
				tosave.add(q);
			}
			
			for(Question question : tosave){
				question.save();
			}
			
			return instance().getTest(testToUpdate, true, false);
		} catch (Throwable ex) {
			Logger.error(ex, "Error...");
			return apiService.buildNotWellFormedJsonErrorResponse();
		}
	}
	
	

	public static class TestOrderQuestionRequest {
		String testId;
		List<TestOrderQuestionOrderRequest> questions;
	}
	
	public static class TestOrderQuestionOrderRequest {
		String question;
		Integer order;
	}
	
	public String sortSlides() {
		String json = apiService.getCurrentJson();
		try {
			TestOrderSlidesRequest req = (TestOrderSlidesRequest) apiService.buildObjectFromJson(json, TestOrderSlidesRequest.class);
			if (req == null) {
				return apiService.buildNotWellFormedJsonErrorResponse();
			}
			String questionToUpdate = req.questionId;
			if (questionToUpdate == null) {
				return apiService.buildNotWellFormedJsonErrorResponse();
			}
			Question q = Question.findById(questionToUpdate);
			if (q == null) {
				return apiService.buildNotWellFormedJsonErrorResponse();
			}
			List<Slide> tosave = new ArrayList<Slide>();
			for(int i = 0; i<req.slides.size(); i++) {
				Slide s = Slide.findById(req.slides.get(i).slide);
				if(s == null)
					return apiService.buildNotWellFormedJsonErrorResponse();
				if(!s.getQuestion().equals(q))
					return apiService.buildNotWellFormedJsonErrorResponse();
				s.setOrder(req.slides.get(i).order);
				tosave.add(s);
			}
			
			for(Slide slide : tosave){
				slide.save();
			}
			
			return instance().getTest(q.getTest().getUuid(), true, false);
		} catch (Throwable ex) {
			Logger.error(ex, "Error...");
			return apiService.buildNotWellFormedJsonErrorResponse();
		}
	}
	
	public static class TestOrderSlidesRequest {
		String questionId;
		List<TestOrderSlidesOrderRequest> slides;
	}
	
	public static class TestOrderSlidesOrderRequest {
		String slide;
		Integer order;
	}
	
}