/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.k_int.ciim;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;

import java.util.HashMap;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;

import com.k_int.aggr2.mimsy.data.hdo.CIIMDocMediaDescriptionHDO;
import com.k_int.ciim.json.CIIMGroupAttachmentJSON;
import com.k_int.ciim.json.CIIMGroupJSON;
import com.k_int.ciim.kernel.CIIMDataManager;
import com.k_int.ciim.ref.Constants;
import com.k_int.ciim.utils.CIIMGroupComparator;
import com.k_int.ciim.utils.IdentityServiceWrapper;
import com.k_int.ciim.utils.MailSender;
import com.k_int.mimsy.ref.GroupPublicationStatusEnum;
import com.k_int.mimsy.ref.MediaRecordTypeEnum;
import com.k_int.svc.identity.datamodel.AuthenticationDetailsHDO;
import com.k_int.svc.identity.service.IdentityService;
import com.k_int.svc.identity.service.IdentityServiceException;
import com.sun.jersey.api.view.ImplicitProduces;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlTransient;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.SessionFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * REST Web Service
 *
 * @author ibbo
 * curl -s -H 'Accept: application/json' http://localhost:8080/Template/CIIM/Groups
 * ImplicitProduces("text/html;qs=5")   
 */

@XmlRootElement
@ImplicitProduces("application/json;qs=5")
@XmlAccessorType(XmlAccessType.FIELD)
@Path("/CIIM/Groups")
@Component
public class Groups implements ApplicationContextAware 
{
	private static Log log = LogFactory.getLog(Groups.class);
	 
    //private static final int GET_ALL = 0;
    private static final int GET_CHILDREN = 1;
    private static final int GET_SPECIFIC_NODE = 2;
    private static final int GET_SPECIFIC_TOP = 3;
    private static final int GET_UNASSOCIATED = 4;
    private static final int GET_ASSOCIATED_TOP = 5; 

  @XmlTransient
  private ApplicationContext ctx;
  
  @XmlTransient
  @Context HttpServletRequest request;
  
  @XmlTransient
  private SessionFactory factory = null;

  private List<CIIMGroupJSON> results = new ArrayList<CIIMGroupJSON>();
  private HashMap<String, CIIMGroupJSON> groupsMap = new HashMap<String, CIIMGroupJSON>();

  public void setApplicationContext(ApplicationContext ctx) 
  {
    this.ctx = ctx;
  }
  
  public void setSessionFactory(SessionFactory factory)
  {
    this.factory = factory;
  }
   
  @javax.annotation.PreDestroy
  public void destroy() {
  }
   
  /** Creates a new instance */
  public Groups() 
  {  	  
  }

