package hiro.yoshioka.sdh;

import hiro.yoshioka.sdh.verify.RowVerifyModel;
import hiro.yoshioka.sdh.verify.VerifyAndList;
import hiro.yoshioka.sdh.verify.VerifyOrMap;
import hiro.yoshioka.util.CSVUtil;
import hiro.yoshioka.util.FileUtil;
import hiro.yoshioka.util.StringUtil;
import hiro.yoshioka.util.Util;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import jp.sf.orangesignal.csv.Csv;
import jp.sf.orangesignal.csv.CsvConfig;
import jp.sf.orangesignal.csv.handlers.StringArrayListHandler;

public class CSVRecordDataHolder extends StringRecordDataHolder {
	private static final long serialVersionUID = -4781540870053999050L;
	transient protected HashMap<Integer, StringRecordData[]> fBackUpMap = new HashMap<Integer, StringRecordData[]>();
	private Set<Integer> notNullIdxSet;
	private List<RowVerifyModel> rowVerifyList;
	private CsvConfig csv_config;

	public CSVRecordDataHolder() {
	}

	public CSVRecordDataHolder(File file, String encoding, CsvConfig config,
			boolean ignoreFirstRowColumnStack) {
		String target = FileUtil.getText(file, encoding);
		if (config == null) {
			this.csv_config = CSVUtil.parseCsvConfig(target);
		} else {
			this.csv_config = config;
		}
		boolean ignoreFirstRowColumn = false;
		String[][] lists = CSVUtil.split(target, this.csv_config);
		if (lists.length > 0) {
			if (ignoreFirstRowColumnStack) {
				if (lists[0].length > 0) {
					if ("ROW".equalsIgnoreCase(lists[0][0])) {
						ignoreFirstRowColumn = true;
						lists[0] = Arrays.copyOfRange(lists[0], 1,
								lists[0].length);
					}
				}
			}
			initKeys(lists[0]);
			for (int i = 1; i < lists.length; i++) {
				if (ignoreFirstRowColumn) {
					lists[i] = Arrays.copyOfRange(lists[i], 1, lists[i].length);
				}
				addRow(lists[i]);
			}
		} else {
			initKeys(StringUtil.EMPTY_STRING_ARRAY);
		}
	}

	public CSVRecordDataHolder(String[] argNames) throws NullPointerException {
		initKeys(argNames);
	}

	private void initKeys(String[] argNames) {
		String[] nargNames = new String[argNames.length + 1];
		for (int i = 0; i < argNames.length; i++) {
			nargNames[i + 1] = argNames[i];
		}
		nargNames[0] = "ROW";
		key = nargNames;
	}

	public String[] getNoRowKeys() {
		String[] ret = new String[key.length - 1];
		for (int i = 0; i < ret.length; i++) {
			ret[i] = key[i + 1];
		}
		return ret;
	}

	public CsvConfig getCsvConfig() {
		return csv_config;
	}

	public void addRowRelpaceHead(StringRecordData[] datas) {
		HeaderData head = new HeaderData(Integer.toString(getRowCount() + 1));

		StringRecordData[] in = new StringRecordData[datas.length];
		for (int i = 1; i < datas.length; i++) {
			try {
				in[i] = (StringRecordData) datas[i].clone();
			} catch (CloneNotSupportedException e) {
				e.printStackTrace();
			}
		}
		in[0] = head;
		list.add(in);
	}

	public void addRow(StringRecordData[] datas) {
		HeaderData head = new HeaderData(Integer.toString(getRowCount() + 1));

		if (datas == null) {
			datas = createEmptyRow();
			head.type = HeaderType.INSERT;
			datas[0] = head;
			list.add(datas);
		} else {
			StringRecordData[] in = new StringRecordData[datas.length + 1];
			for (int i = 0; i < datas.length; i++) {
				in[i + 1] = datas[i];
			}
			in[0] = head;
			list.add(in);
		}
	}

	public void changeStatus(int index, HeaderType new_type) {
		if (((HeaderData) getStringRecordRow(index)[0]).insert()) {
			if (HeaderType.UPDATE.equals(new_type)
					|| HeaderType.DELETE.equals(new_type)) {
				return;
			}
		}
		((HeaderData) getStringRecordRow(index)[0]).type = new_type;
	}

	public CSVRecordDataHolder load(File file, String encoding, CsvConfig config) {
		CSVRecordDataHolder ret = null;
		List<String[]> csvList;
		try {
			csvList = Csv.load(file, encoding, config,
					new StringArrayListHandler());
			if (csvList == null || csvList.size() == 0) {
				return null;
			}
			ret = new CSVRecordDataHolder(csvList.get(0));
			for (int i = 1; i < csvList.size(); i++) {
				ret.addRow(csvList.get(i));
			}
		} catch (IOException e) {
			log.error(e);
		}
		return ret;
	}

