package info.bukova.isspst.services.addressbook;

import info.bukova.isspst.data.Address;
import info.bukova.isspst.services.IsspstException;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.ValidationException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

public class AddressFinderAres implements AddressFinder {
	
	private Map<String, String> conditions;
	private String aresUrl;
	private Unmarshaller unmarsheller;
	
	private NameValuePair[] buildQueryString() {
		List<NameValuePair> resList = new ArrayList<NameValuePair>();
		
		for (String key : conditions.keySet()) {
			resList.add(new NameValuePair(key, conditions.get(key)));
		}
		
		return resList.toArray(new NameValuePair[resList.size()]);
	}
	
	private InputStream getAresResponse() throws HttpException, IOException, IsspstException {
		GetMethod httpGet = new GetMethod(aresUrl);
		HttpClient httpCli = new HttpClient();
		
		httpGet.setQueryString(buildQueryString());
		int res = httpCli.executeMethod(httpGet);
		
		if (res != 200) {
			throw new IsspstException("Server returned error code " + String.valueOf(res));
		}
		
		return httpGet.getResponseBodyAsStream();
	}
	
	private StringReader transformOut(InputStream inXml) throws IsspstException {
		TransformerFactory tf = TransformerFactory.newInstance();
		
		Resource resource = new ClassPathResource("info/bukova/isspst/services/addressbook/ares.xsl");
		
		Transformer transformer;
		StringWriter sWriter = null;
		InputStream is = null;
		try {
			sWriter = new StringWriter();
			is = resource.getInputStream();
			StreamSource src = new StreamSource(is);
			transformer = tf.newTransformer(src);
			is.close();
					
			StreamSource source = new StreamSource(inXml);
			StreamResult result = new StreamResult(sWriter);
			transformer.transform(source, result);
			sWriter.flush();
			sWriter.close();
			
			return new StringReader(sWriter.getBuffer().toString());
		} catch (TransformerException e) {
			throw new IsspstException("ARES transformation error", e.getCause());
		} catch (IOException e) {
			throw new IsspstException("Transformation file \"ares.xsl\" not found.", e.getCause());
		} finally {
			try {
				if (sWriter != null) {
					sWriter.flush();
					sWriter.close();
				}
			} catch (IOException e) {
				throw new IsspstException("Cannot close stream", e.getCause());
			}
			try {
				if (is != null) {
					is.close();
				}
			} catch (IOException e) {
				throw new IsspstException("Cannot close stream", e.getCause());
			}
		}
	}
	
	private List<Address> convertToAddrList(AresOdpoved odpoved) {
		List<Address> resList = new ArrayList<Address>();
		
		if (odpoved == null || odpoved.getPocetZaznamu() <= 0)
			return resList;
		
		for (AresZaznam zaznam : odpoved.getAresZaznam()) {
			Address addr = new Address();
			addr.setCompany(zaznam.getFirma());
			addr.setIc(zaznam.getIco());
			addr.setStreet(zaznam.getUlice());
			addr.setHouseNumber(zaznam.getCp());
			addr.setCity(zaznam.getObec());
			addr.setZipCode(zaznam.getPsc());
			resList.add(addr);
		}
		return resList;
	}

	private void setConditionAddr(Address condAddress) {
		if (conditions == null)
			conditions = new HashMap<String, String>();
		else
			conditions.clear();
		
		if (condAddress.getCompany() != null && !condAddress.getCompany().isEmpty())
			conditions.put("obchodni_firma", condAddress.getCompany());
		if (condAddress.getIc() != 0)
			conditions.put("ico", String.valueOf(condAddress.getIc()));
		if (condAddress.getCity() != null && !condAddress.getCity().isEmpty())
			conditions.put("nazev_obce", condAddress.getCity());
		conditions.put("max_pocet", "200");
		conditions.put("czk", "utf");
	}

	@Override
	public synchronized List<Address> findAddress(Address condAddress) throws IsspstException {
		setConditionAddr(condAddress);
		AresOdpoved odpoved;
		InputStream is = null;
		try {
			is = getAresResponse();
			odpoved = (AresOdpoved) unmarsheller.unmarshal(transformOut(is));
			is.close();
			return convertToAddrList(odpoved);
		} catch (MarshalException e) {
			throw new IsspstException("Can't create ARES answer object", e.getCause());
		} catch (ValidationException e) {
			throw new IsspstException("ARES answer is not valid", e.getCause());
		} catch (HttpException e) {
			throw new IsspstException("Error while comunication with ARES server", e.getCause());
		} catch (IOException e) {
			throw new IsspstException("ARES find error", e.getCause());
		} finally {
			try {
				if (is != null)
					is.close();
			} catch (IOException e) {
				throw new IsspstException("Cannot close stream", e.getCause());
			}
		}
		
	}

	public void setAresUrl(String aresUrl) {
		this.aresUrl = aresUrl;
	}

	public void setUnmarsheller(Unmarshaller unmarsheller) {
		this.unmarsheller = unmarsheller;
	}

}
