package org.gcube.portlets.admin.sepeditor.server;

import static org.gcube.resources.discovery.icclient.ICFactory.clientFor;
import static org.gcube.resources.discovery.icclient.ICFactory.queryFor;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.gcube.application.framework.core.session.ASLSession;
import org.gcube.application.framework.core.session.SessionManager;
import org.gcube.common.core.contexts.GHNContext;
import org.gcube.common.core.informationsystem.ISException;
import org.gcube.common.core.informationsystem.client.AtomicCondition;
import org.gcube.common.core.informationsystem.client.ISClient;
import org.gcube.common.core.informationsystem.client.queries.GCUBERuntimeResourceQuery;
import org.gcube.common.core.informationsystem.publisher.ISPublisher;
import org.gcube.common.core.resources.GCUBERuntimeResource;
import org.gcube.common.core.resources.common.PlatformDescription;
import org.gcube.common.core.resources.runtime.AccessPoint;
import org.gcube.common.core.scope.GCUBEScope;
import org.gcube.common.core.scope.ServiceMap;
import org.gcube.common.core.security.GCUBESecurityManagerImpl;
import org.gcube.common.resources.gcore.GenericResource;
import org.gcube.common.scope.api.ScopeProvider;
import org.gcube.common.scope.impl.ScopeBean;
import org.gcube.portlets.admin.sepeditor.client.RuntimeResourceCreatorService;
import org.gcube.portlets.admin.sepeditor.shared.Category;
import org.gcube.portlets.admin.sepeditor.shared.FilledRuntimeResource;
import org.gcube.portlets.admin.sepeditor.shared.InitInfo;
import org.gcube.portlets.admin.sepeditor.shared.Property;
import org.gcube.portlets.admin.sepeditor.shared.RRAccessPoint;
import org.gcube.resources.discovery.client.api.DiscoveryClient;
import org.gcube.resources.discovery.client.queries.api.SimpleQuery;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;