	public boolean saveCSV(File file, String encoding, CsvConfig config,
			boolean withRowColumn) {
		try {
			Csv.save(getAllRecordList(true, withRowColumn), file, encoding,
					config, new StringArrayListHandler());
			return true;
		} catch (IOException e) {
			log.error(e);
		}
		return false;
	}

	public String toCSV(CsvConfig config, boolean withRowColumn) {

		StringWriter writer = new StringWriter();
		try {
			Csv.save(getAllRecordList(true, withRowColumn), writer, config,
					new StringArrayListHandler());
		} catch (Throwable e) {
			log.error(e);
			return "Error" + StringUtil.LINE_SEPARATOR
					+ "Cant convert CSV check CsvConfig";
		}
		return writer.toString();

	}

	/**
	 * @param row
	 * @throws CloneNotSupportedException
	 */
	public void createBackUp(int row) throws CloneNotSupportedException {
		StringRecordData[] data = fBackUpMap.get(new Integer(row));
		if (getRowCount() <= row) {
			return;
		}
		if (data == null) {
			data = getStringRecordRow(row);
			StringRecordData[] clones = new StringRecordData[data.length];
			for (int i = 0; i < clones.length; i++) {
				clones[i] = (StringRecordData) data[i].clone();
			}
			fBackUpMap.put(new Integer(row), clones);
		}
	}

	public CSVRecordDataHolder createIncetanceByClip(int x1, int y1, int x2,
			int y2) {
		if (log.isDebugEnabled()) {
			log.debug("x1:" + x1 + " y1:" + y1 + " x2:" + x2 + " y2:" + y2);
		}
		if (x1 < 1 || x2 < 1) {
			throw new IllegalArgumentException("x1 ,x2  >= 1!!!!");
		}
		String[] newkey = new String[x2 - x1 + 1];

		for (int i = 0, j = x1; i < newkey.length; i++, j++) {
			newkey[i] = key[j];
		}
		CSVRecordDataHolder cdh = new CSVRecordDataHolder(newkey);
		for (int i = y1; i <= y2; i++) {
			if (log.isDebugEnabled()) {
				log.debug("i:" + i + " row_count:" + getRowCount());
			}
			cdh.addRow(cdh.createNewIndexOf(getRow(i), x1, x2));
		}
		return cdh;
	}

	public CSVRecordDataHolder createIncetanceByClipIndices(
			int[] selectionIndices) {
		String[] newkey = new String[key.length - 1];

		for (int i = 0, j = 1; i < newkey.length; i++, j++) {
			newkey[i] = key[j];
		}
		CSVRecordDataHolder cdh = new CSVRecordDataHolder(newkey);
		for (int i = 0; i < selectionIndices.length; i++) {
			cdh.addRowRelpaceHead(getStringRecordRow(selectionIndices[i]));
		}
		return cdh;
	}

	public boolean pasteOverrideAt(int from, CSVRecordDataHolder pasetDataHolder) {
		if (from < 0 || from >= getRowCount()) {
			return false;
		}
		for (int i = 0; i < pasetDataHolder.getRowCount(); i++) {
			if (list.size() <= (from + i)) {
				if (log.isDebugEnabled()) {
					log.debug("extended row..." + list.size());
				}
				addRow((StringRecordData[]) null);
			}

			try {
				createBackUp(from + i);
			} catch (CloneNotSupportedException e) {
				log.fatal(StringUtil.EMPTY_STRING, e);
				return false;
			}
			String[] key2 = pasetDataHolder.getKey();
			for (int j = 1; j < key2.length; j++) {
				int col = getIndexByName(key2[j]);
				changeString(from + i, col,
						pasetDataHolder.getStringData(i, key2[j]));
			}
			if (reseted(from + i)) {
				changeStatus(from + i, HeaderType.NO_EDITION);
			} else {
				changeStatus(from + i, HeaderType.UPDATE);
			}
		}
		return true;
	}

	/**
	 * @param index
	 * @param i
	 * @param text
	 */
	public boolean reseted(int index) {
		StringRecordData[] data1 = fBackUpMap.get(new Integer(index));
		StringRecordData[] data2 = (StringRecordData[]) list.get(index);
		if (data1 == null || data2 == null) {
			return false;
		}
		for (int i = 1; i < data1.length; i++) {
			String d1 = data1[i].getString().replaceAll("([\r\n]+)", "\n");
			String d2 = data2[i].getString().replaceAll("([\r\n]+)", "\n");
			if (!d1.equals(d2)) {
				return false;
			}
		}
		fBackUpMap.remove(new Integer(index));
		return true;
	}

