/*
 * Created on Aug 18, 2008 (C) Copyright TANDBERG Television Ltd.
 */

package com.tandbergtv.watchpoint.pmm.title.validation;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import com.tandbergtv.watchpoint.pmm.entities.Title;
import com.tandbergtv.watchpoint.pmm.title.conf.ISpecificationManager;
import com.tandbergtv.watchpoint.pmm.title.conf.Specification;
import com.tandbergtv.watchpoint.pmm.util.validation.ValidationException;
import com.tandbergtv.watchpoint.pmm.util.validation.ValidationMessage;
import com.tandbergtv.workflow.core.service.ServiceRegistry;

/**
 * Service that provides validation of a Title that needs to be stored in the Title repository.
 * 
 * @author Vijay Silva
 */
public class TitleValidationService implements ITitleValidationService {

	/**
	 * Constructor
	 */
	public TitleValidationService() {
	}

	/**
	 * {@inheritDoc}
	 */
	@Deprecated
	public void validateTitle(Title title) throws ValidationException {
		/*if (title == null) {
			throw new IllegalArgumentException("The title for validation cannot be null.");
		}

		 The list of validation messages for validation failure messages 
		List<ValidationMessage> validationMessages = new ArrayList<ValidationMessage>();

		 Validate that the title specification is present and valid 
		this.validateSpecification(title, validationMessages);

		 Perform all validation defined by the specification 
		this.validateTitleBySpecification(title, validationMessages);

		 Throw new validation exception if the validation fails 
		if (validationMessages.size() > 0) {
			throw new ValidationException(validationMessages);
		}*/
	}

	/* Validates that the specification name is present and that is a valid specification */
	@Deprecated
	private void validateSpecification(Title title, List<ValidationMessage> messages) {
		//TODO
		/*String name = title.getSpecification();
		if (name == null || name.trim().length() == 0) {
			String code = TitleValidationCode.SPEC_UNDEFINED.toString();
			ValidationMessage message = new ValidationMessage(code);
			messages.add(message);
		} else {
			Specification specification = this.getSpecification(name);
			if (specification == null) {
				String code = TitleValidationCode.SPEC_NAME_INVALID.toString();
				ValidationMessage message = new ValidationMessage(code);
				message.getProperties().add(name);
				messages.add(message);
			}
		}*/
	}

	// ========================================================================
	// ===================== TITLE SPECIFICATION VALIDATION
	// ========================================================================

	/* Validate that the title obeys the rules defined by the specification */
	/*private void validateTitleBySpecification(Title title, List<ValidationMessage> messages) {
		 Check if the specification is provided 
		Specification specification = this.getSpecification(title.getSpecification());
		if (specification == null)
			return;

		 Validate that the title is unique based on the keys specified in the specification 
		this.validateKeysUnique(title, specification, messages);

		 Validate that title structure obeys the specification 
		this.validateStructure(title, specification, messages);

		 Validate the title metadata 
		this.validateMetadata(title, specification, messages);
	}*/

