/* $This file is distributed under the terms of the license in LICENSE$ */

package edu.cornell.mannlib.vedit.controller;

import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.ReasoningOption.ASSERTIONS_ONLY;

import java.text.Collator;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Random;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import edu.cornell.mannlib.vedit.beans.EditProcessObject;
import edu.cornell.mannlib.vedit.beans.Option;
import edu.cornell.mannlib.vedit.util.FormUtils;
import edu.cornell.mannlib.vitro.webapp.controller.Controllers;
import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;

public class BaseEditController extends VitroHttpServlet {

	public static final boolean FORCE_NEW = true; // when you know you're starting a new edit process
    
    public static final String JSP_PREFIX = "/templates/edit/specific/";
	
    protected static DateFormat DISPLAY_DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy");
    protected static final int BASE_10 = 10;

    private static final Log log = LogFactory.getLog(BaseEditController.class.getName());
    private static final String DEFAULT_LANDING_PAGE = Controllers.SITE_ADMIN;
    protected static final String MULTIPLEXED_PARAMETER_NAME = "multiplexedParam";
    private final String EPO_HASH_ATTR = "epoHash";
    private final String EPO_KEYLIST_ATTR = "epoKeylist";
    private final int MAX_EPOS = 5;
    private final Calendar cal = Calendar.getInstance();

    /* EPO is reused if the controller is passed an epoKey, e.g.
      if a previous form submission failed validation, or the edit is a multistage process. */

    protected EditProcessObject createEpo(HttpServletRequest request) {
    	return createEpo(request, false);
    }
    
    protected EditProcessObject createEpo(HttpServletRequest request, boolean forceNew) {
        /* this is actually a bit of a misnomer, because we will reuse an epo
        if an epoKey parameter is passed */
        EditProcessObject epo = null;
        HashMap epoHash = getEpoHash(request);
        String existingEpoKey = request.getParameter("_epoKey");
        if (!forceNew && existingEpoKey != null && epoHash.get(existingEpoKey) != null)  {
            epo = (EditProcessObject) epoHash.get(existingEpoKey);
            epo.setKey(existingEpoKey);
            epo.setUseRecycledBean(true);
        } else {
            LinkedList epoKeylist = getEpoKeylist(request);
            if (epoHash.size() == MAX_EPOS) {
            	try {
            		epoHash.remove(epoKeylist.getFirst());
            		epoKeylist.removeFirst();
            	} catch (Exception e) {
            		// see JIRA issue VITRO-340, "Odd exception from backend editing"
            		// possible rare concurrency issue here
            		log.error("Error removing old EPO", e);
            	}
            }
            Random rand = new Random();
            String epoKey = createEpoKey();
            while (epoHash.get(epoKey) != null) {
                epoKey+=Integer.toHexString(rand.nextInt());
            }
            epo = new EditProcessObject();
            epoHash.put (epoKey,epo);
            epoKeylist.add(epoKey);
            epo.setKey(epoKey);
            epo.setReferer( (forceNew) ? request.getRequestURL().append('?').append(request.getQueryString()).toString() : request.getHeader("Referer") );
            epo.setSession(request.getSession());
        }
        return epo;
    }

    private LinkedList getEpoKeylist(HttpServletRequest request){
        return (LinkedList) request.getSession().getAttribute(EPO_KEYLIST_ATTR);
    }

    private HashMap getEpoHash(HttpServletRequest request){
        HashMap epoHash = (HashMap) request.getSession().getAttribute(EPO_HASH_ATTR);
        if (epoHash == null) {
            epoHash = new HashMap();
            request.getSession().setAttribute(EPO_HASH_ATTR,epoHash);
            //since we're making a new EPO hash, we should also make a new keylist.
            LinkedList epoKeylist = new LinkedList();
            request.getSession().setAttribute(EPO_KEYLIST_ATTR,epoKeylist);
        }
        return epoHash;
    }

    private String createEpoKey(){
        return Long.toHexString(cal.getTimeInMillis());
    }

    protected void setRequestAttributes(HttpServletRequest request, EditProcessObject epo){
    	VitroRequest vreq = new VitroRequest(request);
        request.setAttribute("epoKey",epo.getKey());
        request.setAttribute("epo",epo);
        request.setAttribute("globalErrorMsg",epo.getAttribute("globalErrorMsg"));
        request.setAttribute("css", "<link rel=\"stylesheet\" type=\"text/css\" href=\""+vreq.getAppBean().getThemeDir()+"css/edit.css\"/>");
    }

    protected void populateBeanFromParams(Object bean, HttpServletRequest request) {
        Map params = request.getParameterMap();
        Enumeration paramNames = request.getParameterNames();
        while (paramNames.hasMoreElements()){
            String key = "";
            try {
                key = (String) paramNames.nextElement();
            } catch (ClassCastException cce) {
                log.error("populateBeanFromParams() could not cast parameter name to String");
            }
            String value = "";
            if (key.equals(MULTIPLEXED_PARAMETER_NAME)) {
                String multiplexedStr = request.getParameterValues(key)[0];
                Map<String, String> paramMap = FormUtils.beanParamMapFromString(multiplexedStr);
                for (String param : paramMap.keySet()) {
                    String demultiplexedValue = (String) paramMap.get(param);
                    FormUtils.beanSet(bean, param, demultiplexedValue);
                }

            } else {
                try {
                    value = (String) request.getParameterValues(key)[0];
                } catch (ClassCastException cce) {
                    try {
                        value = ((Integer) params.get(key)).toString();
                    } catch (ClassCastException ccf) {
                        log.error("populateBeanFromParams() could not cast parameter name to String");
                    }
                }
                FormUtils.beanSet(bean, key, value);
            }
        }
    }
    
    public List<Option> getSortedList(HashMap<String,Option> hashMap, List<Option> optionList, VitroRequest vreq){
        
        class ListComparator implements Comparator<String>{
            
            Collator collator;
            
            public ListComparator(Collator collator) {
                this.collator = collator;
            }
            
            @Override
            public int compare(String str1, String str2) {
                return collator.compare(str1, str2);
            }
            
        }

       List<String> bodyVal = new ArrayList<String>();
       List<Option> options = new ArrayList<Option>();
        for (Option option : optionList) {
            hashMap.put(option.getBody(), option);
            bodyVal.add(option.getBody());
        }
        
                
       bodyVal.sort(new ListComparator(vreq.getCollator()));
        for (String aBodyVal : bodyVal) {
            options.add(hashMap.get(aBodyVal));
        }
       return options;
   }
    
    protected WebappDaoFactory getWebappDaoFactory() {
    	return ModelAccess.on(getServletContext()).getWebappDaoFactory(ASSERTIONS_ONLY);
    }
    
    protected WebappDaoFactory getWebappDaoFactory(String userURI) {
        return getWebappDaoFactory().getUserAwareDaoFactory(userURI);
    }
    
    public String getDefaultLandingPage(HttpServletRequest request) {
    	return(request.getContextPath() + DEFAULT_LANDING_PAGE);
    }

}