  @javax.annotation.PostConstruct
  public void init() 
  {
	  System.out.println("CTX : " + ctx);
    
	  try
	  {
	      IdentityService lIdentSrv = (IdentityService) ctx.getBean("IdentityService");   
	      lIdentSrv.addRole("admin", Constants.CIIM_ADMIN_ROLE, true);
	      lIdentSrv.addRole("admin", Constants.CIIM_PUBLISHER_ROLE, true);
	      lIdentSrv.addRole("admin", Constants.CIIM_EDITOR_ROLE, true);
	      lIdentSrv.addRole("admin", Constants.CIIM_USER_ROLE, true);
	  }
	  catch(IdentityServiceException ex)
	  {
	      log.error("Error granting editor role to admin", ex);
	  }  
	  
	  this.updateGroupsMap();
  }
 
  
  @Path("/results")
  @GET
  @Produces({MediaType.APPLICATION_JSON})
  public Groups getJSON(@DefaultValue("0") @QueryParam("minhier") int minhier,
                        @DefaultValue("3") @QueryParam("maxhier") int maxhier,
                        @DefaultValue("0") @QueryParam("returntype") int returntype,
                        @DefaultValue("") @QueryParam("node") String node,
                        @DefaultValue("") @QueryParam("top_group") String top_group)
  { 
	    this.updateGroupsMap();
	    
		results = null; 
		  
	    if(results == null)
	    {
	        results = new ArrayList<CIIMGroupJSON>();
	    }
	    // clear array in preparation
	    results.clear();	    
    
    if(returntype == GET_CHILDREN && node != null && node.length() > 0 && top_group != null && top_group.length() > 0) //thus true
    {
      CIIMGroupJSON lGroup = (CIIMGroupJSON) groupsMap.get(node);
      
      if(lGroup != null)
      {
        if(lGroup.relationships != null)
        {
          ArrayList<String> lChildren = lGroup.relationships.get(top_group); 
          
          if(lChildren != null && lChildren.size() > 0)
          {            
            for(String lChildrenId : lChildren)
            {
              results.add(groupsMap.get(lChildrenId));
            }
          }
        }
      } 
	  Collections.sort(results, new CIIMGroupComparator(top_group));
    }
    else if(returntype == GET_SPECIFIC_TOP && top_group.length() > 0) 
    {            
      CIIMGroupJSON lGroup = (CIIMGroupJSON) groupsMap.get(top_group);
      
      results.add(lGroup);
    }
    else if(returntype == GET_SPECIFIC_NODE && node.length() > 0) 
    {       
      CIIMGroupJSON lGroup = (CIIMGroupJSON) groupsMap.get(node);
      
      results.add(lGroup);
    }
    else if(returntype == GET_UNASSOCIATED && top_group.length() > 0) 
    {    
      results = new ArrayList<CIIMGroupJSON>(groupsMap.values());
      
      for(CIIMGroupJSON lGroup : groupsMap.values())
      {    
        if(lGroup.hier == CIIMGroupJSON.HIERARCHY_TOP)
        {
          results.remove(lGroup);
        }
        if(lGroup.relationships != null && lGroup.relationships.containsKey(top_group))
        {
          ArrayList<String> lChildren = lGroup.relationships.get(top_group);
          
          if(lChildren != null && lChildren.size() > 0)
          {            
            for(String lChildrenId : lChildren)
            {
              results.remove(groupsMap.get(lChildrenId));
            }
          }
        }
      }
    }
    else if(returntype == GET_ASSOCIATED_TOP && node != null && node.length() > 0) 
    {  	
    	results = new ArrayList<CIIMGroupJSON>();
    	
    	CIIMDataManager cdm = (CIIMDataManager) ctx.getBean("CIIMDataManager");
    	
    	for(String top_group_id : cdm.getAssociatedTopGroups(node))
    	{
    		results.add(groupsMap.get(top_group_id));
    	}
    }
    else
    {
      for(CIIMGroupJSON lGroup : groupsMap.values())
      {        
        if(lGroup.hier >= minhier && lGroup.hier <= maxhier)
        {
          results.add(lGroup);
        }
      }
    }  
    
    return this;
  }
  
  @Path("/AddRelationship")
  @POST
  @Produces({MediaType.TEXT_PLAIN})
  public String addRelationship(  	@FormParam("top_id") String top_id,
                      				@FormParam("node_id") String node_id,
                      				@FormParam("child_id") String child_id)
  {
	  String lReturn = "false"; 
	  
	  if(child_id != null && child_id.length() > 0
	  && node_id != null && node_id.length() > 0
	  && top_id != null && top_id.length() > 0)
	  {
		  CIIMDataManager cdm = (CIIMDataManager) ctx.getBean("CIIMDataManager");
	  
		  if(cdm.insertIntoTree(child_id, node_id, top_id, request))
		  {
			  lReturn = "true";
		  }
	  } 
	    
	  this.updateGroupsMap();
 
	  return lReturn;
  } 
  
  @Path("/RemoveRelationship")
  @POST
  @Produces({MediaType.TEXT_PLAIN})
  public String removeRelationship(  	@FormParam("top_id") String top_id,
		  								@FormParam("node_id") String node_id)
  {
	  String retval = null;
	  
	  if(node_id != null && node_id.length() > 0
	  && top_id != null && top_id.length() > 0)
	  {
		  CIIMDataManager cdm = (CIIMDataManager) ctx.getBean("CIIMDataManager");
	  
		  if(cdm.removeFromTree(node_id, top_id, request))
		  {
			  retval = "success";
		  }
	  } 
	    
	  this.updateGroupsMap();
	  
	  return retval;
  } 
  
