package com.k_int.discover.lookup.datamodel;

import com.k_int.aggregator.datamodel.AggregatorResourceHDO;
import com.k_int.aggregator.datamodel.ResourceInstanceHDO;
import com.k_int.discover.datamodel.CultureGrid_BaseHDO;
import com.k_int.discover.datamodel.CultureGrid_CollectionHDO;
import com.k_int.discover.datamodel.CultureGrid_InstitutionHDO;
import com.k_int.discover.datamodel.CultureGrid_ItemHDO;
import com.k_int.discover.datamodel.CultureGrid_LinkHDO;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * A DTO to hold the details of an individual result of a lookup operation
 * @author rpb rich@k-int.com
 * @version 1.0 31.08.10
 */
public class LookupResultDTO {

    private static Log log = LogFactory.getLog(LookupResultDTO.class);
    
    private String identifier;
    private String type;
    private boolean deleted = false;
    private String containingCollectionHierarchy;
    private Date created;
    private Date lastUpdated;
    private String provider;
    private String uploadedBy;
    private String lastEditedBy;
    private Map<String,String> availableFormats = new HashMap<String,String>();
    private String latitude;
    private String longitude;
    private String temporalFrom;
    private String temporalTo;
    private String temporalGranularity;
    private Set<String> temporalDecade = new HashSet<String>();
    private String relatedLink;
    private String matchMethod;


    public LookupResultDTO() {
        super();
    }

    public String getIdentifier() {
        return this.identifier;
    }

    public void setIdentifier(String identifier) {
        this.identifier = identifier;
    }

