package com.j256.ormlite.table;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.j256.ormlite.field.DatabaseFieldConfig;
import com.j256.ormlite.field.DatabaseFieldConfigLoader;

/**
 * Database table configuration loader which reads and writes {@link DatabaseTableConfig} from a text file/stream.
 * 
 * @author graywatson
 */
public class DatabaseTableConfigLoader {

	private static final String CONFIG_FILE_START_MARKER = "# --table-start--";
	private static final String CONFIG_FILE_END_MARKER = "# --table-end--";
	private static final String CONFIG_FILE_FIELDS_START = "# --table-fields-start--";
	private static final String CONFIG_FILE_FIELDS_END = "# --table-fields-end--";

	/**
	 * Load in a number of database configuration entries from a buffered reader.
	 */
	public static List<DatabaseTableConfig<?>> loadDatabaseConfigFromReader(BufferedReader reader) throws SQLException {
		List<DatabaseTableConfig<?>> list = new ArrayList<DatabaseTableConfig<?>>();
		while (true) {
			DatabaseTableConfig<?> config = DatabaseTableConfigLoader.fromReader(reader);
			if (config == null) {
				break;
			}
			list.add(config);
		}
		return list;
	}

	/**
	 * Load a table configuration in from a text-file reader.
	 * 
	 * @return A config if any of the fields were set otherwise null if we reach EOF.
	 */
	public static <T> DatabaseTableConfig<T> fromReader(BufferedReader reader) throws SQLException {
		DatabaseTableConfig<T> config = new DatabaseTableConfig<T>();
		boolean anything = false;
		while (true) {
			String line;
			try {
				line = reader.readLine();
			} catch (IOException e) {
				throw new SQLException("Could not read DatabaseTableConfig from stream", e);
			}
			if (line == null) {
				break;
			}
			// we do this so we can support multiple class configs per file
			if (line.equals(CONFIG_FILE_END_MARKER)) {
				break;
			}
			// we do this so we can support multiple class configs per file
			if (line.equals(CONFIG_FILE_FIELDS_START)) {
				readFields(reader, config);
				continue;
			}
			// skip empty lines or comments
			if (line.length() == 0 || line.startsWith("#") || line.equals(CONFIG_FILE_START_MARKER)) {
				continue;
			}
			String[] parts = line.split("=", -2);
			if (parts.length != 2) {
				throw new SQLException("DatabaseTableConfig reading from stream cannot parse line: " + line);
			}
			readTableField(config, parts[0], parts[1]);
			anything = true;
		}
		// if we got any config lines then we return the config
		if (anything) {
			return config;
		} else {
			// otherwise we return null for none
			return null;
		}
	}

	/**
	 * Write the table configuration to a buffered writer.
	 */
	public static <T> void write(BufferedWriter writer, DatabaseTableConfig<T> config) throws SQLException {
		try {
			writeConfig(writer, config);
		} catch (IOException e) {
			throw new SQLException("Could not write config to writer", e);
		}
	}

	// field names in the config file
	private static final String FIELD_NAME_DATA_CLASS = "dataClass";
	private static final String FIELD_NAME_TABLE_NAME = "tableName";
	private static final String FIELD_NAME_SCHEMA_NAME = "schemaName";

	/**
	 * Write the config to the writer.
	 */
	private static <T> void writeConfig(BufferedWriter writer, DatabaseTableConfig<T> config)
			throws IOException, SQLException {
		writer.append(CONFIG_FILE_START_MARKER);
		writer.newLine();
		if (config.getDataClass() != null) {
			writer.append(FIELD_NAME_DATA_CLASS).append('=').append(config.getDataClass().getName());
			writer.newLine();
		}
		if (config.getSchemaName() != null) {
			writer.append(FIELD_NAME_SCHEMA_NAME).append('=').append(config.getSchemaName());
			writer.newLine();
		}
		if (config.getTableName() != null) {
			writer.append(FIELD_NAME_TABLE_NAME).append('=').append(config.getTableName());
			writer.newLine();
		}
		writer.append(CONFIG_FILE_FIELDS_START);
		writer.newLine();
		if (config.getFieldConfigs() != null) {
			for (DatabaseFieldConfig field : config.getFieldConfigs()) {
				DatabaseFieldConfigLoader.write(writer, field, config.getTableName());
			}
		}
		writer.append(CONFIG_FILE_FIELDS_END);
		writer.newLine();
		writer.append(CONFIG_FILE_END_MARKER);
		writer.newLine();
	}

	/**
	 * Read a field into our table configuration for field=value line.
	 */
	private static <T> void readTableField(DatabaseTableConfig<T> config, String field, String value) {
		if (field.equals(FIELD_NAME_DATA_CLASS)) {
			try {
				@SuppressWarnings("unchecked")
				Class<T> clazz = (Class<T>) Class.forName(value);
				config.setDataClass(clazz);
			} catch (ClassNotFoundException e) {
				throw new IllegalArgumentException("Unknown class specified for dataClass: " + value);
			}
		} else if (field.equals(FIELD_NAME_TABLE_NAME)) {
			config.setTableName(value);
		} else if (field.equals(FIELD_NAME_SCHEMA_NAME)) {
			config.setSchemaName(value);
		}
	}

	/**
	 * Read all of the fields information from the configuration file.
	 */
	private static <T> void readFields(BufferedReader reader, DatabaseTableConfig<T> config) throws SQLException {
		List<DatabaseFieldConfig> fields = new ArrayList<DatabaseFieldConfig>();
		while (true) {
			String line;
			try {
				line = reader.readLine();
			} catch (IOException e) {
				throw new SQLException("Could not read next field from config file", e);
			}
			if (line == null || line.equals(CONFIG_FILE_FIELDS_END)) {
				break;
			}
			DatabaseFieldConfig fieldConfig = DatabaseFieldConfigLoader.fromReader(reader);
			if (fieldConfig == null) {
				break;
			}
			fields.add(fieldConfig);
		}
		config.setFieldConfigs(fields);
	}
}
