package com.liquidnet.service.reconciliation.util.csv.base;

import com.liquidnet.service.reconciliation.util.csv.annotation.*;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.CharUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class CsvIndexerProcessor {
	private static final Log logger	= LogFactory.getLog(CsvIndexerProcessor.class);

	public static String createHeader(Object o) {

		Field[] fields = FieldCache.getField(o);

		List<ValueHandler> list = new ArrayList<ValueHandler>();

		for (Field f : fields) {
			if (f.isAnnotationPresent(CsvIndexer.class)) {
				CsvIndexer annotation = f.getAnnotation(CsvIndexer.class);

				String value = f.getAnnotation(CsvHeader.class) != null ? f.getAnnotation(CsvHeader.class).value() : "";

				ValueHandler h = new ValueHandler(value, CsvType.STRING, annotation.index(), f.isAnnotationPresent(CsvIfBlank.class));

				list.add(h);

			}
		}
		Collections.sort(list, new Comparator<ValueHandler>() {

			public int compare(ValueHandler o1, ValueHandler o2) {
				return new Integer(o1.index).compareTo(new Integer(o2.index));
			}
		});

		StringBuilder sb = new StringBuilder();

		for (Iterator<ValueHandler> iterator = list.iterator(); iterator.hasNext();) {
			ValueHandler h = iterator.next();

			sb.append(h.value);

			if (iterator.hasNext()) {
				sb.append(",");
			}

		}

		return sb.toString();

	}

	private static class ValueHandler {
		private Object	value;
		private CsvType	type;
		private int		index;
		
		private boolean ifBlank;

		public ValueHandler(Object value, CsvType type, int index, boolean ifBlank) {
			super();
			this.value = value;
			this.type = type;
			this.index = index;
			this.ifBlank = ifBlank;
		}

	}

	public static String createLine(Object o) {
		Field[] fields = FieldCache.getField(o);

		List<ValueHandler> list = new ArrayList<ValueHandler>();

		for (Field f : fields) {
			if (f.isAnnotationPresent(CsvIndexer.class)) {
				CsvIndexer annotation = f.getAnnotation(CsvIndexer.class);

				Object value = getProperty(f.getName(), o);

				ValueHandler h = new ValueHandler(value, annotation.type(), annotation.index(), f.isAnnotationPresent(CsvIfBlank.class));

				list.add(h);

			}
		}

		Collections.sort(list, new Comparator<ValueHandler>() {

			public int compare(ValueHandler o1, ValueHandler o2) {
				return new Integer(o1.index).compareTo(new Integer(o2.index));
			}
		});

		StringBuilder sb = new StringBuilder();

		for (Iterator<ValueHandler> iterator = list.iterator(); iterator.hasNext();) {
			ValueHandler h = iterator.next();

			sb.append(toValue(h.type, h.value, h.ifBlank));

			if (iterator.hasNext()) {
				sb.append(",");
			}

		}

		return sb.toString();

	}
	
	public static Map<String, Object> parseSetValue(Object o) {

		Map<String, Object> map = new HashMap<String, Object>();
		Field[] fields = FieldCache.getField(o);
		for (Field f : fields) {
			if (f.isAnnotationPresent(CsvSetValue.class)) {
				CsvSetValue annotation = f.getAnnotation(CsvSetValue.class);
				Class<?> clz = annotation.clz();

				Object _object = newInstance(clz);

				setValue(_object, annotation.property(), getProperty(f.getName(), o));

				map.put(f.getName(), _object);
			}
		}

		return map;
	}

	public static void parseLine(Object o, String[] line) {
		Field[] fields = FieldCache.getField(o);
		if (fields.length < line.length) {
			throw new IllegalArgumentException("fields.length < line.length");
		}
		for (Field f : fields) {
			if (f.isAnnotationPresent(CsvIndexer.class)) {
				CsvIndexer annotation = f.getAnnotation(CsvIndexer.class);
				int index = annotation.index();
				if (index > line.length) {
					throw new IllegalArgumentException("index > line.length " + line.length);
				}
				if (index < 0) {
					throw new IllegalArgumentException("index < 0 ");
				}

				String value = line[index];
				
				if (value != null) {
					value = value.trim();
				}
				
				Object _value = null;
				try {
					_value = parseValue(annotation, value);
				} catch (Exception e) {
					logger.error("getValue at [" + o.getClass().getName() + "." + f.getName() + "] index[" + 
							index + "], value[" + value + "] expect type[" + annotation.type() + "] " + e);
					throw new RuntimeException(e);
				}

				setValue(o, f, _value);
			}
		}
	}

	private static Object toValue(CsvType type, Object value, boolean ifBlank) {
		if (CsvType.ISODATE.equals(type)) {
			SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
			df.setTimeZone(TimeZone.getTimeZone("UTC"));

			return value == null ? StringUtils.EMPTY : df.format(value);
		}
		if (CsvType.STRING.equals(type)) {
			return value == null ? StringUtils.EMPTY : escapeCsv(value.toString());
		}
		if (CsvType.DOUBLE.equals(type)) {
			if (value == null) {
				return StringUtils.EMPTY;
			} 
			if (Double.valueOf(value.toString()) == 0d && ifBlank) {
				return StringUtils.EMPTY;
			}
			return value.toString();
		}
		if (CsvType.INT.equals(type)) {
			if (value == null) {
				return StringUtils.EMPTY;
			} 
			if (Integer.parseInt(value.toString()) == 0 && ifBlank) {
				return StringUtils.EMPTY;
			}
			return value.toString();
		}
		if (CsvType.TIME.equals(type)) {
			SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			return value == null ? StringUtils.EMPTY : df.format(value);
		}
		throw new IllegalArgumentException("unsupport type:" + type);
	}

	private static Object parseValue(CsvIndexer annotation, String value) throws ParseException {
		if (CsvType.ISODATE.equals(annotation.type())) {
			return parseUtcDate(value);
		}
		if (CsvType.DATE.equals(annotation.type())) {
//			SimpleDateFormat df = new SimpleDateFormat();
			return StringUtils.isEmpty(value) ? null : DateUtils.parseDate(value, new String[]{"yyyy-MM-dd","yyyy/MM/dd","yyyyMMdd"});
		}
		if (CsvType.STRING.equals(annotation.type())) {
			return value;
		}
		if (CsvType.DOUBLE.equals(annotation.type())) {
			return StringUtils.isEmpty(value) ? (annotation.defaultValue()? 0d : null) : new Double(value);
		}
		if (CsvType.INT.equals(annotation.type())) {
			return StringUtils.isEmpty(value) ? null : new Integer(value);
		}
		throw new IllegalArgumentException("unsupport type:" + annotation.type());
	}

	public static Date parseUtcDate(String value) throws ParseException {
		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
		df.setTimeZone(TimeZone.getTimeZone("UTC"));
		return StringUtils.isEmpty(value) ? null : df.parse(value);
	}
	
	public static Date parseUtcDate0(String value) throws ParseException {
		SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
		df.setTimeZone(TimeZone.getTimeZone("UTC"));
		return StringUtils.isEmpty(value) ? null : df.parse(value);
	}
	
	public static String toSimpleDate(Date date) {
		SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
		return df.format(date);
	}
	
	public static String toUtcDate0(Date date) {
		SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
		df.setTimeZone(TimeZone.getTimeZone("UTC"));
		return df.format(date);
	}
    private static final char	CSV_DELIMITER		= ',';
	private static final char	CSV_QUOTE			= '"';
	private static final String	CSV_QUOTE_STR		= String.valueOf(CSV_QUOTE);
	private static final char[]	CSV_SEARCH_CHARS	= new char[] { CSV_DELIMITER, CSV_QUOTE, CharUtils.CR, CharUtils.LF };

	public static String escapeCsv(String str) {
		if (StringUtils.containsNone(str, CSV_SEARCH_CHARS)) {
			return str;
		}
		try {
			StringWriter writer = new StringWriter();
			escapeCsv(writer, str);
			return writer.toString();
		} catch (IOException ioe) {
			// this should never ever happen while writing to a StringWriter
			ioe.printStackTrace();
			return null;
		}
	}

	/**
	 * <p>
	 * Writes a <code>String</code> value for a CSV column enclosed in double
	 * quotes, if required.
	 * </p>
	 * 
	 * <p>
	 * If the value contains a comma, newline or double quote, then the String
	 * value is written enclosed in double quotes.
	 * </p>
	 * </p>
	 * 
	 * <p>
	 * Any double quote characters in the value are escaped with another double
	 * quote.
	 * </p>
	 * 
	 * <p>
	 * If the value does not contain a comma, newline or double quote, then the
	 * String value is written unchanged (null values are ignored).
	 * </p>
	 * </p>
	 * 
	 * see <a
	 * href="http://en.wikipedia.org/wiki/Comma-separated_values">Wikipedia</a>
	 * and <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.
	 * 
	 * @param str
	 *            the input CSV column String, may be null
	 * @param out
	 *            Writer to write input string to, enclosed in double quotes if
	 *            it contains a comma, newline or double quote
	 * @throws IOException
	 *             if error occurs on underlying Writer
	 * @since 2.4
	 */
	public static void escapeCsv(Writer out, String str) throws IOException {
		if (StringUtils.containsNone(str, CSV_SEARCH_CHARS)) {
			if (str != null) {
				out.write(str);
			}
			return;
		}
		out.write(CSV_QUOTE);
		for (int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);
			if (c == CSV_QUOTE) {
				out.write(CSV_QUOTE); // escape double quote
			}
			out.write(c);
		}
		out.write(CSV_QUOTE);
	}



	private static void setValue(Object o, Field f, Object value) {
		try {
			BeanUtils.setProperty(o, f.getName(), value);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}
	
	private static void setValue(Object o, String fieldName, Object value) {
		try {
			BeanUtils.setProperty(o, fieldName, value);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

	private static <T> Object getProperty(String fieldName, T object) {
		PropertyDescriptor pd = null;
		try {
			pd = new PropertyDescriptor(fieldName, object.getClass());
		} catch (IntrospectionException e) {
			e.printStackTrace();
		}

		return getProperty(pd, object);
	}

	private static <T> Object getProperty(PropertyDescriptor descriptor, T obejct) {
		if (descriptor.getReadMethod() != null) {
			Method readMethod = descriptor.getReadMethod();
			if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
				readMethod.setAccessible(true);
			}
			try {
				Object value = readMethod.invoke(obejct, new Object[0]);
				return value;
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		return null;
	}
	
	public static <T> Object newInstance(Class<T> cls) {
		T t = null;
		try {
			t = cls.newInstance();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return t;
	}
	

	public static class FieldCache {

		public static final Map<String, Field[]>	cache	= new ConcurrentHashMap<String, Field[]>(5);

		public static Field[] getField(Object o) {

			Field[] fields = o.getClass().getDeclaredFields();
			if (o.getClass().getSuperclass() != null) {
				fields = (Field[]) ArrayUtils.addAll(fields, o.getClass().getSuperclass().getDeclaredFields());
			}

			return fields;

		}
		
		public static Field getField(Object o, String fieldName) {

			Field[] fields = getField(o);
			for (Field f : fields) {
				if (f.getName().equals(fieldName)) {
					return f;
				}
			}

			return null;

		}

	}

}
