package com.k_int.npdb.service;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xpath.XPathAPI;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.k_int.aggregator.core.DepositResult;
import com.k_int.aggregator.plugin.XMLDocumentHandlerPlugin;
import com.k_int.aggregator.plugin.XMLProcessingResult;
import com.k_int.aggregator.util.SOLRHelper;
import com.k_int.aggregator.util.SOLRHelper.NVPair;
import com.k_int.npdb.datamodel.Address;
import com.k_int.npdb.datamodel.Contributor;
import com.k_int.npdb.datamodel.Performance;
import com.k_int.npdb.datamodel.Person;
import com.k_int.npdb.datamodel.Production;
import com.k_int.npdb.datamodel.Role;
import com.k_int.npdb.datamodel.Source;
import com.k_int.npdb.datamodel.Venue;
import com.k_int.npdb.datamodel.Work;

@Service("XMLHandler-TheatreWeb")
public class TheatreWebHandler implements XMLDocumentHandlerPlugin,
		ApplicationContextAware {

	private ApplicationContext ctx;
	private SessionFactory sf;
	private SOLRHelper solrHelper;
	private Element namespaceNode;
	private Source source;
	private static Log log = LogFactory.getLog(TheatreWebHandler.class);
	private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

	private static final String venues_xpath = "/tw:DATA/tw:VENUES/tw:VENUE";
	private static final String works_xpath = "/tw:DATA/tw:WORKS/tw:WORK";
	private static final String listings_xpath = "/tw:DATA/tw:LISTINGS/tw:LISTING";

	public static String XMLNS_XSI = "http://www.w3.org/2001/XMLSchema-instance";
	public static String XMLNS_THEATREWEB = "http://www.uktw.co.uk/";

	public String[] getSupportedSchemas() {
		return new String[] { XMLNS_THEATREWEB };
	}

	public TheatreWebHandler() {
		log.debug("TheatreWebHandler");
	}

	public void setApplicationContext(ApplicationContext ctx) {
		this.ctx = ctx;
	}

	public XMLProcessingResult process(DepositResult depositResult,
			byte[] content, Document d, String depositor_id, String owner_id,
			String root_namespace, Boolean authoritative) {
		return process(depositResult, content, d, depositor_id, owner_id,
				root_namespace, authoritative, 0);
	}

	public XMLProcessingResult process(DepositResult depositResult,
			byte[] content, Document d, String depositor_id, String ownerId,
			String rootNamespace, Boolean authoritative, long options) {
		XMLProcessingResult result = new XMLProcessingResult();
		Transaction tx = null;
		sf = (SessionFactory) ctx.getBean("AggregatorSessionFactory");
		Session sess = SessionFactoryUtils.getSession(sf, true);
		long start = (new Date()).getTime();
		try {

			namespaceNode = d.createElement("NSNode");
			namespaceNode.setAttribute("xmlns:xsi", XMLNS_XSI);
			namespaceNode.setAttribute("xmlns:tw", XMLNS_THEATREWEB);
			tx = sess.beginTransaction();
			source = Source.lookupOrCreate(sess, ownerId);
			solrHelper = (SOLRHelper) ctx.getBean("SolrHelper");
			processVenues(sess, XPathAPI.selectNodeList(d, venues_xpath,
					namespaceNode));
			sess.flush();
			processWorks(sess, XPathAPI.selectNodeList(d, works_xpath,
					namespaceNode));
			sess.flush();
			processListings(sess, XPathAPI.selectNodeList(d, listings_xpath,
					namespaceNode));
			sess.flush();
			tx.commit();
			long end = (new Date()).getTime();
			log.debug("Time processed " + (end - start));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	private ArrayList<Role> getContributors(Session sess,
			NodeList contributors, List<Role> existingRoles) {
		ArrayList<Role> roles = new ArrayList<Role>();
		try {
			for (int j = 0; j < contributors.getLength(); j++) {
				Element person = (Element) contributors.item(j);
				String typeAttr = person.getAttribute("PERSONTYPE");
				String personName = getString(person, "tw:PERSON_NAME",
						namespaceNode);
				String roleType = getString(person, "tw:PERSON_TYPE",
						namespaceNode);
				String roleQualifier = getString(person, "tw:PERSON_QUALIFIER",
						namespaceNode);
				String type = null, firstName = null, lastName = null;
				if ("C".equals(typeAttr)) {
					type = Contributor.COMPANY;
				} else if ("G".equals(typeAttr)) {
					type = Contributor.GROUP;
				} else if (!"X".equals(typeAttr) && !"W".equals(typeAttr)
						&& !"R".equals(typeAttr) && !"O".equals(typeAttr)
						&& !"E".equals(typeAttr) && !"?".equals(typeAttr)) {
					int spaceIndex = personName.lastIndexOf(" ");
					if (spaceIndex > 0) {
						firstName = personName.substring(0, spaceIndex);
						lastName = personName.substring(spaceIndex + 1);
					} else {
						lastName = personName;
					}
					type = Contributor.PERSON;
				}
				Contributor contributor = Contributor.lookupOrCreate(sess,
						personName, lastName);
				if (type != null) {
					contributor.setType(type);
					if (Contributor.PERSON.equals(type)) {
						Person p = contributor.getPerson();
						if (p == null)
							p = new Person();
						p.setLastname(lastName);
						if (firstName != null)
							p.setFirstname(firstName);
						sess.save(p);
						contributor.setPerson(p);
					}
				}

				if (contributor.getSource() == null) {
					contributor.setSource(source);
					sess.save(contributor);
				}
				boolean alreadyExists = false;
				for (Role ex : existingRoles) {
					if (sameValue(ex.getRole(), roleType)
							&& sameValue(ex.getRoleQualifier(), roleQualifier)
							&& ex.getContributor() != null
							&& ex.getContributor().getId() == contributor
									.getId())
						alreadyExists = true;
				}
				if (!alreadyExists) {
					Role role = new Role();
					role.setContributor(contributor);
					role.setSource(source);
					role.setRole(roleType);
					roleQualifier = (roleQualifier!=null && roleQualifier.length() > 255) ? roleQualifier.substring(0,250) + "..." : roleQualifier;
					role.setRoleQualifier(roleQualifier);
					sess.save(role);
					roles.add(role);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return roles;
	}

	private boolean sameValue(String a, String b) {
		if (a == null && b == null)
			return true;
		else if (a != null && a.equals(b))
			return true;
		else
			return false;
	}

	private String getString(Node metadata_record, String xpath,
			Node namespace_node)
			throws javax.xml.transform.TransformerException {
		Node value = XPathAPI.selectSingleNode(metadata_record, xpath,
				namespace_node);
		if (value != null) {
			return extractText(value);
		}
		return null;
	}

	private String extractText(Node n) {
		try {
			Node node = XPathAPI.selectSingleNode(n, "./text()");
			if (node != null) {
				node.normalize();
				return node.getNodeValue().trim();
			}
		} catch (javax.xml.transform.TransformerException te) {
			te.printStackTrace();
		}
		return null;
	}

	private void processVenues(Session sess, NodeList venues) {
		try {
			for (int i = 0; i < venues.getLength(); i++) {
				Node n = venues.item(i);
				String id = ((Element) n).getAttribute("VID");
				String name = getString(n, "tw:VENUE_NAME", namespaceNode);
				String type = getString(n, "tw:VENUE_TYPE", namespaceNode);
				String county = getString(n, "tw:VENUE_COUNTY", namespaceNode);
				String country = getString(n, "tw:VENUE_REGION", namespaceNode);
				String town = getString(n, "tw:VENUE_TOWN", namespaceNode);
				String street = getString(n, "tw:VENUE_ADDRESS1", namespaceNode);
				String postcode = getString(n, "tw:VENUE_POSTCODE",
						namespaceNode);
				String easting = getString(n, "tw:VENUE_EASTING", namespaceNode);
				String northing = getString(n, "tw:VENUE_NORTHING",
						namespaceNode);
				String url = getString(n, "tw:VENUE_URL", namespaceNode);
				String description = getString(n, "tw:VENUE_DESCRIPTION",
						namespaceNode);

				Venue venue = Venue.lookupOrCreate(sess, source, id, name);
				Address address = new Address();
				if (venue.getAddress() == null) {
					address.setCountry(country);
					address.setCounty(county);
					address.setName(name);
					address.setTown(town);
					address.setStreet(street);
					address.setPostcode(postcode);
					address.setEasting(easting);
					address.setNorthing(northing);
					venue.setAddress(address);
					sess.save(address);
				}
				venue.setDescription(description);
				venue.setType(type);
				venue.setUrl(url);
				venue.setExternalId(id);
				venue.setSource(source);
				venue.setName(name);
				if (description != null) {
					Map<String, String> list = extractAsociatedVenues(description);
					List<Venue> associatedVenues = new ArrayList<Venue>();
					for (String v : list.keySet()) {
						Venue associated = Venue.lookupOrCreate(sess, source,
								v, list.get(v));
						associatedVenues.add(associated);
						venue.setAssociatedVenues(associatedVenues);
					}
				}
				List<NVPair> pairs = venue.toSOLRIndex();
				if (pairs.size() > 0)
					solrHelper.postIndexEntries(pairs, false);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	private Map<String, String> extractAsociatedVenues(String desc) {

		Map<String, String> list = new HashMap<String, String>();
		while (desc.matches(".*\\{.*::.*\\}.*")) {
			int start = desc.indexOf("{");
			int index = desc.indexOf("::");
			int end = desc.indexOf("}", index);
			String id = desc.substring(index + 2, end);
			String name = desc.substring(start + 1, index);
			list.put(id, name);
			desc = desc.substring(end);
		}
		return list;
	}

	private void processWorks(Session sess, NodeList works) {
		try {
			for (int i = 0; i < works.getLength(); i++) {

				Node n = works.item(i);
				String id = ((Element) n).getAttribute("WID");
				String name = getString(n, "tw:WORK_NAME", namespaceNode);
				String genre = getString(n, "tw:WORK_TYPE", namespaceNode);
				String description = getString(n, "tw:WORK_DESCRIPTION",
						namespaceNode);
				String url = getString(n, "tw:WORK_URL", namespaceNode);

				Work work = Work.lookupOrCreate(sess, source, id, name);

				work.setExternalId(id);
				work.setSource(source);
				work.setName(name);
				work.setUrl(url);
				work.setDescription(description);
				work.setGenre(genre);
				NodeList workContributors = XPathAPI.selectNodeList(n,
						"tw:WORK_PEOPLE/tw:PERSON", namespaceNode);
				sess.save(work);
				ArrayList<Role> roles = getContributors(sess, workContributors, work.getContributors());
				Contributor company = null;

				for (Role r : roles) {
					if ("Company".equals(r.getRole())) {
						company = r.getContributor();
					} else
						work.getContributors().add(r);
					List<NVPair> pairs = r.getContributor().toSOLRIndex();
					if (pairs.size() > 0)
						solrHelper.postIndexEntries(pairs, false);
				}
				sess.save(work);
				List<NVPair> pairs = work.toSOLRIndex();
				if (pairs.size() > 0)
					solrHelper.postIndexEntries(pairs, false);

				NodeList workProductions = XPathAPI.selectNodeList(n,
						"tw:WORK_PRODUCTIONS/tw:PRODUCTION", namespaceNode);
				if (workProductions.getLength() == 0) {
					Production production = new Production();
					production.setSource(source);
					production.setWork(work);
					sess.save(production);
					if (company != null) {
						production.setCompany(company);
					}
					pairs = production.toSOLRIndex();
					if (pairs.size() > 0)
						solrHelper.postIndexEntries(pairs, false);
				}
				for (int j = 0; j < workProductions.getLength(); j++) {
					Node p = workProductions.item(j);
					String productionId = ((Element) p).getAttribute("PID");
					String subtitle = getString(p, "tw:PRODUCTION_SUBTITLE",
							namespaceNode);
					String productionDescription = getString(p,
							"tw:PRODUCTION_DESCRIPTION", namespaceNode);
					String productionUrl = getString(p, "tw:PRODUCTION_URL",
							namespaceNode);
					Production production = Production.lookupOrCreate(sess,
							source, productionId);
					production.setExternalId(productionId);
					production.setSource(source);
					production.setSubtitle(subtitle);
					production.setUrl(productionUrl);
					production.setDescription(productionDescription);
					production.setWork(work);
					NodeList productionContributors = XPathAPI.selectNodeList(
							p, "tw:PRODUCTION_PEOPLE/tw:PERSON", namespaceNode);
					ArrayList<Role> productionRoles = getContributors(sess,
							productionContributors, production.getContributors());
					sess.save(production);
					for (Role r : productionRoles) {
						if ("Company".equals(r.getRole())) {
							production.setCompany(r.getContributor());
						}
						production.getContributors().add(r);
						pairs = r.getContributor().toSOLRIndex();
						if (pairs.size() > 0)
							solrHelper.postIndexEntries(pairs, false);
					}
					pairs = production.toSOLRIndex();
					if (pairs.size() > 0)
						solrHelper.postIndexEntries(pairs, false);
				}
				sess.save(work);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	private void processListings(Session sess, NodeList listings) {
		try {
			for (int i = 0; i < listings.getLength(); i++) {
				Node n = listings.item(i);
				String id = ((Element) n).getAttribute("LID");
				Performance performance = Performance.lookupOrCreate(sess,
						source, id);
				performance.setExternalId(id);
				performance.setSource(source);
				String venueId = ((Element) n).getAttribute("LVENUE");
				String productionId = ((Element) n).getAttribute("LPRODUCTION");
				String workId = ((Element) n).getAttribute("LWORK");
				Venue venue = Venue.lookupOrCreate(sess, source, venueId, null);
				performance.setVenue(venue);
				if (productionId != null && !"".equals(productionId)) {
					
					Production production = Production.lookupOrCreate(sess,
							source, productionId);
					performance.setProduction(production);
				} else if (workId != null && !"".equals(workId)) {
					Work work = Work.lookupOrCreate(sess, source, workId, null);
					Production production = getStubProduction(sess, work);
					performance.setProduction(production);
				}
				
				performance.setNote(getString(n, "tw:LISTING_DESCRIPTION",
						namespaceNode));
				String from = getString(n, "tw:LISTING_DATES/tw:LISTING_FROM",
						namespaceNode);
				String to = getString(n, "tw:LISTING_DATES/tw:LISTING_TO",
						namespaceNode);
				String open = getString(n, "tw:LISTING_DATES/tw:LISTING_OPEN",
						namespaceNode);
				if (from != null)
					performance.setFrom(sdf.parse(from));
				if (to != null)
					performance.setTo(sdf.parse(to));
				if (open != null)
					performance.setOpeningNight(sdf.parse(open));
				List<NVPair> pairs = performance.toSOLRIndex();
				if (pairs.size() > 0)
					solrHelper.postIndexEntries(pairs, false);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private Production getStubProduction(Session sess, Work work) {
		Production production = null;
		for (Production p : work.getProductions()) {
			if (p.getExternalId() == null || "".equals(p.getExternalId())) {
				production = p;
				break;
			}
		}
		if (production == null) {
			production = new Production();
			production.setSource(source);
			production.setWork(work);
			sess.save(production);
			List<NVPair> pairs = production.toSOLRIndex();
			if (pairs.size() > 0)
				solrHelper.postIndexEntries(pairs, false);
		}
		return production;
	}

}
