/*
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 *
 * The Apereo Foundation licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
*/
package org.unitime.timetable.server.sectioning;

import java.io.ByteArrayInputStream;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import org.cpsolver.ifs.util.DataProperties;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.springframework.beans.factory.annotation.Autowired;
import org.unitime.localization.impl.Localization;
import org.unitime.timetable.defaults.SessionAttribute;
import org.unitime.timetable.defaults.UserProperty;
import org.unitime.timetable.gwt.command.client.GwtRpcException;
import org.unitime.timetable.gwt.command.client.GwtRpcResponseList;
import org.unitime.timetable.gwt.command.server.GwtRpcImplementation;
import org.unitime.timetable.gwt.command.server.GwtRpcImplements;
import org.unitime.timetable.gwt.resources.GwtMessages;
import org.unitime.timetable.gwt.shared.PublishedSectioningSolutionInterface;
import org.unitime.timetable.gwt.shared.PublishedSectioningSolutionInterface.PublishedSectioningSolutionsRequest;
import org.unitime.timetable.model.SectioningSolutionLog;
import org.unitime.timetable.model.dao.SectioningSolutionLogDAO;
import org.unitime.timetable.security.SessionContext;
import org.unitime.timetable.security.rights.Right;
import org.unitime.timetable.solver.service.StudentSectioningSolverService;
import org.unitime.timetable.solver.studentsct.StudentSolverProxy;
import org.unitime.timetable.util.NameFormat;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

/**
 * @author Tomas Muller
 */
@GwtRpcImplements(PublishedSectioningSolutionsRequest.class)
public class PublishedSectioningSolutionsBackend implements GwtRpcImplementation<PublishedSectioningSolutionsRequest, GwtRpcResponseList<PublishedSectioningSolutionInterface>> {
	protected static GwtMessages MESSAGES = Localization.create(GwtMessages.class);
	
	@Autowired StudentSectioningSolverService studentSectioningSolverService;