	public boolean pasteOverrideAt(int from, int columnIndex, String[][] datum) {
		if (from < 0 || from >= getRowCount() || datum.length == 0) {
			return false;
		}
		boolean FirstIsHeader = true;
		for (int j = 0; j < datum[0].length; j++) {
			if (getIndexByName(datum[0][j]) < 0) {
				FirstIsHeader = false;
				break;
			}
		}
		int i = 0;
		if (FirstIsHeader) {
			i++;
		}
		for (int myrow = 0; i < datum.length; myrow++, i++) {
			if (list.size() <= (from + myrow)) {
				if (log.isDebugEnabled()) {
					log.debug("extended row..." + list.size());
				}
				addRow((StringRecordData[]) null);
			}
			try {
				createBackUp(from + i);
			} catch (CloneNotSupportedException e) {
				log.fatal(StringUtil.EMPTY_STRING, e);
				return false;
			}
			if (FirstIsHeader) {
				for (int j = 0; j < datum[0].length; j++) {
					int col = getIndexByName(datum[0][j]);
					if (log.isDebugEnabled()) {
						log.debug(from + "/" + myrow + "/" + columnIndex + "/"
								+ j + " datum=" + datum[i][j]);
					}
					if (col < 0) {
						continue;
					}
					changeString(from + myrow, col, datum[i][j]);
				}
				if (reseted(from + myrow)) {
					changeStatus(from + myrow, HeaderType.NO_EDITION);
				} else {
					changeStatus(from + myrow, HeaderType.UPDATE);
				}
			} else {
				for (int j = 0; j < datum[myrow].length; j++) {
					if (log.isDebugEnabled()) {
						log.debug(from + "/" + myrow + "/" + columnIndex + "/"
								+ j + " datum=" + datum[i][j]);
					}
					changeString(from + myrow, columnIndex + j, datum[i][j]);
				}
				if (reseted(from + myrow)) {
					changeStatus(from + myrow, HeaderType.NO_EDITION);
				} else {
					changeStatus(from + myrow, HeaderType.UPDATE);
				}
			}
		}
		return true;
	}

	public CSVRecordDataHolder distinct(String... keys) {
		if (keys == null) {
			return this;
		}
		Set<List<String>> duplicateCheck = new HashSet<List<String>>();
		for (int i = 0; i < getRowCount(); i++) {
			List<String> matcheKeys = getDistinctCheckKeys(i, this, keys);
			if (duplicateCheck.contains(matcheKeys)) {
				deleteRow(i);
				i--;
			} else {
				duplicateCheck.add(matcheKeys);
			}
		}
		return this;
	}

	private List<String> getDistinctCheckKeys(int iRow,
			StringRecordDataHolder cdh, String... keys) {
		List<String> retList = new ArrayList<String>();
		for (int i = 0; i < keys.length; i++) {
			retList.add(cdh.getStringData(iRow, keys[i]));
		}
		return retList;
	}

	public boolean isNotNullIdx(int columnIdx) {
		if (notNullIdxSet == null) {
			return false;
		}
		return notNullIdxSet.contains(columnIdx);
	}

	public void clearNotNullIdx() {
		if (notNullIdxSet != null) {
			notNullIdxSet.clear();
		}
	}

	public void addNotNullIdx(String columnName) {
		int idx = getIndexByName(columnName);
		if (idx >= 0) {
			if (notNullIdxSet == null) {
				notNullIdxSet = new TreeSet<Integer>();
			}
			notNullIdxSet.add(idx);
		}
	}

	public class HeaderData extends StringRecordData {
		HeaderType type = HeaderType.NO_EDITION;

		public HeaderData(String string) {
			super(string);
		}

		public HeaderData(String string, HeaderType type) {
			super(string);
			this.type = type;
		}

		/**
		 * @return
		 */
		public boolean evenNumber() {
			if (fString != null && Integer.parseInt(fString) % 2 == 0) {
				return true;
			}
			return false;
		}

		public String getString() {
			return fString + type.getMark();
		}

		/**
		 * @return
		 */
		public boolean update() {
			return HeaderType.UPDATE.equals(type);
		}

		/**
		 * @return
		 */
		public boolean insert() {
			return HeaderType.INSERT.equals(type);
		}

		/**
		 * @return
		 */
		public boolean delete() {
			return HeaderType.DELETE.equals(type);
		}
	}

	public void verify() {
		if (rowVerifyList == null) {
			return;
		}
		for (int iRow = 0; iRow < getRowCount(); iRow++) {
			for (RowVerifyModel verifyModel : rowVerifyList) {
				verifyModel.verify(this, iRow);
				String mes = verifyModel.getVerifiedMessage();
				if (!StringUtil.isEmpty(mes)) {
					System.err.println(mes);
				}
			}
		}
	}

	public void addRowVerifyModel(RowVerifyModel model) {
		if (this.rowVerifyList == null) {
			this.rowVerifyList = new ArrayList<RowVerifyModel>();
		}
		this.rowVerifyList.add(model);
	}

}