package models;

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

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Transient;

import core.services.OptionService;
import core.services.QuestionService;
import core.services.UserService;
import models.Test.TestType;
import play.Logger;
import utils.StringUtil;

@Entity
public class Test extends ModelUuid {

	public static enum TestStatus {
		NEW, DRAFT, PUBLISHED;

		public static TestStatus tryBuildTestStatusFromName(String name) {
			if (StringUtil.isNil(name)) {
				return null;
			}

			for (TestStatus testStatus : values()) {
				if (testStatus.name().equals(name)) {
					return testStatus;
				}
			}

			return null;
		}
	}
	
	public static enum TestType {
		TESTINGTOOL, ATLAS;

		public static TestType tryBuildTestTypeFromName(String name) {
			if (StringUtil.isNil(name)) {
				return null;
			}

			for (TestType testType : values()) {
				if (testType.name().equals(name)) {
					return testType;
				}
			}

			return null;
		}
	}

	@Basic
	private String testName;

	@Basic
	@ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH })
	private User author;

	@Basic
	private Boolean deleted = false;

	@Basic
	@Enumerated(EnumType.STRING)
	private TestStatus state = TestStatus.NEW;
	
	@Basic
	@Enumerated(EnumType.STRING)
	private TestType testType;

	@Basic
	private Date revisionDate = new Date();
	
	@Basic
	private Date creationDate = new Date();

	@Transient
	public static final String REVISION_DATE_FORMAT = "dd_MM_yyyy_HH:mm:ss:SSS";
	
	@Transient
	public static final String CREATION_DATE_FORMAT = "yyyy MM dd HH:mm:ss:SSS";

	@OneToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.DETACH })
	private List<Question> questions = new LinkedList<Question>();

	@OneToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE })
	private List<Option> options = new LinkedList<Option>();

	public String getTestName() {
		return testName;
	}

	public void setTestName(String testName) {
		this.testName = testName;
	}

	public User getAuthor() {
		return author;
	}

	public void setAuthor(User author) {
		this.author = author;
	}

	public Boolean isDeleted() {
		return deleted;
	}

	public void setDeleted(Boolean deleted) {
		this.deleted = deleted;
	}

	public TestStatus getState() {
		return state;
	}

	public void setState(TestStatus state) {
		this.state = state;
	}

	public Date getRevisionDate() {
		return revisionDate;
	}

	public String buildRevisionDateFormatted() {
		return StringUtil.date(this.getRevisionDate(), Test.REVISION_DATE_FORMAT);
	}

	public void setRevisionDate(Date revisionDate) {
		this.revisionDate = revisionDate;
	}
	
	public Date getCreationDate() {
		return creationDate;
	}

	public String buildCreationDateFormatted() {
		return StringUtil.date(this.getCreationDate(), Test.REVISION_DATE_FORMAT);
	}

	public void setCreationDate(Date creationDate) {
		this.creationDate = creationDate;
	}
	
	

	public TestType getType() {
		return testType;
	}

	public void setType(TestType type) {
		this.testType = type;
	}

	public List<Question> getQuestions() {
		
		return orderQuestion(questions);
	}
	
	private List<Question> orderQuestion(List<Question> questions) {
		LinkedList<Question> ret = new LinkedList<Question>();
		Question[] tmp = new Question[questions.size()];
		for(Question question : questions){
			tmp[question.getOrder()] = question;
		}
		for(int i=0; i<tmp.length; i++){
			Question question = tmp[i];
			ret.add(question);
		}
		return ret;
	}

	public void setQuestions(List<Question> questions) {
		this.questions = questions;
	}

	public List<Option> getOptions() {
		return options;
	}

	public void setOptions(List<Option> options) {
		this.options = options;
	}

	public Boolean getDeleted() {
		return deleted;
	}

	public boolean addQuestion(Question question) {
		if (question == null) {
			return false;
		}

		if (this.questions.contains(question)) {
			return false;
		}
		question.setOrder(this.questions.size());
		question.save();
		return this.questions.add(question);
	}

	public boolean addOption(Option option) {
		if (option == null) {
			return false;
		}

		if (this.options.contains(option)) {
			return false;
		}
		return options.add(option);
	}

	public static List<Test> findAllByDeleted(Boolean deleted, TestType type) {
		List<Test> ret = new LinkedList<Test>();
		if (deleted == null) {
			ret = Test.find("testType=:type").setParameter("testType", type).fetch();
		} else {
			ret = Test.find("deleted=:deleted and testType=:type").setParameter("deleted", deleted).setParameter("type", type).fetch();
		}
		return ret;
	}
	
	public static List<Test> findAllByDeletedOrdered(Boolean deleted, TestType type, String orderby, String ordertype) {
		if(StringUtil.isNil(orderby) || StringUtil.isNil(ordertype))
			return findAllByDeleted(deleted, type);
		List<Test> ret = new LinkedList<Test>();
		if (deleted == null) {
			ret = Test.find("testType=:type order by :orderby").setParameter("testType", type).setParameter("orderby", orderby).fetch();
		} else {
			ret = Test.find("deleted=:deleted and testType=:type order by "+ orderby +" " +ordertype).setParameter("deleted", deleted).setParameter("type", type).fetch();
		}
		return ret;
	}

	public List<Question> retrieveQuestionsByDeleted(Boolean deleted) {
		List<Question> ret = new LinkedList<Question>();
		if (deleted == null) {
			ret = getQuestions();
		} else {
			for (Question question : getQuestions()) {
				if (question.getDeleted() == deleted) {
					ret.add(question);
				}
			}
		}
		return ret;
	}

	public Test clone() throws CloneNotSupportedException {
		try {
			OptionService optionService = OptionService.instance();
			QuestionService questionService = QuestionService.instance();
			Test clonedTest = new Test();
			clonedTest.setTestName(this.getTestName());
			clonedTest.setState(this.getState());
			clonedTest.setType(this.getType());
			clonedTest.setAuthor(UserService.instance().getCurrentUserLogged());
			clonedTest.setDeleted(this.isDeleted());
			clonedTest.save();

			List<Option> clonedOptions = optionService.cloneOptionsList(clonedTest, null, null, null,
					this.getOptions());
			clonedTest.setOptions(clonedOptions);
			clonedTest.save();

			List<Question> clonedQuestions = questionService.cloneQuestionsList(this, this.getQuestions());
			clonedTest.setQuestions(clonedQuestions);

			clonedTest.save();

			return clonedTest;
		} catch (Exception ex) {
			Logger.error(ex, "Exception during Test clonation...return null");
			return null;
		}
	}
	
	public static List<Test> findAllOrdered(TestType type){
		return Test.find("testType=:type order by :revisiondate DESC").setParameter("type", type).setParameter("revisiondate", "revisionDate").fetch();
	}

	public static List<Test> findAll(TestType type) {
		return Test.find("testType=:type").setParameter("type", type).fetch();
	}
	
	public static List<Test> findAllOrdered(TestType type, String orderby, String ordertype) {
		if(StringUtil.isNil(orderby) || StringUtil.isNil(ordertype))
			return findAll(type);
		return Test.find("testType=:type  order by " +orderby + " " +ordertype).setParameter("type", type).fetch();
	}
	
}