/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class RuntimeResourceCreatorServiceImpl extends RemoteServiceServlet implements	RuntimeResourceCreatorService {
	private static final Logger _log = Logger.getLogger(RuntimeResourceCreatorServiceImpl.class);
	
	private boolean withinPortal = false;
	private static final String SCOPE = "/gcube/devsec";

	private final String EDIT_ID_ATTR = "RMP_EDIT_ID";
	String idToTest = "b7fce5e0-b0e5-11e2-9d26-c9dc2c525e1c";

	/**
	 * the current ASLSession
	 * @return the session
	 */
	private ASLSession getASLSession() {
		String sessionID = this.getThreadLocalRequest().getSession().getId();
		String user = (String) this.getThreadLocalRequest().getSession().getAttribute("username");
		if (user == null) {
			_log.warn("USER IS NULL setting test.user");
			user = "test.user";		
			sessionID = "123";
		}
		else {
			withinPortal = true;
			_log.info("LIFERAY PORTAL DETECTED user=" + user);
		}
		ASLSession toReturn = SessionManager.getInstance().getASLSession(sessionID, user);
		if (!withinPortal)
			toReturn.setScope(SCOPE);
		return toReturn;
	}

	@Override
	public Boolean createRuntimeResource(String scope,	FilledRuntimeResource rs, boolean isUpdate) {

		GCUBERuntimeResource rt;
		boolean result = true;
		try {
			if (!isUpdate) {
				_log.info("Trying creating Runtime Resource: " + rs.getResourceName() + " SCOPE: " + scope);
				rt =GHNContext.getImplementation(GCUBERuntimeResource.class);
			}
			else {
				ISClient client = GHNContext.getImplementation(ISClient.class);
				GCUBERuntimeResourceQuery query = client.getQuery(GCUBERuntimeResourceQuery.class);
				query.addAtomicConditions(new AtomicCondition("//ID", rs.getResourceId()));
				_log.info("Trying editing  Runtime Resource ID: " + rs.getResourceId());
				rt = client.execute(query,GCUBEScope.getScope(scope)).get(0); //only one rr per id

			}
			rt.setCategory(rs.getCategory());
			rt.setDescription(rs.getDescription());
			rt.setGHN(rs.getRuntimegHNUniqueID());
			rt.setHostedOn(rs.getRuntimeHostedOn());
			rt.setName(rs.getResourceName());
			rt.setStatus(rs.getRuntimeStatus());
			PlatformDescription platform = new PlatformDescription();
			platform.setName(rs.getPlatformName());
			platform.setVersion((short) Integer.parseInt( (rs.getPlatformVersion().equals("")) ? "0" : rs.getPlatformVersion()) );

			platform.setMinorVersion((short) Integer.parseInt( (rs.getPlatformMinorVersion().equals("")) ? "0" : rs.getPlatformMinorVersion()) );
			platform.setRevisionVersion((short) Integer.parseInt( (rs.getPlatformRevisionVersion().equals("")) ? "0" : rs.getPlatformRevisionVersion()) );
			platform.setBuildVersion((short) 	Integer.parseInt( (rs.getPlatformBuildVersion().equals("")) ? "0" : rs.getPlatformBuildVersion()) );
			rt.setPlatform(platform);

			ArrayList<RRAccessPoint> myAccessPoints = rs.getRRAccessPoints();

			List<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
			for (RRAccessPoint p : myAccessPoints) {
				AccessPoint a = new AccessPoint();
				a.setEndpoint(p.getInterfaceEndPoint());
				a.setEntryname(p.getInterfaceEntryNameAttr());
				a.setDescription(p.getDesc());

				a.setUsername(p.getUsername());
				a.setPassword(p.getPassword());

				for (Property prop : p.getProperties()) {
					a.addProperty(prop.getKey(), prop.getValue(), prop.isCrypted());
				}

				accessPoints.add(a);
			}
			rt.setAccessPoints(accessPoints );

			rt.addScope(GCUBEScope.getScope(scope));

			ISPublisher publisher = GHNContext.getImplementation(ISPublisher.class);
			if (! isUpdate) {
				String xmlProfile = publisher.registerGCUBEResource(rt, GCUBEScope.getScope(scope), new GCUBESecurityManagerImpl() {

					@Override
					public boolean isSecurityEnabled() {
						// TODO Auto-generated method stub
						return false;
					}
				});
				System.out.println("Created");
				_log.trace("Created new RR sent, Got from publisher: \n" + xmlProfile);
			} else {
				System.out.println("Updating " + rs.getResourceId());
				for (String scope2Update: rt.getScopes().keySet()) {
					GCUBEScope gScope = GCUBEScope.getScope(scope2Update);
					ServiceMap map = loadServiceMap(scope2Update);
					gScope.setServiceMap(map);
					publisher.updateGCUBEResource(rt, gScope,  new GCUBESecurityManagerImpl() {
						@Override
						public boolean isSecurityEnabled() {
							return false;
						}					
					});
					System.out.println("Updated " + rs.getResourceId() + " on " + scope2Update);
				}
				System.out.println("Updated " + rt.getID());
				_log.trace("Updated RR sent, id: " + rt.getID());
			}

		} catch (Throwable e) {
			_log.error("ERROR While Creating or Updating RT Resource");
			e.printStackTrace();
			return false;
		}
		_log.info("Registration Request successfully Sent");
		return result;
	}
	/**
	 * 
	 * @param scope
	 * @return
	 * @throws Exception
	 */
	private static ServiceMap loadServiceMap(String scope) throws Exception {
		ServiceMap map = new ServiceMap();
		if (scope.split("/").length > 3) {  //is a VRE SCOPE get its VO Map
			int lastSlash = scope.lastIndexOf("/");
			scope = scope.substring(0, lastSlash);
		}

		int lastSlash = scope.lastIndexOf("/");
		scope = scope.substring(lastSlash+1, scope.length());

		String fileName = "ServiceMap_" + scope + ".xml";
		_log.debug("Trying load map " + fileName);
		String filePath = System.getenv("GLOBUS_LOCATION") + File.separator + "config" + File.separator + fileName;
		map.load(new FileReader(filePath));
		return map;
	}


	@Override
	public InitInfo getInitialInfo(boolean isEditMode, String idToEdit, String curscope) {
		getASLSession();
		ArrayList<String> scopes = getAvailableScopes();
		if (! isEditMode) {
			_log.info("Editing Mode OFF");
			System.out.println("Editing Mode OFF");
			for (String scopeFound : scopes) {
				System.out.println(scopeFound);
			}
			return new InitInfo(scopes, null);
		}
		/*
		 * else return the bean of the resource to edit
		 */
		//get the id from the session
		String rr2editId = idToEdit;
		_log.info("Editing Mode ON for id: " + rr2editId);
		FilledRuntimeResource fRR = getResource2EditById(rr2editId);
		return new InitInfo(scopes, fRR);
	}

	private FilledRuntimeResource getResource2EditById(String id) {
		GCUBEScope scope = null;
		try {
			ISClient client = GHNContext.getImplementation(ISClient.class);
			GCUBERuntimeResourceQuery query = client.getQuery(GCUBERuntimeResourceQuery.class);
			query.addAtomicConditions(new AtomicCondition("//ID", id));

			scope = GCUBEScope.getScope(getASLSession().getScopeName());

			GCUBERuntimeResource rRes = client.execute(query,scope).get(0); //only one rr per id

			ArrayList<RRAccessPoint> acPoints = new ArrayList<RRAccessPoint>();
			for (AccessPoint ac : rRes.getAccessPoints()) {
				RRAccessPoint rac = new RRAccessPoint();

				rac.setInterfaceEndPoint(ac.getEndpoint());
				rac.setInterfaceEntryNameAttr(ac.getEntryname());
				rac.setDesc(ac.getDescription());
				rac.setUsername(ac.getUsername());
				rac.setPassword(ac.getPassword()); 

				ArrayList<Property> props = new ArrayList<Property>();
				for (String prop : ac.getAllPropertyNames()) {
					String propValue = ac.getProperty(prop);
					props.add(new Property(prop, propValue, ac.isPropertyEncrypted(prop)));
				}
				rac.setProperties(props);
				acPoints.add(rac);
			}
			return new FilledRuntimeResource(id, acPoints, rRes.getName(), rRes.getVersion(), rRes.getCategory(), rRes.getDescription(), 
					rRes.getPlatform().getName(), ""+rRes.getPlatform().getVersion(), ""+rRes.getPlatform().getMinorVersion(), ""+rRes.getPlatform().getRevisionVersion(),
					""+rRes.getPlatform().getBuildVersion(), rRes.getHostedOn(), rRes.getStatus(), rRes.getGHN());
		} 
		catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}



	/**
	 * NOT USED
	 */
	@Override
	public ArrayList<Category> getCategories() {
		Set<Category> mySet = new HashSet<Category>();
		ArrayList<Category> toReturn = new ArrayList<Category>();
		GCUBEScope scope = null;
		scope = GCUBEScope.getScope(getASLSession().getScopeName());
		ISClient client;
		try {
			_log.info("Fetching gCube Runtime resource categories into " + scope.getName());
			client = GHNContext.getImplementation(ISClient.class);
			GCUBERuntimeResourceQuery query = client.getQuery(GCUBERuntimeResourceQuery.class);
			for (GCUBERuntimeResource rRes : client.execute(query,scope)) {
				toReturn.add(new Category(rRes.getCategory()));
			}
			for (Category category : mySet) {
				toReturn.add(category);
			}
			return toReturn;
		} catch (Exception e) {
			_log.error("Generic Exception for " + scope.getName()  + " " + e.getMessage());
			return null;
		}
	}

	public ArrayList<String> getAvailableScopes() {
		ArrayList<String> retval = new ArrayList<String>();
		String currentScope = getASLSession().getScopeName();
		System.out.println("currentScope: " + currentScope);
		try {
			Map<String, ScopeBean> scopes = readScopes(this.getScopeDataPath());
			for (ScopeBean scope : scopes.values()) {
				if (scope.toString().startsWith(currentScope))
					retval.add(scope.toString());
			}
			Collections.sort(retval);
			return retval;
		} catch (Exception e) {
			e.printStackTrace();
			retval.add("/gcube");
			retval.add("/gcube/devsec");
		}
		return retval;
	}

	public static Map<String, ScopeBean> readScopes(String confFile) throws Exception {
		if (confFile == null) {
			throw new NullPointerException("the scope file has not been defined");
		}
		Map<String, ScopeBean>  toReturn = new HashMap<String, ScopeBean>();
		String scopeXML = fileToString(confFile);

		Document scopeDocument = getDocumentGivenXML(scopeXML);
		NodeList voElements = scopeDocument.getElementsByTagName("vo");

		for (int i = 0; i < voElements.getLength(); i++) {
			NodeList voDetails = voElements.item(i).getChildNodes();
			String voString = voDetails.item(5).getFirstChild().getNodeValue();
			// String voName = voDetails.item(1).getFirstChild().getNodeValue();
			ScopeBean vo = new ScopeBean(voString);

			toReturn.put(vo.toString(), vo);
			try {
				for (String vre : getVREFromVO(vo)) {
					// This operation overrides the vo map
					toReturn.put(vre.toString(), new ScopeBean(vo.toString()+"/"+vre));
				}
			} catch (ISException e) {
				_log.error("Exception raised while loading VREs for VO : " + vo, e);
			}
		}
		return toReturn;

	}

	private String getScopeDataPath() {
		String startDirectory = getServletFSPath();
		return startDirectory + File.separator + "xml" + File.separator +"scopedata_admin.xml";

	}
	private String getServletFSPath() {
		return this.getServletContext().getRealPath("") + File.separator + "WEB-INF";
	}


	public static String fileToString(final String path) throws IOException {
		BufferedReader filebuf = null;
		String nextStr = null;
		StringBuilder ret = new StringBuilder();

		filebuf = new BufferedReader(new FileReader(path));
		nextStr = filebuf.readLine(); // legge una riga dal file
		while (nextStr != null) {
			ret.append(nextStr);
			nextStr = filebuf.readLine(); // legge la prossima riga
		}
		filebuf.close(); // chiude il file

		return ret.toString();
	}

	public static Document getDocumentGivenXML(final String result) {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setValidating(false);
		DocumentBuilder db;
		Document document = null;
		try {
			db = dbf.newDocumentBuilder();
			document = db.parse(new ByteArrayInputStream(result.getBytes()));
		} catch (ParserConfigurationException e1) {
			e1.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return document;
	}

	protected static List<String> getVREFromVO(final ScopeBean vo) 	throws Exception {
		_log.debug("Starting Retrieving VREs for VO : " + vo);
		List<String> toReturn = new ArrayList<String>();

		ScopeProvider.instance.set(vo.toString());	
		SimpleQuery query = queryFor(GenericResource.class);
		query.addCondition("$resource/Profile/SecondaryType/text() eq 'VRE'");

		DiscoveryClient<GenericResource> client = clientFor(GenericResource.class);

		List<GenericResource> gRes = client.submit(query);

		for (GenericResource res : gRes) {
			_log.debug("Found: " + res.profile().name());
			toReturn.add(res.profile().name());
		}
		return toReturn;
	}



}
