package com.k_int.discover.update;

import com.k_int.aggregator.core.DepositResult;
import com.k_int.aggregator.datamodel.AggregatorCollectionHDO;
import com.k_int.aggregator.dto.AggregatorCollectionDTO;
import com.k_int.aggregator.dto.AggregatorSourceDTO;
import com.k_int.aggregator.repository.RepositoryStoreOptions;
//import com.k_int.aggregator.util.SOLRHelper;
import com.k_int.discover.datamodel.CultureGrid_BaseHDO;
import com.k_int.discover.datamodel.CultureGrid_InstitutionHDO;
import com.k_int.discover.datamodel.dto.CultureGrid_InstitutionDTO;
import com.k_int.discover.datamodel.updates.UpdateRequestHDO;
import com.k_int.discover.datamodel.updates.UpdateStatusEnum;
import com.k_int.discover.service.dtoHandler.DTODocumentHandlerFactory;
import com.k_int.discover.service.dtoHandler.DTODocumentHandlerPlugin;
import com.k_int.discover.service.dtoHandler.DTOProcessingResult;
import com.opensymphony.xwork2.ActionSupport;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.interceptor.ServletRequestAware;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * A class to allow the location of an institution to be updated with
 * lat and long values passed in by the caller
 *
 * @author rpb rich@k-int.com
 * @version 1.0 31.01.11
 */
public class LocationUpdateAction extends ActionSupport implements ServletRequestAware, ApplicationContextAware {
	private static final long serialVersionUID = -7387105055027042199L;
	private static Log log = LogFactory.getLog(LocationUpdateAction.class);

    private HttpServletRequest request;
    private ApplicationContext ctx;
    private SessionFactory factory;
    
    public void setServletRequest(HttpServletRequest request) { this.request = request; }
    public void setApplicationContext(ApplicationContext ctx) {	this.ctx = ctx; }
    public void setSessionFactory(SessionFactory factory) { this.factory = factory; }

    public UpdateRequestResponse updateResponse = null;
    
    public UpdateRequestResponse getUpdateResponse() { return this.updateResponse; }

