package hiro.yoshioka.sdh;

import hiro.yoshioka.util.SQLDataType;
import hiro.yoshioka.util.StringUtil;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public class ResultSetDataHolder extends CSVRecordDataHolder {
	private static final long serialVersionUID = -4781540870053874050L;
	public static final DecimalFormat f = new DecimalFormat("#,##0.0#####");
	private long _wrap_time;
	transient public java.util.Date createTime = new java.util.Date();
	protected ResultSetMetaCopy meta;
	private boolean forUpdate;
	protected int[] pkPositions = new int[0];
	private DatabaseType databaseType = DatabaseType.UNKNOWN;

	public boolean isForUpdate() {
		return forUpdate;
	}

	public void setForUpdate(boolean forUpdate) {
		this.forUpdate = forUpdate;
	}

	public ResultSetDataHolder() {
	}

	/**
	 * @param argNames
	 * @throws NullPointerException
	 */
	public ResultSetDataHolder(String[] argNames, ResultSetMetaData meta,
			DatabaseType databaseType) throws NullPointerException {
		super(argNames);
		if (meta != null) {
			this.meta = new ResultSetMetaCopy(meta);
		}
		this.databaseType = databaseType;
	}

	public ResultSetDataHolder(String[] argNames) throws NullPointerException {
		this(argNames, null, null);
	}

	public DatabaseType getDatabaseType() {
		return databaseType;
	}

	@Override
	public ResultSetDataHolder 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];
		}
		ResultSetDataHolder cdh = new ResultSetDataHolder(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;
	}

	@Override
	public ResultSetDataHolder 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];
		}
		ResultSetDataHolder cdh = new ResultSetDataHolder(newkey);
		for (int i = 0; i < selectionIndices.length; i++) {
			cdh.addRowRelpaceHead(getStringRecordRow(selectionIndices[i]));
		}
		return cdh;
	}

	public boolean hasResultSetMetaData() {
		if (log.isTraceEnabled()) {
			log.trace("meta=" + meta);
		}
		return meta != null;
	}

	public boolean isPrimaryKey(int index) {
		for (int i = 0; i < pkPositions.length; i++) {
			if (pkPositions[i] == index) {
				return true;
			}
		}
		return false;
	}

	public int[] getPkPositions() {
		return pkPositions;
	}

	public List<RDHMetaColumn> getColumnList(boolean withRow) {
		List<RDHMetaColumn> retList = new ArrayList<RDHMetaColumn>();
		if (withRow) {
			retList.add(new RDHMetaColumn(key[0], 0));
		}
		for (int i = 1; i < key.length; i++) {
			RDHMetaColumn col = null;
			col = new RDHMetaColumn(key[i], i);
			if (hasResultSetMetaData()) {
				col.setType(meta.getColumnTypeOf(i));
				col.setNotNull(isNotNullIdx(i));
				col.setPk(isPrimaryKey(i));
			}
			retList.add(col);
		}
		return retList;
	}

	public List<RDHMetaColumn> getColumnList(boolean withRow,
			Collection<SQLDataType> includeSQLDataTypes) {
		List<RDHMetaColumn> retList = new ArrayList<RDHMetaColumn>();
		if (withRow) {
			retList.add(new RDHMetaColumn(key[0], 0));
		}
		for (int i = 1; i < key.length; i++) {
			RDHMetaColumn col = null;
			col = new RDHMetaColumn(key[i], i);
			if (hasResultSetMetaData()) {
				col.setType(meta.getColumnTypeOf(i));
				if (includeSQLDataTypes.contains(col.getType())) {
					col.setNotNull(isNotNullIdx(i));
					col.setPk(isPrimaryKey(i));
					retList.add(col);
				}
			}
		}
		return retList;
	}

	public void setPkPositions(int[] pkPositions) {
		if (pkPositions == null) {
			log.error("can't set null.");
			return;
		}
		this.pkPositions = pkPositions;
	}

	public String[] generateInsertScripts() {
		ArrayList<String> retList = new ArrayList<String>();
		for (int i = 0; i < getRowCount(); i++) {
			StringBuilder insert = new StringBuilder("");
			StringBuilder temp = new StringBuilder("");
			try {
				for (int j = 1; j <= meta.columnCount; j++) {
					if (j > 1) {
						temp.append(",");
					}
					temp.append(meta.getColumnName(j));
				}
				insert.append(String.format("INSERT INTO %s (%s) %s VALUES (",
						meta.getTableName(1), temp.toString(),
						StringUtil.LINE_SEPARATOR));

				temp = new StringBuilder();
				for (int j = 1; j <= meta.columnCount; j++) {
					if (j > 1) {
						temp.append(",");
					}
					String value = getStringData(i, meta.getColumnName(j));
					if (value.length() > 0) {
						if (isString(j) || isTimesType(j)) {
							temp.append("'" + StringUtil.cnv2SQLEscape(value)
									+ "'");
						} else {
							temp.append(value);
						}
					} else {
						temp.append("NULL");
					}
				}
				insert.append(String.format("%s); %s", temp.toString(),
						StringUtil.LINE_SEPARATOR));
				retList.add(insert.toString());
			} catch (SQLException e) {
				if (log.isInfoEnabled()) {
					log.info(StringUtil.EMPTY_STRING, e);
				}
			}
		}
		return retList.toArray(new String[retList.size()]);
	}

	class SeqNumberHolder {
		HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();

		int getMin(int keta) {
			if (keta == 0) {
				return 0;
			}
			if (keta >= 4) {
				keta = 4;
			}
			int ret = 0;
			Integer integer = map.get(new Integer(keta));
			System.out.println("keta" + keta + "/in=" + integer);
			if (integer == null) {
				String str = "0";
				if (keta >= 2) {
					str = "" + keta;
					for (int i = 0; i < keta - 1; i++) {
						str += "0";
					}
					System.out.println("str=" + str);
				}
				integer = new Integer(str);
				map.put(new Integer(keta), integer);
			}
			if (Math.pow(10, keta) > integer.intValue() + 1) {
				ret = integer.intValue();
				map.put(new Integer(keta), new Integer(integer.intValue() + 1));
			} else {
				map.remove(new Integer(keta));
				ret = getMin(keta);
			}
			System.out.println("ret=" + ret);
			return ret;
		}
	}

	public void clearMeta() {
		meta = null;
	}

	public void addDummyRow2(int num) {
		SeqNumberHolder seq = new SeqNumberHolder();
		HeaderData head = new HeaderData(Integer.toString(getRowCount() + 1));
		StringRecordData[] in = new StringRecordData[key.length];
		head.type = HeaderType.INSERT;
		in[0] = head;

		for (int i = 1; i < in.length; i++) {
			try {
				if (isTimeStamp(i)) {
					in[i] = new StringRecordData(StringUtil.EMPTY_STRING);
				} else {
					in[i] = new StringRecordData(String.valueOf(seq.getMin(meta
							.getPrecision(i))));
				}
			} catch (SQLException e) {
				in[i] = new StringRecordData(StringUtil.EMPTY_STRING);
			}
		}
		list.add(in);
	}

	public long getWrapTime() {
		return _wrap_time;
	}

	public String getFormattedWrapTime() {
		return f.format(_wrap_time / 1000.0);
	}

	public void setWrapTime(long time) {
		_wrap_time = time;
	}

	public boolean isBlob(int index) {
		if (meta == null) {
			return false;
		}
		return meta.isBlob(index);
	}

	public SQLDataType getSQLDataTypeOf(int index) {
		if (meta == null || index <= 0) {
			return null;
		}
		return meta.getColumnTypeOf(index);
	}

	public boolean needsQuote(int index) {
		if (meta == null) {
			return false;
		}
		return meta.needsQuote(index);
	}

	public boolean changeSQLDataType(int columnIndex, SQLDataType newType) {
		if (columnIndex <= 0 || !this.hasResultSetMetaData()) {
			return false;
		}
		meta.columnType[columnIndex] = newType;

		return true;
	}

	public Set<StringRecordData> getPartOfStringRecordRow(int row,
			Collection<SQLDataType> includeSQLDataTypes) {
		List<RDHMetaColumn> retList = getColumnList(false, includeSQLDataTypes);
		if (retList.size() == 0) {
			return Collections.EMPTY_SET;
		}

		Set<StringRecordData> retSet = new LinkedHashSet<StringRecordData>();
		StringRecordData[] rowData = this.list.get(row);
		for (RDHMetaColumn column : retList) {
			retSet.add(rowData[column.getIndex()]);
		}
		return retSet;
	}

	public boolean isColumnTypeOf(int index, Collection<SQLDataType> collections) {
		if (meta == null) {
			return false;
		}
		return meta.isColumnTypeOf(index, collections);
	}

	public boolean isBinaly(int index) {
		if (meta == null) {
			return false;
		}
		return meta.isBinaly(index);
	}

	public int getSize(int index) {
		if (meta == null) {
			return 0;
		}
		try {
			return meta.getScale(index);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return 0;
	}

	public String getMaxString(int index) {
		StringBuilder buf = new StringBuilder();
		try {

			switch (meta.getColumnType(index)) {
			case Types.TIMESTAMP:
				// fRs.getMetaData().get
				break;
			case Types.NUMERIC:
			case Types.DECIMAL:
				System.out.println("lDECIMAL:"
						+ getMaxNumber(meta.getPrecision(index),
								meta.getScale(index)));
				buf.append(getMaxNumber(meta.getPrecision(index),
						meta.getScale(index)));
				break;
			default:
				buf.append(meta.getPrecision(index));
				if (buf.length() == meta.getPrecision(index) - 1) {
					buf.insert(0, "X");
				} else if (buf.length() <= meta.getPrecision(index) - 2) {
					buf.insert(0, "X");
					buf.append("X");
					for (int i = 0; buf.length() < meta.getPrecision(index); i++) {
						if (i % 2 == 0) {
							buf.insert(1, "-");
						} else {
							buf.insert(buf.length() - 1, "-");
						}
					}
				}

			}
		} catch (Exception e) {
		}
		return buf.toString();
	}

	public void addDummyRow(int num) {
		HeaderData head = new HeaderData(Integer.toString(getRowCount() + 1));
		StringRecordData[] in = new StringRecordData[key.length];
		head.type = HeaderType.INSERT;
		in[0] = head;
		for (int i = 1; i < in.length; i++) {
			in[i] = new StringRecordData(getMaxString(i));
		}
		list.add(in);
	}

	public static String getMaxNumber(int precision, int scale) {
		StringBuilder buf = new StringBuilder();
		if (scale > 0) {
			precision -= scale;
		}
		for (int i = 0; i < precision; i++) {
			buf.append((i + 1) % 10);
		}
		if (scale > 0) {
			buf.append(".");
			for (int i = 0; i < scale; i++) {
				buf.append((i + 1) % 10);
			}
		}
		return buf.toString();
	}

	private boolean isTimeStamp(int index) throws SQLException {
		if (meta == null) {
			return false;
		}
		return meta.getColumnType(index) == Types.TIMESTAMP;
	}

	public boolean isTimesType(int index) throws SQLException {
		if (meta == null) {
			return false;
		}
		return meta.isTypeTimes(index);
	}

	public boolean isString(int index) {
		if (meta == null) {
			return false;
		}
		return meta.isTypeString(index);
	}

	public String getYukoString(int index, String value) throws SQLException {
		if (meta == null) {
			return value;
		}
		switch (meta.getColumnTypeOf(index)) {
		case TIMESTAMP:
		case TIME:
		case DATE:
			break;
		case DECIMAL:
		case NUMERIC:
		case FLOAT:
		case DOUBLE:
			int pre = meta.getPrecision(index);
			int scale = 0;
			try {
				scale = meta.getScale(index);
			} catch (Exception e) {
			}
			if (scale > 0) {
				pre++;
			}
			if (value.startsWith("-")) {
				pre++;
			}
			if (value.length() > pre && pre > 0) {
				return value.substring(0, pre);
			}
			break;
		default:
			int pre2 = meta.getPrecision(index);
			if (value.length() > pre2 && pre2 > 0) {
				return value.substring(0, pre2);
			}
		}
		return value;
	}

	/**
	 * @param selectionIndex
	 */
	public void setNameValue(int selectionIndex) {
		StringRecordData[] data = (StringRecordData[]) list.get(selectionIndex);
		for (int i = 1; i < data.length; i++) {
			data[i] = new StringRecordData(getMaxString(i));
		}
		try {
			createBackUp(selectionIndex);
		} catch (CloneNotSupportedException e) {
			log.fatal(StringUtil.EMPTY_STRING, e);
		}
	}

	private void readObject(ObjectInputStream in) throws IOException,
			ClassNotFoundException {
		in.defaultReadObject(); // Figureは引数なしコンストラクタで初期化
		fBackUpMap = new HashMap<Integer, StringRecordData[]>();
	}

}