	/* Validates that the title structure obeys the structure defined by the specification */
	/*private void validateStructure(Title title, Specification specification,
			List<ValidationMessage> messages) {
		 Validate that the root title name matches the expected name 
		TitleConf rootTitleConf = specification.getRootTitleConf();
		String expectedName = rootTitleConf.getName();
		if (!expectedName.equals(title.getName())) {
			String code = TitleValidationCode.TITLE_ROOT_SECTION_NAME_MISMATCH.getCode();
			ValidationMessage message = new ValidationMessage(code);
			message.getProperties().add(title.getName());
			message.getProperties().add(expectedName);
			messages.add(message);
		}

		 Validate that each title has the expected parent title 
		List<Title> allTitles = this.getAllTitles(title);
		for (Title currentTitle : allTitles) {
			 Check if TitleConf exists for corresponding title 
			TitleConf titleConf = specification.getTitleConfByName(currentTitle.getName());
			if (titleConf == null)
				continue;

			Title parentTitle = currentTitle.getParent();
			String parentName = (parentTitle != null) ? parentTitle.getName() : null;
			String expectedParentName = (titleConf.getParentName());
			if (expectedParentName == null || expectedParentName.trim().length() == 0) {
				if (parentName != null) {
					 Parent is defined, but specification indicates no parent 
					String code = TitleValidationCode.TITLE_PARENT_INVALID.getCode();
					ValidationMessage message = new ValidationMessage(code);
					message.getProperties().add(title.getName());
					message.getProperties().add(parentName);
					messages.add(message);
				}
			} else if (!expectedParentName.equals(parentName)) {
				 Parent does not match the specification 
				String code = (parentName == null) ? TITLE_PARENT_MISSING.getCode()
						: TITLE_PARENT_MISMATCH.getCode();
				ValidationMessage message = new ValidationMessage(code);
				message.getProperties().add(title.getName());
				if (parentName != null)
					message.getProperties().add(parentName);
				message.getProperties().add(expectedParentName);
				messages.add(message);
			}
		}
	}*/

	// ========================================================================
	// ===================== DATATYPE VALIDATION
	// ========================================================================

	/* Validate that the Title is unique based on the metadata keys defined in the specification */
	/*private void validateKeysUnique(Title rootTitle, Specification specification,
			List<ValidationMessage> messages) {
		List<Variable> keyVariables = specification.getKeyMetadataProps();
		if (keyVariables == null || keyVariables.size() == 0)
			return;

		Map<String, String> metadataKeys = new HashMap<String, String>();
		boolean allKeysPresent = true;
		for (Variable key : keyVariables) {
			Title title = rootTitle.getTitle(key.getTitleConfName());
			String value = (title != null) ? title.getMetadataFieldValue(key.getName()) : null;
			if (value == null || value.trim().length() == 0) {
				allKeysPresent = false;
				break;
			}
			metadataKeys.put(key.getName(), value);
		}

		 Validate the keys only if all keys are present 
		if (!allKeysPresent)
			return;

		SearchCriteria criteria = this.buildSearchCriteria(rootTitle, metadataKeys);
		ServiceRegistry registry = ServiceRegistry.getDefault();
		ITitleSearchService searchService = registry.lookup(ITitleSearchService.class);
		Collection<Title> titles = searchService.search(criteria);
		if (titles.size() > 0) {
			Title matchingTitle = titles.iterator().next();
			if (!rootTitle.equals(matchingTitle)) {
				String code = TitleValidationCode.METADATA_KEYS_DUPLICATE.getCode();
				ValidationMessage message = new ValidationMessage(code);
				messages.add(message);
			}
		}
	}*/

	/* Build the Search Criteria required to search for a title by the specification keys */
	/*private SearchCriteria buildSearchCriteria(Title rootTitle, Map<String, String> metadataKeys) {
		SearchCriteria criteria = new SearchCriteria();
		criteria.setRecordsCount(1);

		 Add the basic title entity criteria 
		Entity title = new Entity(TITLE.toString(), Title.class, "t");
		title.addParameter(new ValueParameter(TITLE_PARENT.toString(), STRING));
		title.addParameter(new ValueParameter("isActive", NUMERIC, 1));
		String specName = rootTitle.getSpecification();
		title.addParameter(new ValueParameter(TITLE_SPEC.toString(), STRING, specName));
		criteria.addParameter(title);

		 Add all metadata keys 
		for (String key : metadataKeys.keySet()) {
			String value = metadataKeys.get(key);
			Entity metadata = new Entity(key, COMPLETE_METADATA.toString(), "m");
			metadata.addParameter(new ValueParameter(METADATA_NAME.toString(), STRING, key));
			metadata.addParameter(new ValueParameter(METADATA_VALUE.toString(), STRING, value));
			title.addParameter(metadata);
		}

		return criteria;
	}*/