    @Override
    public String execute() {
        log.debug("In the Location update action execute method");
        String returnValue = "xml";
        boolean updateMade = false;

        // Get all of the information from the call
        String tempInstIdentifier = request.getParameter("id");
        String tempNewLat = request.getParameter("lat");
        String tempNewLong = request.getParameter("long");
        String format = request.getParameter("format");
        if ( format != null ) {
            if ( "json".equals(format.trim()) ) {
                returnValue = "json";
            } else if ( "xml".equals(format.trim()) ) {
                returnValue = "xml";
            }
        }
        log.debug("identifier: " + tempInstIdentifier + " newLat: " + tempNewLat + " newLong: " + tempNewLong + " format: " + format);

        // Get the allowable update range from config
        ResourceBundle bundle = null;
	try {
            bundle = ResourceBundle.getBundle("updateService", Locale.getDefault());
	} catch (MissingResourceException mre) {
	    //leave null and use defaults.
	}
        String rangeStr = null;
        if ( bundle.containsKey("com.k_int.discover.updateService.acceptableRange") ) {
            rangeStr = bundle.getString("com.k_int.discover.updateService.acceptableRange");
            if ( rangeStr == null || "".equals(rangeStr.trim()) )
                rangeStr = "5";
        }

        Double range = Double.parseDouble(rangeStr);

        log.debug("rangeStr retrieved from config as " + rangeStr + " and range parsed to: " + range);

        if ( tempInstIdentifier == null || "".equals(tempInstIdentifier.trim()) ||
                tempNewLat == null || "".equals(tempNewLat.trim()) ||
                tempNewLong == null || "".equals(tempNewLong.trim()) ) {
            // Don't have all of the information we need - can't do anything
            UpdateRequestResponse tempResp = new UpdateRequestResponse();
            tempResp.setSuccessful(false);
            tempResp.setRequestStatus(null);
            tempResp.setMessage("Unable to update the location as not all of the required information was provided");

            this.updateResponse = tempResp;
        } else {
            // We have at least a value specified for everything we need - now check they are valid
            try {
                Long recIdentifier = Long.parseLong(tempInstIdentifier);
                Double newLat = Double.parseDouble(tempNewLat);
                Double newLong = Double.parseDouble(tempNewLong);

                // Go and get the current institution record from the database
                Session sess = null;
                Transaction tx = null;
                
                try {
                    sess = factory.openSession();
                    tx = sess.beginTransaction();
                    
                    CultureGrid_BaseHDO baseHDO = CultureGrid_BaseHDO.lookupByResourceId(sess, recIdentifier);
                    if ( baseHDO != null ) {
                        // We have a record - is it an institution?
                        if ( baseHDO instanceof CultureGrid_InstitutionHDO ) {
                            // It is an institution - OK to continue
                            CultureGrid_InstitutionHDO instHDO = (CultureGrid_InstitutionHDO)baseHDO;

                            Double oldLat = instHDO.getFirstLatitude();
                            Double oldLong = instHDO.getFirstLongitude();

                            if ( oldLat != null && !oldLat.equals(0.0d) &&
                                    oldLong != null && !oldLong.equals(0.0d) ) {

                                // There was a lat and long before - ok to continue

                                // Now work out the distance between the old lat/long and the new ones..
                                Double distance = calcDistance(oldLat,oldLong,newLat,newLong);

                                if ( distance == null || distance < 0 || distance > range ) {
                                    // Distance outside of auto limits - don't update, but remember request

                                    // Now log the update as requested, but outside of limits
                                    Date updateTime = new Date();
                                    UpdateRequestHDO updateRequest = new UpdateRequestHDO();
                                    updateRequest.setResourceToUpdate(instHDO.getResource());
                                    updateRequest.setSubmittedDate(updateTime);
                                    updateRequest.setRequestStatus(UpdateStatusEnum.SUBMITTED);
                                    StringBuilder sb = new StringBuilder();
                                    sb.append("Location change from Find a Library but the new location is outside the specified range. New values - Lat: ").append(newLat);
                                    sb.append(" Long: ").append(newLong).append(" Distance: ").append(distance);
                                    updateRequest.setUpdateRequest(sb.toString());
                                    updateRequest.setMapLink("http://maps.google.co.uk/maps?&geocode=&q=" + newLat + "," + newLong);

                                    log.debug("Saving the location update request for later acceptance");
                                    sess.save(updateRequest);
                                    tx.commit();

                                    UpdateRequestResponse tempResp = new UpdateRequestResponse();
                                    tempResp.setSuccessful(true);
                                    tempResp.setRequestStatus(UpdateStatusEnum.SUBMITTED);
                                    tempResp.setMessage("Update request submitted successfully and awaiting confirmation and acceptance");
                                    
                                    this.updateResponse = tempResp;
                                } else {
                                    // Distance is OK to update automatically
                                    instHDO.setLatitudeLongitude(newLat, newLong);
                                    // TODO - should this also regsiter update information on the actual resource? i.e. edited, etc.

                                    // Now log the update as performed
                                    Date updateTime = new Date();

                                    UpdateRequestHDO updateRequest = new UpdateRequestHDO();
                                    updateRequest.setResourceToUpdate(instHDO.getResource());
                                    updateRequest.setSubmittedDate(updateTime);
                                    updateRequest.setRequestStatus(UpdateStatusEnum.ACCEPTED_AUTO);
                                    updateRequest.setUpdateRequest("Location change from Find A Library");
                                    updateRequest.setChangeDate(updateTime);
                                    updateRequest.setChangedBy("FindALibraryService");

                                    log.debug("Saving the automatically accepted update request into the database");
                                    
                                    sess.save(updateRequest);
                                    tx.commit();

                                    UpdateRequestResponse tempResp = new UpdateRequestResponse();
                                    tempResp.setSuccessful(true);
                                    tempResp.setRequestStatus(UpdateStatusEnum.ACCEPTED_AUTO);
                                    tempResp.setMessage("Location updated");
                                    this.updateResponse = tempResp;
                                }
                            } else {
                                // No lat / long before - can't do anything
                                log.debug("No lat / long in the database so can't do an update");

                                Date updateTime = new Date();
                                UpdateRequestHDO updateRequest = new UpdateRequestHDO();
                                updateRequest.setResourceToUpdate(instHDO.getResource());
                                updateRequest.setSubmittedDate(updateTime);
                                updateRequest.setRequestStatus(UpdateStatusEnum.SUBMITTED);
                                updateRequest.setUpdateRequest("Location change from Find a Library but there are no existing latitude/longitude values to check against. New values - Lat: " + newLat + " Long: " + newLong);

                                log.debug("Saving the location update request for later acceptance");
                                sess.save(updateRequest);
                                tx.commit();
                                
                                UpdateRequestResponse tempResp = new UpdateRequestResponse();
                                tempResp.setSuccessful(true);
                                tempResp.setRequestStatus(UpdateStatusEnum.SUBMITTED);
                                tempResp.setMessage("Update request submitted successfully and awaiting confirmation and acceptance");
                                this.updateResponse = tempResp;
                            }

                            // If an update has occurred then we need to reprocess the institution
                            if ( updateMade ) {
                                this.reindexRecord(sess, recIdentifier);
                            }

                        } else {
                            // Not an institution - currently not supported!
                            log.debug("The specified identifier relates to a record that exists, but isn't an institution - not currently able to update");
                            UpdateRequestResponse tempResp = new UpdateRequestResponse();
                            tempResp.setSuccessful(false);
                            tempResp.setMessage("Unable to update the specified record");
                            tempResp.setRequestStatus(null);
                            this.updateResponse = tempResp;
                        }
                    } else {
                        log.debug("No institution with the specified identifier - unable to update it!");

                        UpdateRequestResponse tempResp = new UpdateRequestResponse();
                        tempResp.setSuccessful(false);
                        tempResp.setMessage("Unable to identify the record to be updated - no update can be performed");
                        tempResp.setRequestStatus(null);
                        this.updateResponse = tempResp;
                    }

                } catch (HibernateException he) {
                    log.error("HibernateException thrown when attempting to update an institution's location: " + he.getMessage());
                    he.printStackTrace();

                    if ( tx != null ) {
                        tx.rollback();
                    }
                } finally {
                    if ( sess != null  && sess.isOpen() ) {
                        try {
                            sess.close();
                        } catch (Exception e) {
                            log.error("Exception thrown when closing the session!");
                        }
                    }
                }

            } catch (NumberFormatException nfe) {
                // Unable to parse the values into long's - therefore not valid
                log.debug("Exception thrown when parsing the passed information into doubles.. Unable to continue");

                UpdateRequestResponse tempResp = new UpdateRequestResponse();
                tempResp.setSuccessful(false);
                tempResp.setRequestStatus(null);
                tempResp.setMessage("Unable to update the record as the lat and long passed could not be parsed");
                this.updateResponse = tempResp;
            }

        }

        // If we haven't worked out a response yet then add in a generic one
        if ( this.updateResponse == null ) {
            UpdateRequestResponse tempResp = new UpdateRequestResponse();
            tempResp.setSuccessful(false);
            tempResp.setRequestStatus(null);
            tempResp.setMessage("An unexpected error occurred when processing the update request");

            this.updateResponse = tempResp;
        }

        request.setAttribute("updateResponse", this.updateResponse);
        
        return returnValue;
    }


