package com.tandbergtv.watchpoint.pmm.title.conf;

import java.io.File;
import java.io.InputStream;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import com.tandbergtv.watchpoint.pmm.title.conf.jaxb.ComplexVariableType;
import com.tandbergtv.watchpoint.pmm.title.conf.jaxb.ParamListType;
import com.tandbergtv.watchpoint.pmm.title.conf.jaxb.ParamType;
import com.tandbergtv.watchpoint.pmm.title.conf.jaxb.SimpleVariableType;
import com.tandbergtv.watchpoint.pmm.title.conf.jaxb.TitleType;
import com.tandbergtv.watchpoint.pmm.title.conf.jaxb.VariableType;
import com.tandbergtv.watchpoint.pmm.title.conf.jaxb.SimpleVariableType.SearchInfo;
import com.tandbergtv.watchpoint.pmm.title.conf.jaxb.TitleType.Metadata;

/**
 * Parser interface for Specification definitions.
 * Converts JAXB objects into PMM-defined objects.
 * 
 * @author Raj Prakash
 */
class SpecificationReader {

	/**
	 * Unmarshals the file referenced by the filePath to a Specification.
	 * 
	 * @param filePath	the file that contains the specification structure
	 * @return			the Specification object
	 * @throws SpecificationValidationException	if there is any parse failure
	 */
	public Specification unmarshal(String filePath) throws SpecificationValidationException {
		try {
			JAXBContext jc = JAXBContext.newInstance(SpecificationReader.class.getPackage().getName() + ".jaxb");
			Unmarshaller unmarshaller = jc.createUnmarshaller();
			com.tandbergtv.watchpoint.pmm.title.conf.jaxb.Specification jaxbSpec = 
				(com.tandbergtv.watchpoint.pmm.title.conf.jaxb.Specification) unmarshaller.unmarshal(new File(filePath));
			return convertToSpecification(jaxbSpec);
		} catch(JAXBException e) {
			throw new SpecificationValidationException(e);
		}
	}

	/**
	 * Unmarshals the file referenced by the input stream to a Specification.
	 * 
	 * @param stream Input Stream to the file that contains the specification structure
	 * @return			the Specification object
	 * @throws SpecificationValidationException	if there is any parse failure
	 */
	public Specification unmarshal(InputStream stream) throws SpecificationValidationException {
		try {
			JAXBContext jc = JAXBContext.newInstance(SpecificationReader.class.getPackage().getName() + ".jaxb");
			Unmarshaller unmarshaller = jc.createUnmarshaller();
			com.tandbergtv.watchpoint.pmm.title.conf.jaxb.Specification jaxbSpec = 
				(com.tandbergtv.watchpoint.pmm.title.conf.jaxb.Specification) unmarshaller.unmarshal(stream);
			return convertToSpecification(jaxbSpec);
		} catch(JAXBException e) {
			throw new SpecificationValidationException(e);
		}
	}

	/*
	 * Converts the JAXB Specification object into Specification object.
	 */
	private Specification convertToSpecification(com.tandbergtv.watchpoint.pmm.title.conf.jaxb.Specification jaxbSpec) throws SpecificationValidationException {
		Specification s = new Specification();
		
		s.setName(jaxbSpec.getName());
		s.setConverterClass(jaxbSpec.getConverterClass());
		
		//Job Rule Menu Options
		ParamListType jrmOptionsListType = jaxbSpec.getJobRuleMenuOptions();
		if(jrmOptionsListType != null) {
			List<ParamType> jrmOptionsList = jrmOptionsListType.getParam();
			if(jrmOptionsList != null) {
				for(ParamType jrmOption : jrmOptionsList) {
					String name = jrmOption.getName();
					String value = jrmOption.getValue();
					if(isNullOrEmpty(name) || isNullOrEmpty(value)) {
						throw new SpecificationValidationException("Job Rule Menu Options Name or Value cannot be null, empty or blank");
					}
					s.addJobRuleMenuOption(name, value);
				}
			}
		}
		
		//Job Param Menu Options
		ParamListType jpmOptionsListType = jaxbSpec.getJobParameterMenuOptions();
		if(jpmOptionsListType != null) {
			List<ParamType> jpmOptionsList = jpmOptionsListType.getParam();
			if(jpmOptionsList != null) {
				for(ParamType jpmOption : jpmOptionsList) {
					String name = jpmOption.getName();
					String value = jpmOption.getValue();
					if(isNullOrEmpty(name) || isNullOrEmpty(value)) {
						throw new SpecificationValidationException("Job Parameter Menu Options Name or Value cannot be null, empty or blank");
					}
					s.addJobParameterMenuOption(name, value);
				}
			}
		}
		
		//Titles
		List<TitleType> titleList = jaxbSpec.getTitle();
		if(titleList != null) {
			for(TitleType title : titleList) {
				s.addRootTitleConf(convertToTitleConf(s, title));
			}
		}

		return s;
	}