	// ========================================================================
	// ===================== TITLE METADATA VALIDATION
	// ========================================================================

	/*
	 * Validates the metadata of the root title and all its children ensuring that the data types
	 * defined for the metadata are obeyed, and that all required fields are present
	 */
	/*private void validateMetadata(Title rootTitle, Specification specification,
			List<ValidationMessage> messages) {
		 Validate that all fields required by the metadata are defined 
		this.validateRequiredMetadata(rootTitle, specification, messages);

		 Validate each metadata field in the title and its children 
		this.validateMetadataByDatatype(rootTitle, specification, messages);
	}*/

	/* Validates all metadata fields that are required by the specification */
	/*private void validateRequiredMetadata(Title rootTitle, Specification specification,
			List<ValidationMessage> messages) {
		List<Variable> requiredMetadata = specification.getRequiredMetadataProps();
		if (requiredMetadata != null) {
			for (Variable requiredVariable : requiredMetadata) {
				String titleSectionName = requiredVariable.getTitleConfName();
				Title title = rootTitle.getTitle(titleSectionName);
				String name = requiredVariable.getName();
				String value = (title != null) ? title.getMetadataFieldValue(name) : null;
				if (value == null || value.trim().length() == 0) {
					String code = TitleValidationCode.METADATA_REQUIRED_MISSING.getCode();
					ValidationMessage message = new ValidationMessage(code);
					message.getProperties().add(titleSectionName);
					message.getProperties().add(requiredVariable.getName());
					message.getProperties().add(requiredVariable.getDisplayName());
					messages.add(message);
				}
			}
		}
	}*/

	/* Validate the metadata value based on the data type defined in the specification */
	/*private void validateMetadataByDatatype(Title rootTitle, Specification specification,
			List<ValidationMessage> messages) {
		List<Title> allTitles = getAllTitles(rootTitle);
		for (Title currentTitle : allTitles) {
			TitleConf titleConf = specification.getTitleConfByName(currentTitle.getName());
			Collection<TitleMetadata> titleMetadata = currentTitle.getMetadata();
			if (titleConf != null && titleMetadata != null) {
				for (TitleMetadata metadata : titleMetadata) {
					String name = metadata.getName();
					Variable variable = titleConf.getMetadataVariable(name);
					this.validateMetadataByDatatype(rootTitle, metadata, variable, messages);
				}
			}
		}
	}*/

	/*
	 * Validate the metadata value given the parent title section and the definition for the
	 * metadata field
	 */
	/*private void validateMetadataByDatatype(Title title, TitleMetadata metadata,
			Variable metadataDefinition, List<ValidationMessage> messages) {
		 If no metadata definition exists, no validation is required 
		if (metadataDefinition == null)
			return;

		 If no value is present, no validation is required 
		String value = metadata.getValue();
		if (value == null || value.trim().length() == 0)
			return;

		 Validate the value based on the data type string 
		String dataType = metadataDefinition.getDataType();
		String code = null;
		String type = null;
		String pattern = null;
		if (Datatype.BOOLEAN.toString().equals(dataType)) {
			if (!isValidBoolean(value)) {
				code = TitleValidationCode.METADATA_VALUE_DATATYPE_MISMATCH.getCode();
				type = "boolean";
			}
		} else if (Datatype.INT.toString().equals(dataType)) {
			if (!isValidInteger(value)) {
				code = TitleValidationCode.METADATA_VALUE_DATATYPE_MISMATCH.getCode();
				type = "integer";
			}
		} else if (Datatype.DATE.toString().equals(dataType)) {
			if (!isValidDate(value)) {
				code = TitleValidationCode.METADATA_VALUE_DATATYPE_MISMATCH_WITH_PATTERN.getCode();
				type = "date";
				pattern = this.getDatePattern();
			}
		} else if (Datatype.DURATION.toString().equals(dataType)) {
			if (!isValidDuration(value)) {
				code = TitleValidationCode.METADATA_VALUE_DATATYPE_MISMATCH_WITH_PATTERN.getCode();
				type = "duration";
				pattern = this.getDurationPattern();
			}
		}

		if (code != null) {
			ValidationMessage message = new ValidationMessage(code);
			message.getProperties().add(title.getName());
			message.getProperties().add(metadata.getName());
			message.getProperties().add(metadataDefinition.getDisplayName());
			message.getProperties().add(type);
			if (pattern != null)
				message.getProperties().add(pattern);
			messages.add(message);
		}
	}*/