    /**
     * Work out the distance between two points given as lats/longs in radians
     * @return The distance in km between the two specified points
     */
    private Double calcDistance(Double lat1, Double long1, Double lat2, Double long2 ) {
        Double retVal = null;

        // Radius of Earth in km
        Double earthRad = 6371.0d;

        // Convert all of the lats/longs to radians from degrees..
        lat1 = Math.toRadians(lat1);
        long1 = Math.toRadians(long1);
        lat2 = Math.toRadians(lat2);
        long2 = Math.toRadians(long2);

        retVal = Math.acos(Math.sin(lat1)*Math.sin(lat2) + Math.cos(lat1)*Math.cos(lat2) * Math.cos(long2-long1)) * earthRad;
        
        return retVal;
    }

    private void reindexRecord(Session sess, Long resourceId) {

        // Get the Solr helper and dto handler factory from the context
//        SOLRHelper solrHelper = (SOLRHelper)ctx.getBean("SolrHelper");
        DTODocumentHandlerFactory dtoFactory = (DTODocumentHandlerFactory)ctx.getBean("DTOHandlerFactory");

        Long reindexOptions = RepositoryStoreOptions.REPO_STORE_OPTIONS_CACHE_THUMBNAIL | RepositoryStoreOptions.REPO_STORE_OPTIONS_DO_NOT_PERFORM_DATA_AUGMENTATION;

        CultureGrid_BaseHDO baseHDO = CultureGrid_BaseHDO.lookupByResourceId(sess, resourceId);
        CultureGrid_InstitutionHDO instHDO = (CultureGrid_InstitutionHDO)baseHDO;
        CultureGrid_InstitutionDTO instDTO = CultureGrid_InstitutionDTO.convertHDOToDTO(sess, instHDO);

        DTODocumentHandlerPlugin dtoHandler = (DTODocumentHandlerPlugin)dtoFactory.getDTOHandler("CultureGrid_InstitutionDTO");
        if ( dtoHandler != null ) {
            log.debug("got the dto handler - now to process..");
            String depositor_id = baseHDO.getResource().getLastUpdateBy();
            if ( depositor_id == null || "".equals(depositor_id.trim()) )
                depositor_id = baseHDO.getResource().getCreatedBy();

            AggregatorSourceDTO source_dto = AggregatorSourceDTO.convertHDOtoDTO(baseHDO.getResource().getSource());
            boolean authoritative = baseHDO.getResource().getAuthoritative();
            Set<AggregatorCollectionHDO> collHDOList = baseHDO.getResource().getCollections();
            List<AggregatorCollectionDTO> collList = new ArrayList<AggregatorCollectionDTO>();
            Iterator<AggregatorCollectionHDO> collIter = collHDOList.iterator();
            while(collIter.hasNext()) {
                AggregatorCollectionHDO nextHDO = collIter.next();
                if ( nextHDO != null )
                    collList.add(AggregatorCollectionDTO.convertHDOtoDTO(nextHDO));
            }

            DepositResult deposit_result = new DepositResult();
            @SuppressWarnings("unused")
			DTOProcessingResult procResult = dtoHandler.process(deposit_result, instDTO, depositor_id, source_dto, null, authoritative, reindexOptions, collList, resourceId);

            log.debug("have just finished processing the dto..");
        } else {
            log.error("Unable to find a dto handler for the institution dto..");
        }
        
    }
    
}