    public String getType() {
        return this.type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public boolean getDeleted() {
        return this.deleted;
    }

    public void setDeleted(boolean deleted) {
        this.deleted = deleted;
    }
    
    public String getContainingCollectionHierarchy() {
        return this.containingCollectionHierarchy;
    }

    public void setContainingCollectionHierarchy(String containingCollectionHierarchy) {
        this.containingCollectionHierarchy = containingCollectionHierarchy;
    }

    public Date getCreated() {
        return this.created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }
    
    public Date getLastUpdated() {
        return this.lastUpdated;
    }

    public void setLastUpdated(Date lastUpdated) {
        this.lastUpdated = lastUpdated;
    }

    public String getProvider() {
        return this.provider;
    }

    public void setProvider(String provider) {
        this.provider = provider;
    }

    public String getUploadedBy() {
        return this.uploadedBy;
    }

    public void setUploadedBy(String uploadedBy) {
        this.uploadedBy = uploadedBy;
    }

    public String getLastEditedBy() {
        return this.lastEditedBy;
    }

    public void setLastEditedBy(String lastEditedBy) {
        this.lastEditedBy = lastEditedBy;
    }

    public Map<String,String> getAvailableFormats() {
        return this.availableFormats;
    }

    public void setAvailableFormats(Map<String,String> availableFormats) {
        this.availableFormats = availableFormats;
    }

    public String getLatitude() {
        return this.latitude;
    }

    public void setLatitude(String latitude) {
        this.latitude = latitude;
    }

    public String getLongitude() {
        return this.longitude;
    }

    public void setLongitude(String longitude) {
        this.longitude = longitude;
    }

    public String getTemporalFrom() {
        return this.temporalFrom;
    }

    public void setTemporalFrom(String temporalFrom) {
        this.temporalFrom = temporalFrom;
    }

    public String getTemporalTo() {
        return this.temporalTo;
    }

    public void setTemporalTo(String temporalTo) {
        this.temporalTo = temporalTo;
    }

    public String getTemporalGranularity() {
        return this.temporalGranularity;
    }

    public void setTemporalGranularity(String temporalGranularity) {
        this.temporalGranularity = temporalGranularity;
    }

    public Set<String> getTemporalDecade() {
        return this.temporalDecade;
    }

    public void setTemporalDecade(Set<String> temporalDecade) {
        this.temporalDecade = temporalDecade;
    }

    public String getRelatedLink() {
        return this.relatedLink;
    }

    public void setRelatedLink(String relatedLink) {
        this.relatedLink = relatedLink;
    }

    public String getMatchMethod() {
        return this.matchMethod;
    }

    public void setMatchMethod(String matchMethod) {
        this.matchMethod = matchMethod;
    }

    public String toXML() {
        StringBuilder sb = new StringBuilder();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");

        sb.append("<record>\n");
        sb.append("    <identifier>").append(this.identifier).append("</identifier>\n");
        sb.append("    <matchMethod>").append(this.matchMethod).append("</matchMethod>\n");
        if ( this.relatedLink != null && !"".equals(this.relatedLink) ) {
            String tempLink = relatedLink;
            if ( this.relatedLink.contains("&") ) {
                tempLink = relatedLink.replaceAll("\\&", "&amp;");
            }
            sb.append("    <relatedLink>").append(tempLink).append("</relatedLink>\n");
        }

        sb.append("    <recordType>").append(this.type).append("</recordType>\n");
        if ( this.getDeleted() ) {
            sb.append("    <status>deleted</status>\n");
        } else {
            sb.append("    <status>current</status>\n");

            sb.append("    <collectionHierarchy>").append(this.containingCollectionHierarchy).append("</collectionHierarchy>\n");
            sb.append("    <dateCreated>").append(sdf.format(this.created)).append("</dateCreated>\n");
            sb.append("    <createdBy>").append(this.uploadedBy).append("</createdBy>\n");
            sb.append("    <lastUpdated>").append(sdf.format(this.lastUpdated)).append("</lastUpdated>\n");
            sb.append("    <updatedBy>").append(this.lastEditedBy).append("</updatedBy>\n");
            sb.append("    <provider>").append(this.provider).append("</provider>\n");
            if ( (this.latitude != null && !"".equals(this.latitude.trim())) || (this.longitude != null && !"".equals(this.longitude.trim())) ) {
                sb.append("    <spatial>\n");
                sb.append("        <latitude>").append(this.latitude).append("</latitude>\n");
                sb.append("        <longitude>").append(this.longitude).append("</longitude>\n");
                sb.append("    </spatial>\n");
            }
            sb.append("    <temporal>\n");
            if ( this.temporalFrom != null && !"".equals(this.temporalFrom.trim()) ) {
                sb.append("        <from>").append(this.temporalFrom.trim()).append("</from>\n");
            }
            if ( this.temporalTo != null && !"".equals(this.temporalTo.trim()) ) {
                sb.append("        <to>").append(this.temporalTo.trim()).append("</to>\n");
            }
            if ( this.temporalGranularity != null && !"".equals(this.temporalGranularity.trim()) ) {
                sb.append("        <granularity>").append(this.temporalGranularity.trim()).append("</granularity>\n");
            }
            if ( this.temporalDecade != null && this.temporalDecade.size() > 0 ) {
                Iterator<String> decadeIter = this.temporalDecade.iterator();
                while(decadeIter.hasNext()) {
                    String decade = decadeIter.next();
                    if ( decade != null && !"".equals(decade.trim()) ) {
                        sb.append("        <decade>").append(decade.trim()).append("</decade>\n");
                    }
                }
            }
            sb.append("    </temporal>\n");

            sb.append("    <availableFormats>\n");
            if ( this.availableFormats != null ) {
                Iterator<String> keysetIter = this.availableFormats.keySet().iterator();
                while(keysetIter.hasNext()) {
                    String key = keysetIter.next();
                    String value = this.availableFormats.get(key);

                    if ( key != null && value != null ) {
                        sb.append("        <link format=\"").append(key).append("\">").append(value).append("</link>\n");
                    }
                }
            }
            sb.append("    </availableFormats>\n");
        }

        sb.append("</record>\n");

        return sb.toString();
    }

    public static LookupResultDTO CreateLookupResultDTO(CultureGrid_BaseHDO record, String baseURL, String matchMethod) {

        LookupResultDTO retVal = null;

        if ( record != null ) {

            retVal = new LookupResultDTO();

            List<CultureGrid_LinkHDO> links = record.getLinks();
            if ((links != null) && !links.isEmpty()) {
            	// For the time being, just grab the first link
            	retVal.setRelatedLink(links.get(0).getLink());
            }

            // The setting of the type should be done through a method on the hdo
            if ( record instanceof CultureGrid_ItemHDO ) {
                // Item
                retVal.setType("item");
            } else if ( record instanceof CultureGrid_CollectionHDO ) {
                // Collection
                retVal.setType("collection");
            } else if ( record instanceof CultureGrid_InstitutionHDO ) {
                // Institution
                retVal.setType("institution");
            } else {
                // Unknown type!
                log.error("Record of unexpected type passed to the LookupResultDTO constructor. Unable to process. Type: " + record.getClass().getName());
                retVal = null;
            }

            // If we have a DTO to put information into (won't have if we don't recognise
            // the type of the incoming record) then put in the generic information
            if ( retVal != null ) {
                AggregatorResourceHDO res = record.getResource();
                if ( res != null ) {
                    retVal.setIdentifier(res.getId().toString());
                    retVal.setUploadedBy(res.getCreatedBy());
                    retVal.setCreated(res.getCreationDate());
                    retVal.setLastEditedBy(res.getLastUpdateBy());
                    retVal.setLastUpdated(res.getLastModifiedDate());
                    if ( res.getDeletedDate() != null ) {
                        retVal.setDeleted(true);
                    }
                    retVal.setProvider(res.getSource().getName());
                    retVal.setMatchMethod(matchMethod);

                    // Add in the various links and remember the contents of the SOLR document
                    // so that we can parse some of the information we need out from it
                    byte[] solrDocumentContents = null;
                    Set<ResourceInstanceHDO> resInstances = res.getInstances();
                    Iterator<ResourceInstanceHDO> resInstIter = resInstances.iterator();
                    Map<String,String> tempFormats = new HashMap<String,String>();
                    while(resInstIter.hasNext()) {
                        ResourceInstanceHDO nextResInst = resInstIter.next();
                        if ( nextResInst != null ) {
                            String format = nextResInst.getResourceType().getIdentifier();
                            String path = baseURL + "/dpp/resource/" + res.getId() + "/stream/" + format;

                            if ( "thumbnail_image_jpeg".equals(format) )
                                format = "thumbnail";
                            
                            tempFormats.put(format, path);

                            if ( "solr".equals(format) ) {
                                solrDocumentContents = nextResInst.getContent();
                            }
                        }
                    }
                    // Add in the static (sitemap) link
                    tempFormats.put("static", baseURL + "/static/showResource/" + res.getId());

                    retVal.setAvailableFormats(tempFormats);


                    // Work out the spatial and temporal information that we have in solr
                    try {
                        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                        DocumentBuilder db = dbf.newDocumentBuilder();
                        Document doc = db.parse(new ByteArrayInputStream(solrDocumentContents));

                        if ( doc != null ) {
                            // Parse out the information we want - first move down to the actual field list
                            NodeList fieldElements =  doc.getElementsByTagName("field");
                            Set<String> tempDecade = new HashSet<String>();
                            // Loop through each field element and get the ones we want
                            for(int ctr = 0; ctr < fieldElements.getLength(); ctr++) {
                                Node aFieldElement = fieldElements.item(ctr);
                                NamedNodeMap attributes = aFieldElement.getAttributes();
                                if ( attributes != null ) {
                                    Node nameAttr = attributes.getNamedItem("name");
                                    if ( nameAttr != null ) {
                                        String nameAttrVal = nameAttr.getTextContent();
                                        String fieldVal = aFieldElement.getTextContent();

                                        // Check that we're looking at a field that we care about here
                                        // and make use of it if we are
                                        if ( "lat".equals(nameAttrVal) ) {
                                            // Latitude
                                            retVal.setLatitude(fieldVal);
                                        } else if ( "lng".equals(nameAttrVal) ) {
                                            // Longitude
                                            retVal.setLongitude(fieldVal);
                                        } else if ( "temporal.from.withEra".equals(nameAttrVal) ) {
                                            // Temporal - from
                                            retVal.setTemporalFrom(fieldVal);
                                        } else if ( "temporal.to.withEra".equals(nameAttrVal) ) {
                                            // Temporal - to
                                            retVal.setTemporalTo(fieldVal);
                                        } else if ( "temporal.granularity".equals(nameAttrVal) ) {
                                            // Temporal - granularity
                                            retVal.setTemporalGranularity(fieldVal);
                                        } else if ( "temporal.decade".equals(nameAttrVal) ) {
                                            // Temporal decade
                                            tempDecade.add(fieldVal);
                                        } else if ( "dcterms.isPartOf_AllNames".equals(nameAttrVal) ) {
                                            // Collection names - need to change the formatting
                                            String tempColls = fieldVal.replaceAll("\\|CG\\_BREAK\\|", ":");
                                            retVal.setContainingCollectionHierarchy(tempColls);
                                        }

                                    }
                                }
                            }

                            retVal.setTemporalDecade(tempDecade);

                        }
                    } catch (ParserConfigurationException pce) {
                        log.error("ParserConfigurationException thrown when creating a parser to parse a Solr document for spatial and temporal data: " + pce.getMessage());
                        pce.printStackTrace();
                    } catch (SAXException se) {
                        log.error("SAXExeption thrown when parsing a Solr document for spatial and temporal data: " + se.getMessage());
                        se.printStackTrace();
                    } catch (IOException ioe) {
                        log.error("IOException thrown when parsing a Solr document for spatial and temporal data: " + ioe.getMessage());
                        ioe.printStackTrace();
                    }
                }
            }

        }

        return retVal;
    }

}