	@Override
	public GwtRpcResponseList<PublishedSectioningSolutionInterface> execute(PublishedSectioningSolutionsRequest request, SessionContext context) {
		context.checkPermission(Right.StudentSectioningSolverPublish);
		
		SectioningSolutionLog solution = null;
		if (request.getUniqueId() != null) {
			solution = SectioningSolutionLogDAO.getInstance().get(request.getUniqueId());
			if (solution == null)
				throw new GwtRpcException(MESSAGES.errorSolutionDoesNotExist(request.getUniqueId().toString()));
		}
		StudentSolverProxy publishedSolver = studentSectioningSolverService.getSolver("PUBLISHED_" + context.getUser().getCurrentAcademicSessionId(), context.getUser().getCurrentAcademicSessionId());
		DataProperties publishedSolverProperties  = (publishedSolver == null ? null : publishedSolver.getProperties());
		Long selectedId = (publishedSolverProperties == null ? null : publishedSolverProperties.getPropertyLong("StudentSct.PublishId", null));

		StudentSolverProxy mySolver = studentSectioningSolverService.getSolver(context.getUser().getExternalUserId(), context.getUser().getCurrentAcademicSessionId());
		DataProperties mySolverProperties = (mySolver == null ? null : mySolver.getProperties());
		Long mySolverId = null;
		if (mySolverProperties != null && context.getUser().getExternalUserId().equals(mySolverProperties.getProperty("General.OwnerPuid"))) {
			mySolverId = mySolverProperties.getPropertyLong("StudentSct.PublishId", null);
		}
		
		switch (request.getOperation()) {
		case LIST:
			break;
		case PUBLISH:
			publishedSolverProperties = getConfig(solution.getData());
			selectedId = solution.getUniqueId();
			publishedSolver = studentSectioningSolverService.publishSolver(solution.getUniqueId(), publishedSolverProperties, solution.getData());
        	break;
		case UNPUBLISH:
			if (publishedSolver != null) {
				publishedSolver.interrupt();
				publishedSolver.dispose();
			}
			context.removeAttribute(SessionAttribute.StudentSectioningSolver);
			context.removeAttribute(SessionAttribute.StudentSectioningUser);
            publishedSolver = null;
            publishedSolverProperties = null;
            selectedId = null;
            break;
		case LOAD:
        	if (mySolver != null && mySolver.isWorking()) throw new GwtRpcException(MESSAGES.warnSolverIsWorking());
        	mySolverProperties = getConfig(solution.getData());
        	mySolverProperties.setProperty("StudentSct.PublishId", solution.getUniqueId().toString());
        	mySolverProperties.remove("General.Host");
        	mySolver = studentSectioningSolverService.createSolver(mySolverProperties, solution.getData());
        	mySolverId = solution.getUniqueId();
        	break;
		case UNLOAD:
			if (mySolver != null) {
				mySolver.interrupt();
				mySolver.dispose();
			}
			context.setAttribute(SessionAttribute.StudentSectioningUser, context.getUser().getExternalUserId());
			context.removeAttribute(SessionAttribute.StudentSectioningSolver);
        	mySolver = null;
			mySolverProperties = null;
        	mySolverId = null;
			break;
		case SELECT:
			context.setAttribute(SessionAttribute.StudentSectioningUser, "PUBLISHED_" + context.getUser().getCurrentAcademicSessionId());
			context.removeAttribute(SessionAttribute.StudentSectioningSolver);
            break;
		case DESELECT:
			context.removeAttribute(SessionAttribute.StudentSectioningUser);
			context.removeAttribute(SessionAttribute.StudentSectioningSolver);
			break;
		case REMOVE:
			SectioningSolutionLogDAO.getInstance().getSession().remove(solution);
			SectioningSolutionLogDAO.getInstance().getSession().flush();
			break;
		case NOTE:
			solution.setNote(request.getNote());
			SectioningSolutionLogDAO.getInstance().getSession().merge(solution);
			SectioningSolutionLogDAO.getInstance().getSession().flush();
			break;
		}
		
		GwtRpcResponseList<PublishedSectioningSolutionInterface> ret = new GwtRpcResponseList<PublishedSectioningSolutionInterface>();
		NameFormat nf = NameFormat.fromReference(UserProperty.NameFormat.get(context.getUser()));
		Gson gson = getGson();
		for (SectioningSolutionLog log: SectioningSolutionLogDAO.getInstance().getSession().createQuery(
				"from SectioningSolutionLog where session.uniqueId = :sessionId order by timeStamp", SectioningSolutionLog.class)
				.setParameter("sessionId", context.getUser().getCurrentAcademicSessionId()).setCacheable(true).list()) {
			PublishedSectioningSolutionInterface pss = new PublishedSectioningSolutionInterface();
			pss.setUniqueId(log.getUniqueId());
			pss.setInfo(new HashMap<String, String>(gson.fromJson(log.getInfo(), Map.class)));
			pss.setOwner(nf.format(log.getOwner()));
			pss.setTimeStamp(log.getTimeStamp());
			pss.setLoaded(log.getUniqueId().equals(selectedId));
			pss.setCanLoad(!pss.isLoaded());
			pss.setClonned(log.getUniqueId().equals(mySolverId));
			pss.setCanClone(mySolver == null);
			pss.setConfig(log.getConfig());
			pss.setNote(log.getNote());
			pss.setCanChangeNote(true);
			if (pss.isLoaded())
				pss.setSelected(("PUBLISHED_" + context.getUser().getCurrentAcademicSessionId()).equals(context.getAttribute(SessionAttribute.StudentSectioningUser)));
			else
				pss.setSelected(false);
			pss.setCanSelect(mySolver != null);
			ret.add(pss);
		}
		return ret;
	}

	protected Gson getGson() {
		GsonBuilder builder = new GsonBuilder()
		.registerTypeAdapter(DateTime.class, new JsonSerializer<DateTime>() {
			@Override
			public JsonElement serialize(DateTime src, Type typeOfSrc, JsonSerializationContext context) {
				return new JsonPrimitive(src.toString("yyyy-MM-dd'T'HH:mm:ss'Z'"));
			}
		})
		.registerTypeAdapter(DateTime.class, new JsonDeserializer<DateTime>() {
			@Override
			public DateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
				return new DateTime(json.getAsJsonPrimitive().getAsString(), DateTimeZone.UTC);
			}
		})
		.registerTypeAdapter(Date.class, new JsonSerializer<Date>() {
			@Override
			public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
				return new JsonPrimitive(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(src));
			}
		})
		.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
			@Override
			public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
				try {
					return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(json.getAsJsonPrimitive().getAsString());
				} catch (ParseException e) {
					throw new JsonParseException(e.getMessage(), e);
				}
			}
		});
		builder.setPrettyPrinting();
		return builder.create();
	}
	
	protected DataProperties getConfig(byte[] data) {
		try {
			Document document = (new SAXReader()).read(new GZIPInputStream(new ByteArrayInputStream(data)));
			DataProperties config = new DataProperties();
			for (Iterator i = document.getRootElement().element("configuration").elementIterator("property"); i.hasNext(); ) {
    			Element e = (Element)i.next();
    			config.setProperty(e.attributeValue("name"), e.getText());
    		}	    		
			return config;
		} catch (Exception e) {
			throw new GwtRpcException(MESSAGES.failedLoadData(e.getMessage()), e);
		}
		
	}
}