  @Path("/PromoteGroup")
  @POST
  public void promoteGroup(  	@FormParam("top_id") String top_id,
		  						@FormParam("node_id") String node_id,
		  						@FormParam("gid") String gid,
		  						@FormParam("promote") Boolean promote)
  {
	  if(node_id != null && node_id.length() > 0
	  && top_id != null && top_id.length() > 0
	  && gid != null && gid.length() > 0
	  && promote != null)
	  {
		  CIIMDataManager cdm = (CIIMDataManager) ctx.getBean("CIIMDataManager");
	  
		  cdm.promoteGroupPosition(gid, node_id, top_id, promote, request);
	  } 
	    
	  this.updateGroupsMap();
  } 
  
  @Path("/MakeTopGroup")
  @POST
  @Produces({MediaType.APPLICATION_JSON})
  public Groups makeTopGroup(  @FormParam("gid") String gid)
  {
	  if(gid != null && gid.length() > 0)
	  {
		  CIIMDataManager cdm = (CIIMDataManager) ctx.getBean("CIIMDataManager");
	  
		  cdm.setAsTopGroup(gid, request);
	  }
	  
	  this.updateGroupsMap();
	  
	  return this;
  } 
  
  @Path("/RemoveAsTopGroup")
  @POST
  @Produces({MediaType.APPLICATION_JSON})
  public Groups removeAsTopGroup(  @FormParam("gid") String gid)
  {
	  if(gid != null && gid.length() > 0)
	  {
		  CIIMDataManager cdm = (CIIMDataManager) ctx.getBean("CIIMDataManager");
	  
		  cdm.removeAsTopGroup(gid, request);
	  }
	  
	  this.updateGroupsMap();
	  
	  return this;
  } 
  