	/*
	 * Converts JAXB Title object into TitleConf object.
	 */
	private TitleConf convertToTitleConf(Specification specification, TitleType t)
	        throws SpecificationValidationException {
		TitleConf tc = new TitleConf(specification);

		tc.setName(trim(t.getName()));
		tc.setAlias(t.getAlias());
		tc.setHasAsset(t.isHasAsset());

		tc.setMin(t.getMin());
		tc.setMax(t.getMax());

		// Metadata
		Metadata m = t.getMetadata();
		if (m != null) {
			List<VariableType> variables = m.getVariable();
			if (variables != null) {
				for (VariableType var : variables) {
					Variable variable = convertToVariable(tc, var);
					if (variable != null)
						tc.addMetadata(variable);
				}
			}
		}

		// Child Titles
		if (t.getChildTitles() != null && t.getChildTitles().getTitle() != null) {
			for (TitleType childTitle : t.getChildTitles().getTitle()) {
				TitleConf child = this.convertToTitleConf(specification, childTitle);
				tc.addChild(child);
			}
		}

		return tc;
	}

	/*
	 * Converts JAXB VariableType object into Variable object.
	 */
	private Variable convertToVariable(TitleConf tc, VariableType vt)
	        throws SpecificationValidationException {
		Variable v = null;
		if (SimpleVariableType.class.equals(vt.getClass())) {
			v = convertToSimpleVariable(tc, SimpleVariableType.class.cast(vt));
		} else if (ComplexVariableType.class.equals(vt.getClass())) {
			v = convertToComplexVariable(tc, ComplexVariableType.class.cast(vt));
		} else {
			String msg = "Failed to convert variable type of class: " + vt.getClass();
			throw new SpecificationValidationException(msg);
		}

		return v;
	}

	/*
	 * Build the Simple Variable
	 */ 
	private SimpleVariable convertToSimpleVariable(TitleConf tc, SimpleVariableType vt)
	        throws SpecificationValidationException {
		SimpleVariable v = new SimpleVariable(tc);

		/* Copy the common properties */
		this.buildVariable(vt, v);
		
		v.setDataType(vt.getDatatype());
		v.setRequired(vt.isRequired());
		v.setFilePath(vt.isFilePath());
		v.setKey(vt.isKey());
		v.setSearchField(vt.isSearchField());
		v.setExternalSearchField(vt.isExternalSearchField());
		v.setAttribute(vt.isAttribute());
		
		//Search Info
		SearchInfo si = vt.getSearchInfo();
		if(si != null) {
			v.setSearchFieldDataProvider(trim(si.getDataProvider()));
			List<ParamType> constantsList = si.getConstant();
			if(constantsList != null) {
				for(ParamType constant : constantsList) {
					String name = constant.getName();
					String value = constant.getValue();
					if(isNullOrEmpty(name) || isNullOrEmpty(value)) {
						throw new SpecificationValidationException("Search Field Constant Name or Value cannot be null, blank or empty");
					}
					v.addSearchFieldValue(trim(name), trim(value));
				}
			}
		}
		
		return v;
	}

	/*
	 * Build the complex variable
	 */
	private ComplexVariable convertToComplexVariable(TitleConf tc, ComplexVariableType vt)
	        throws SpecificationValidationException {
		ComplexVariable v = new ComplexVariable(tc);

		/* Copy the common properties and complex variable properties */
		buildVariable(vt, v);
		v.setShowCollapsed(vt.isShowCollapsed());
		
		List<VariableType> childTypes = vt.getVariable();
		if (childTypes != null) {
			for (VariableType childType : childTypes) {
				Variable child = convertToVariable(tc, childType);
				v.addChild(child);
			}
		}

		return v;
	}

	/*
	 * Copy all the common properties of variable
	 */
	private void buildVariable(VariableType vt, Variable v) {
		v.setName(vt.getName());
		v.setDisplayName(vt.getDisplayName());
		v.setXPath(vt.getXpath());
		v.setMax(vt.getMax());
		v.setMin(vt.getMin());
	}
	
	/*
	 * Checks if the given string is null, empty, or blank. 
	 */
	private boolean isNullOrEmpty(String s) {
		return s == null || s.trim().length() == 0;
	}
	
	/*
	 * Trims the given string.
	 */
	private String trim(String s) {
		return (s != null) ? s.trim() : null; 
	}

}