	// ========================================================================
	// ===================== DATATYPE VALIDATION
	// ========================================================================

	/*
	 * A value is a valid boolean if the value equals 'true' or 'false' (case insensitive
	 * comparison).
	 */
	private boolean isValidBoolean(String value) {
		boolean valid = false;

		if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
			valid = true;
		} else if (Boolean.FALSE.toString().equalsIgnoreCase(value)) {
			valid = true;
		}

		return valid;
	}

	/*
	 * A value is a valid integer if the value can be converted to a long value.
	 */
	private boolean isValidInteger(String value) {
		boolean valid = false;

		try {
			Long.parseLong(value);
			valid = true;
		} catch (Exception e) {
		}

		return valid;
	}

	/*
	 * A value is a valid date if the value obeys the date pattern. Characters that exist in the
	 * value after the pattern is obeyed are ignored.
	 */
	private boolean isValidDate(String value) {
		boolean valid = false;

		try {
			DateFormat dateFormat = new SimpleDateFormat(this.getDatePattern());
			dateFormat.setLenient(false);
			Date dateValue = dateFormat.parse(value);
			valid = (dateValue != null);
		} catch (Exception e) {
		}

		return valid;
	}

	/*
	 * A value is a valid duration if the value obeys the duration pattern. Characters that exist in
	 * the value after the pattern is obeyed are ignored.
	 */
	private boolean isValidDuration(String value) {
		boolean valid = false;

		try {
			DateFormat durationFormat = new SimpleDateFormat(this.getDurationPattern());
			Date dateValue = durationFormat.parse(value);
			valid = (dateValue != null);
		} catch (Exception e) {
		}

		return valid;
	}

	// ========================================================================
	// ===================== INTERNAL HELPER METHODS
	// ========================================================================

	/* Get the specification given the name of the specification */
	private Specification getSpecification(String specificationName) {
		ServiceRegistry registry = ServiceRegistry.getDefault();
		ISpecificationManager specManager = registry.lookup(ISpecificationManager.class);
		return specManager.getSpecificationByName(specificationName);
	}

	/* Get all the Titles given the root title in BFS order */
	/*private List<Title> getAllTitles(Title rootTitle) {
		List<Title> allTitles = new ArrayList<Title>();
		allTitles.add(rootTitle);

		int index = 0;
		while (index < allTitles.size()) {
			Title currentTitle = allTitles.get(index);
			Collection<Title> children = currentTitle.getChildren();
			if (children != null) {
				allTitles.addAll(children);
			}
			index++;
		}

		return allTitles;
	}*/

	/* Get the Date pattern to use for validation */
	private String getDatePattern() {
		// TODO: Get from common location
		return "yyyy-MM-dd";
	}

	/* Get the Duration Pattern used for validation */
	private String getDurationPattern() {
		// TODO: Get from common location		
		return "HH:mm:ss";
	}

	/**
	 * Gets the name of the service: 'Title Validation Service'
	 * 
	 * @see com.tandbergtv.workflow.core.service.Service#getServiceName()
	 */
	public String getServiceName() {
		return "Title Validation Service";
	}

	/**
	 * Does nothing.
	 * 
	 * @see com.tandbergtv.workflow.core.service.ServiceLifecycle#start()
	 */
	public void start() {
	}

	/**
	 * Does nothing.
	 * 
	 * @see com.tandbergtv.workflow.core.service.ServiceLifecycle#start()
	 */
	public void stop() {
	}
}