  @Path("/PublishGroup")
  @POST
  @Produces({MediaType.TEXT_PLAIN})
  public String publishTopGroup(  	@FormParam("gid") String gid,
		  							@FormParam("group_name") String group_name,
		  							@Context HttpServletRequest request)
  {
	  String lReturn = "The group is not a top group so cannot be published";
	  CIIMDataManager cdm = (CIIMDataManager) ctx.getBean("CIIMDataManager");
	  
	  if(request != null && (cdm.userHasPermissions(request, gid) || request.isUserInRole(Constants.CIIM_ADMIN_ROLE)))
	  {
		  if(request.isUserInRole(Constants.CIIM_PUBLISHER_ROLE) || request.isUserInRole(Constants.CIIM_ADMIN_ROLE))
		  {
			  if(gid != null && gid.length() > 0)
			  {
				  final CIIMGroupJSON lGroup = (CIIMGroupJSON) groupsMap.get(gid);
				  
				  if(lGroup != null && lGroup.mimsy_identifier != null)
				  {
					  if(cdm.isTopGroup(gid))
					  {
						  if(cdm.readyToPublish(gid))
						  { 
							 //set self and all child groups to queued
							 cdm.cascadePublicationStatus(gid, GroupPublicationStatusEnum.QUEUED, request);
							 lReturn="The Group has been queued for publication";
							 
							 new Thread(
							            new Runnable() {
							                public void run() 
							                {
							                	String error_str=null;
							                    try 
							                    {
							                    	HttpClient client = new HttpClient();
							   					 
							   					 	GetMethod method = new GetMethod("http://localhost:8080/restapi/groups/" + lGroup.mimsy_identifier + "?action=publish");
							   					 	//GetMethod method = new GetMethod("http://192.168.1.25:8080/restapi/groups/" + lGroup.mimsy_identifier + "?action=publish");
							   					 	method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,  new DefaultHttpMethodRetryHandler(0, false));
							   					 	int result = client.executeMethod(method);
												
							   					 	if(result>=200 && result<300)
							   					 	{
							   					 		// leave the error handling to the app
							   					 	}
							   					 	else
							   					 	{
													
							   					 		error_str = "Failed to publish group: Http error "+result+" "+method.getStatusText();
							   					 	}
							                    } 
							                    catch (Exception e) 
							                    {
							                       error_str=e.getMessage();
							                    }
							                    finally
							                    {
							                    	updateGroupsMap();
							                    //TODO
							                    	// can we fire a refresh event here
							                    	// for groups
							                    	// and group display
							                    	// ditto if we return from the queue as the
							                    	// display still says unpublished at the mo
							                    	// on the display ansd edit page
							                    	if(error_str!=null)
							                    	{	
							                    		// set all the groups to have status error
							                    		// update the error messages for this top group
							                    		// to include error_str
							                    	
							                    	}
							                    }
							                    
							                }
							            }).start();
						 }
						 else
						 {			 
							 lReturn = "Group cannot be published as its status is " + cdm.getPublicationStatus(gid).toString();
						 }
					  }
				  }	
				  else
				  {
					  lReturn = "No Mimsy Id so not published";
				  }
			  }
			  else
			  {
				  lReturn = "Group Id in use is null so not published";
			  }
		  }
		  else
		  {
			  if(sendPublishEmailRequest(request.getUserPrincipal().getName(), gid, group_name))
			  {
				  lReturn = "Your request to publish this group has been forwarded to the assigned publisher(s)";
			  }
			  else 
			  {
				  lReturn = "You do not have sufficient priviledges to fulfil this action.";
			  }
		  }	
	  }
	  else
	  {
		  lReturn = null;
	  }
	  return lReturn;
  } 
   
  @Path("/UpdateGroupData")
  @POST
  @Consumes("application/x-www-form-urlencoded")
  public void updateGroupData(MultivaluedMap<String, String> formParams)	
  {	   	  
	  CIIMDataManager cdm = (CIIMDataManager) ctx.getBean("CIIMDataManager");
	  
	  cdm.updateCIIMGroup(formParams, request);
	  
	  updateGroupsMap();
  }   
    
  @Path("/LoadText")
  @POST
  @Produces({MediaType.APPLICATION_JSON})
  public CIIMGroupAttachmentJSON retrieveDocumentText(@FormParam("attachment_id") Long attachment_id)
  {
	  CIIMGroupAttachmentJSON lReturn = null;
	  
	  CIIMDataManager cdm = (CIIMDataManager) ctx.getBean("CIIMDataManager");
	  
	  CIIMDocMediaDescriptionHDO lAttachment = (CIIMDocMediaDescriptionHDO) cdm.getAttachment(attachment_id);
	  
	  if(lAttachment != null 
	  && lAttachment.getMediaType() != null
	  && lAttachment.getMediaType() == MediaRecordTypeEnum.DOCUMENT 
	  && lAttachment.getLoadedText() == null)
	  { 
		  Byte[] loadedText = null;
		  
		  File lFileToProcess = new File(lAttachment.getLocation() + lAttachment.getName());
	         
		  /* START WEBSERVICE - RETRIEVE TEXT FROM DOCUMENT */
		  
		  HttpClient client = new HttpClient();
		 
		  //PostMethod method = new PostMethod("http://192.168.1.25:8080/restapi/parser"); //- RB MACHINE
		  PostMethod method = new PostMethod("http://localhost:8080/restapi/parser");
		  method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,  new DefaultHttpMethodRetryHandler(0, false));

		  try
		  {
		  	  try
			  {
				  Part[] parts = 
				  { 
						  new FilePart("toParse", lFileToProcess, "application/octet-stream", "utf-8") 
				  }; 
			      
				  method.setRequestEntity(new MultipartRequestEntity(parts,  method.getParams())); 
			  } 
			  catch (FileNotFoundException e) 
			  { 
			      // TODO Auto-generated catch block 
			      e.printStackTrace(); 
			  } 

		      int statusCode = client.executeMethod(method);
		      
		      if(statusCode >= HttpStatus.SC_OK && statusCode <= HttpStatus.SC_ACCEPTED)
		      {
		    	  String aSubstring = method.getResponseBodyAsString().substring(33,(method.getResponseBodyAsString().length() - 2));
		    	  		    	  	    	  
		    	  byte[] lbytes = aSubstring.getBytes();
		    	  
		    	  loadedText = new Byte[lbytes.length];
		    	  
		    	  for(int i = 0; i < lbytes.length; i++)
		    	  {
		    		  loadedText[i] = new Byte(lbytes[i]);
		    	  }
		    	  
		    	  if(loadedText != null)
				  {
					  //update the MediaDescription Record with the returned text and if successful return the updated object. If this fails null will be returned.
					  lReturn = cdm.setAttachmentLoadedText(attachment_id, loadedText, request);
				  }

		    	  System.out.println("Text loaded successfully");                    
		      } 
		      else 
		      {                                
		    	  System.err.println("Text Load failed");                                
		    	  System.err.println("Got HTTP status code " + statusCode);                                                      
		      }
		      
		      System.out.println(method.getResponseBodyAsString());
	
		      method.releaseConnection();
		  }
		  catch(IOException ioe)
		  {
			  System.err.println("IOException");  
		  }
	  
		  /* END WEBSERVICE - RETRIEVE TEXT FROM DOCUMENT */
	
	  }
	  else
	  {
		  // We have already loaded the text previously so just return the attachment as json.
		  lReturn = cdm.getAttachmentAsJSON(lAttachment.getId());
	  } 
	  
	  return lReturn;
  }
  
   
  private void updateGroupsMap()
  {	  
	  //we need a new hashmap as a group may have been deleted.
	  groupsMap = new HashMap<String, CIIMGroupJSON>();
	  
	  CIIMDataManager cdm = (CIIMDataManager) ctx.getBean("CIIMDataManager");

	  for(CIIMGroupJSON lGroup : cdm.getGroups(request))
	  {
		  if(lGroup != null)
		  {			  
			  groupsMap.put(lGroup.gid, lGroup);
		  }
	  }
  }
  
  private final String getProperty(ResourceBundle bundle, String prop_key, String default_value) 
  {
	  String prop = null;
  	
	  if (bundle!=null) 
	  {
		  try 
		  {
			  prop = bundle.getString(prop_key);
		  } 
		  catch (MissingResourceException mre)
		  {
			  log.error(mre);
			  // leave null and use default
		  }
	  }
	  return prop;
  }
  
  private boolean sendPublishEmailRequest(String username, String gid, String group_name)
  {	 
	  boolean ret_val = false;
	  //set up the bundle		  
	  ResourceBundle bundle = null;

	  try
	  {
		  bundle = ResourceBundle.getBundle("ciim", Locale.getDefault()); 
	  } 
	  catch (MissingResourceException mre) 
	  {
		  log.error("Could not retrieve the resource bundle : " + mre);
		//leave null and use defaults.
	  }
	         
	  /* Get smtp server and default sender email address from properties file */
	  String from_email = this.getProperty(bundle, "ciim_source_email", null);
	  
	  MailSender mail_sender = new MailSender(this.getProperty(bundle, "smtp_server", null),
			  								  this.getProperty(bundle, "smtp_port", null),
			  								  this.getProperty(bundle, "smtp_auth_required", "false"),
			  								  this.getProperty(bundle, "smtp_auth_username", null),
			  								  this.getProperty(bundle, "smtp_auth_password", null));
	  
	  if(mail_sender != null)
	  {
		  List<AuthenticationDetailsHDO> auths = IdentityServiceWrapper.getAssignedAuthsInRole(gid, Constants.CIIM_PUBLISHER_ROLE, factory);
		  
		  if(auths != null && auths.size() > 0)
		  {
			  for(AuthenticationDetailsHDO auth : auths)
			  {
				  //log.debug("Auth : " +  auth.getUser().getName());
				  if(auth != null && auth.getUser() != null && auth.getUser().getPrimaryEmail() != null)
				  {
					  mail_sender.sendEmail(from_email, auth.getUser().getPrimaryEmail(), "Top Group Publication Request", MailSender.getPublishRequestEmail(username, group_name));
					  ret_val = true;
				  }
			  }
		  }
	  }
	  
	  return ret_val;		  
  }
  
  private List<String> convertStringToList(String value)
  {
	  List<String> retval = new ArrayList<String>();
	  
	  if(value != null && value.trim().length() > 0)
	  {
		  //we have a multivalue string
		  if(value.contains("|"))
		  {
			  String[] multivalues = value.split("\\|");
			  
			  if(multivalues != null && multivalues.length > 0)
			  {
				  for(int i = 0; i < multivalues.length; i++)
				  {
					  if(multivalues[i] != null && multivalues[i].trim().length() > 0)
					  {
						  retval.add(multivalues[i]);
					  }
				  }
			  }
		  }
		  else
		  {
			  retval.add(value);
		  }
	  }
	  
	  return retval;
  }

}
