package com.tandbergtv.metadatamanager.model;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.log4j.Logger;

import com.tandbergtv.metadatamanager.util.DataTypeConstants;
import com.tandbergtv.metadatamanager.util.DataTypeMappingReader;
import com.tandbergtv.metadatamanager.util.DateUtil;


public abstract class FieldBase implements IField {
	protected String ttvXPath;
	protected String value;
	protected String largeValue;
	protected Date dateValue;
	protected Long intValue;
	protected Float floatValue;
	protected List<Integer> indices;
	protected Asset root;
	protected long id;
	protected String dataType;

	protected Asset parentAsset;

	protected Boolean parsable = true;

	private static final Logger logger = Logger.getLogger(Field.class);
	
	public FieldBase() {
		indices = new ArrayList<Integer>();
	}

	/**
	 * Ctor
	 * 
	 * @param ttvXPath
	 * @param value
	 */
	public FieldBase(String ttvXPath, String value) {
		this();
		this.setTtvXPath(ttvXPath);
		this.value = value;
	}

	//Copy constructor
	public FieldBase(IField copyFromField){
		this.setTtvXPath(copyFromField.getTtvXPath());
		if (copyFromField.getDataType() != null) {
			this.dataType = copyFromField.getDataType();
		}
		this.setValue(copyFromField.getValue());
		this.setIndices(copyFromField.getIndices());
		if (copyFromField.getRoot() != null) {
			this.setRoot(copyFromField.getRoot());
		}
		if (copyFromField.getParentAsset() != null) {
			this.setParentAsset(copyFromField.getParentAsset());
		}
	}
	
	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#getTtvXPath()
	 */
	public String getTtvXPath() {
		return ttvXPath;
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#setTtvXPath(java.lang.String)
	 */
	public void setTtvXPath(String ttvXPath) {
		this.ttvXPath = ttvXPath;
		checkAndSetDataType();
		if(value != null) {
			setValue(value);
		}
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#getValue()
	 */
	public String getValue() {
		if (largeValue != null && !largeValue.equals("")) {
			return largeValue;
		} else {
			try {
				if (dataType.equals(DataTypeConstants.INTEGER)) {
					if(intValue != null) {
						return String.valueOf(intValue);
					} else if(!parsable) {
						return value;
					} else {
						return "";
					}
				} else if (dataType.equals(DataTypeConstants.FLOAT)) {
					if(floatValue != null) {
						return String.valueOf(floatValue);
					} else if(!parsable) {
						return value;
					} else { 
						return "";
					}
				} else if (dataType.equals(DataTypeConstants.DATE)) {
					if(dateValue != null) {
						SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
						return sdf.format(dateValue);
					} else if(!parsable) {
						return value;
					} else {
						return "";
					}
				}
				return value;
			} catch (Exception e) {
				return value;
			}
		}
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#getTypedValue()
	 */
	public Object getTypedValue() {
		String value = this.getValue();
		if (value == null)
			return null;
		
		try {
			if (!parsable) {
				logger.debug("For xpath: " + this.ttvXPath + ", dataType: " + this.dataType + ", value: " + this.value + " returning unparseable value");
				return value;
			} else if (dataType.equals(DataTypeConstants.STRING)) {
				return value;
			} else if (dataType.equals(DataTypeConstants.INTEGER)) {
				return Long.parseLong(value);
			} else if (dataType.equals(DataTypeConstants.BOOLEAN)) {
				return Boolean.parseBoolean(value);
			} else if (dataType.equals(DataTypeConstants.FLOAT)) {
				return Float.parseFloat(value);
			} else if (dataType.equals(DataTypeConstants.TIME)) {
				return value;
			} else if (dataType.equals(DataTypeConstants.DATE)) {
				return DateUtil.convertDate(value);
			}
		} catch (Exception e) {
			logger.debug("For xpath: " + this.ttvXPath + ", dataType: " + this.dataType + ", value: " + this.value + " returning null");
			return null;
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#setTypedValue(java.lang.Object)
	 */
	public void setTypedValue(Object value) {
		String stringValue = null;
		
		/* Ignore the data type, convert the object to string and call set value */
		if (value instanceof Date) {
			stringValue = org.w3c.util.DateParser.getIsoDateNoMillis((Date) value);
		} else if ((value instanceof Boolean) || 
					(value instanceof Long) ||			
					(value instanceof Float) ||
					(value instanceof String)) {
			/* For recognized data types, convert to string */
			stringValue = value.toString();
		}

		this.setValue(stringValue);
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#setValue(java.lang.String)
	 */
	public void setValue(String value) {
		if (value != null && value.length() > 2000) {
			largeValue = value;
			this.value = value.substring(0, 2000);
		} else {
			largeValue = "";
			try {
				if (dataType.equals(DataTypeConstants.INTEGER)) {
					intValue = Long.parseLong(value);
					this.value = null;
				} else if (dataType.equals(DataTypeConstants.FLOAT)) {
					floatValue = Float.parseFloat(value);
					this.value = null;
				} else if (dataType.equals(DataTypeConstants.DATE)) {
					dateValue = DateUtil.convertDate(value);
					this.value = null;
				} else {
					this.value = value;
				}
				parsable = true;
			} catch (Exception e) {
				logger.debug("There was an error converting the value into the specified datatype. Defaulting to Error");
				this.value = value;
				parsable = false;
			}
		}
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#getIndices()
	 */
	public List<Integer> getIndices() {
		return indices;
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#setIndices(java.util.List)
	 */
	public void setIndices(List<Integer> indices) {
		this.indices = indices;
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#getRoot()
	 */
	public Asset getRoot() {
		return root;
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#setRoot(com.tandbergtv.metadatamanager.model.Asset)
	 */
	public void setRoot(Asset root) {
		this.root = root;
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#getStoredIndices()
	 */
	public String getStoredIndices() {
		StringBuilder sb = new StringBuilder();
		for (Integer index : indices) {
			sb.append("#");
			try {
				sb.append(index);
			} catch (Exception e) {
			}
		}
		return sb.toString();
	}

	@SuppressWarnings("unused")
	private void setStoredIndices(String indices) {
		String[] split = indices.split("#");
		boolean first = true;
		for (String index : split) {
			if (first) {
				first = false;
				continue;
			}
			try {
				this.indices.add(Integer.parseInt(index));
			} catch (Exception e) {
				this.indices.add(null);
			}
		}
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#getId()
	 */
	public long getId() {
		return id;
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#setId(long)
	 */
	public void setId(long id) {
		this.id = id;
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#getDataType()
	 */
	public String getDataType() {
		return dataType;
	}

	/**
	 * @param dataType the dataType to set
	 */
	public void setDataType(String dataType) {
		this.dataType = dataType;
		checkAndSetDataType();
	}

	/**
	 * for a given xpath, lookup the DataTypeXPathMap to find the appropriate
	 * datatype
	 * 
	 * @param ttvXPath
	 */
	protected void checkAndSetDataType() {
		if(this.ttvXPath != null && !this.ttvXPath.equals("")) {
			String type = DataTypeMappingReader.getInstance().determineDataType(this.ttvXPath);
			if (type == null || type.equals("")) {
				if(dataType == null || dataType.equals("")) {
					dataType = DataTypeConstants.STRING;
				}
			} else {
				dataType = type;
			}
		}
	}


	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#getParentAsset()
	 */
	public Asset getParentAsset() {
		return parentAsset;
	}

	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#setParentAsset(com.tandbergtv.metadatamanager.model.Asset)
	 */
	public void setParentAsset(Asset parentAsset) {
		this.parentAsset = parentAsset;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	/* (non-Javadoc)
	 * @see com.tandbergtv.metadatamanager.model.IField#toString()
	 */
	@Override
	public String toString() {
		return "[" + id + "] " + getTtvXPath() + ", " + getValue() + " "
				+ getStoredIndices();
	}

	/**
	 * @return the largeValue
	 */
	@SuppressWarnings("unused")
	private String getLargeValue() {
		return largeValue;
	}

	/**
	 * @param largeValue
	 *            the largeValue to set
	 */
	@SuppressWarnings("unused")
	private void setLargeValue(String largeValue) {
		this.largeValue = largeValue;
	}
	
	public Boolean getParsable() {
		return parsable;
	}

	public void setParsable(Boolean parsable) {
		this.parsable = parsable;
	}
	
	/**
	 * Returns the xpath with indices
	 * @return
	 */
	public String getTtvXPathWithIndices() {
		return buildTtvXPathWithIndices(ttvXPath, indices);
	}
	
	/**
	 * This is common code used both in Metadata Manager and rules so to prevent
	 * duplicate code I put it here as a public static method 
	 * @param xpath
	 * @param indices
	 * @return
	 */
	public static String buildTtvXPathWithIndices(String xpath, List<Integer> indices) {
		StringTokenizer strTok = new StringTokenizer(xpath,"/");
		String str = "";
		
		int i = 0;
		while(strTok.hasMoreTokens()) {
			String token = strTok.nextToken();
			
			if(i > 0) {
				str += "/";
			}
			
			if(!token.startsWith("@")) {
				token+="["+indices.get(i)+"]";
				i++;
			}
			
			str += token; 
		}
		
		return str;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(obj.getClass().getSuperclass().equals(FieldBase.class)) {
			FieldBase f = (FieldBase) obj;
			if(f.id > 0  && this.id > 0)
				return f.id == this.id;
			else {
				return f.dataType.equals(this.dataType) && 
				f.getTtvXPath().equals(this.getTtvXPath()) &&
				f.getValue().equals(this.getValue());
			}
		}
		return false;
	}
}
