// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.db.oom.sqlgen.chunks;

import jodd.db.oom.DbEntityDescriptor;
import jodd.db.oom.DbEntityColumnDescriptor;
import jodd.db.oom.sqlgen.DbSqlBuilderException;
import jodd.db.oom.sqlgen.TemplateData;
import jodd.util.CharUtil;

/**
 * SQL chunk defines part of the SQL query that can be processed.
 */
public abstract class SqlChunk {

	public static final int COLS_NA = 0;                // using explicit reference.
	public static final int COLS_ONLY_EXISTING = 1;     // using only existing columns i.e. that are not-null
	public static final int COLS_ONLY_IDS = 2;          // using only identity columns
	public static final int COLS_ALL = 3;               // using all available columns
	public static final int COLS_ALL_BUT_ID = 4;        // using all available columns except the identity column
	public static final int COLS_NA_MULTI = 0;          // using explicit reference.

	protected final int chunkType;                      // chunk type
	public static final int CHUNK_RAW = -1;
	public static final int CHUNK_SELECT_COLUMNS = 1;
	public static final int CHUNK_TABLE = 2;
	public static final int CHUNK_REFERENCE = 3;
	public static final int CHUNK_MATCH = 4;
	public static final int CHUNK_VALUE = 5;
	public static final int CHUNK_INSERT = 5;
	public static final int CHUNK_UPDATE = 6;


	protected SqlChunk(int chunkType) {
		this.chunkType = chunkType;
	}

	// ---------------------------------------------------------------- linked list

	protected SqlChunk previousChunk;

	/**
	 * Returns previous chunk.
	 */
	public SqlChunk getPreviousChunk() {
		return previousChunk;
	}

	protected SqlChunk nextChunk;

	/**
	 * Returns next chunk.
	 */
	public SqlChunk getNextChunk() {
		return nextChunk;
	}

	/**
	 * Appends chunk to previous one and maintains the double-linked list of the previous chunk.
	 * Current surrounding connections of this chunk will be cut-off.
	 */
	public void insertChunkAfter(SqlChunk previous) {
		SqlChunk next = previous.nextChunk;
		previous.nextChunk = this;
		this.previousChunk = previous;
		if (next != null) {
			next.previousChunk = this;
			this.nextChunk = next;
		}
	}

	/**
	 * Returns <code>true</code> if previous chunk is of provided type.
	 */
	public boolean isPreviousChunkOfType(int type) {
		if (previousChunk == null) {
			return false;
		}
		return previousChunk.chunkType == type;
	}

	/**
	 * Returns <code>true</code> if previous chunk is of the same type.
	 */
	public boolean isPreviousChunkOfSameType() {
		if (previousChunk == null) {
			return false;
		}
		return previousChunk.chunkType == chunkType;
	}

	/**
	 * Returns <code>true</code> if previous chunk is not raw.
	 */
	public boolean isPreviousMacroChunk() {
		if (previousChunk == null) {
			return false;
		}
		return previousChunk.chunkType != CHUNK_RAW;
	}

	public boolean isPreviousRawChunk() {
		if (previousChunk == null) {
			return false;
		}
		return previousChunk.chunkType == CHUNK_RAW;
	}


	// ---------------------------------------------------------------- process

	protected TemplateData templateData;      // working template context

	/**
	 * Initializes chunk. Assigns {@link jodd.db.oom.sqlgen.TemplateData} to chunk.
	 * If chunk needs some pre-processing, they should be done here.
	 */
	public void init(TemplateData templateData) {
		this.templateData = templateData;
	}

	/**
	 * Process the chunk and appends data to the output.
	 */
	public abstract void process(StringBuilder out);


	// ---------------------------------------------------------------- lookup

	/**
	 * Lookups for entity name and throws exception if entity name not found.
	 */
	protected DbEntityDescriptor lookupName(String entityName) {
		DbEntityDescriptor ded = templateData.getDbOomManager().lookupName(entityName);
		if (ded == null) {
			throw new DbSqlBuilderException("Entity name not registered: " + entityName);
		}
		return ded;
	}

	/**
	 * Lookups for entity name and throws an exception if entity type is invalid.
	 */
	protected DbEntityDescriptor lookupType(Class entity) {
		DbEntityDescriptor ded = templateData.getDbOomManager().lookupType(entity);
		if (ded == null) {
			throw new DbSqlBuilderException("Invalid or not-persistent entity: " + entity.getName());
		}
		return ded;
	}

	/**
	 * Lookups for table reference and throws an exception if table reference not found.
	 */
	protected DbEntityDescriptor lookupTableRef(String tableRef) {
		return lookupTableRef(tableRef, true);
	}

	/**
	 * Lookups for table reference and optionally throws an exception if table reference not found.
	 */
	protected DbEntityDescriptor lookupTableRef(String tableRef, boolean throwExceptionIfNotFound) {
		DbEntityDescriptor ded = templateData.getTableDescriptor(tableRef);
		if (ded == null) {
			if (throwExceptionIfNotFound) {
				throw new DbSqlBuilderException("Invalid table reference: " + tableRef);
			}
		}
		return ded;
	}

	/**
	 * Finds for table that contains column,
	 */
	protected DbEntityDescriptor findColumnRef(String columnRef) {
		DbEntityDescriptor ded = templateData.findTableDescriptorByColumnRef(columnRef);
		if (ded == null) {
			throw new DbSqlBuilderException("Invalid column reference: " + columnRef);
		}
		return ded;
	}

	// ---------------------------------------------------------------- misc

	/**
	 * Resolves table name or alias that will be used in the query.
	 */
	protected String resolveTable(String tableRef, DbEntityDescriptor ded) {
		String tableAlias = templateData.getTableAlias(tableRef);
		if (tableAlias != null) {
			return tableAlias;
		}
		return ded.getTableName();
	}

	/**
	 * Defines parameter with name and its value.
	 */
	protected void defineParameter(StringBuilder query, String name, Object value, DbEntityColumnDescriptor dec) {
		if (name == null) {
			name = templateData.getNextParameterName();
		}
		query.append(':').append(name);
		templateData.addParameter(name, value, dec);
	}

	/**
	 * Resolves object to a class.
	 */
	protected static Class resolveClass(Object object) {
		Class type = object.getClass();
		return type == Class.class ? (Class) object : type;
	}

	// ---------------------------------------------------------------- separation

	/**
	 * Appends missing space if the output doesn't end with whitespace.
	 */
	protected void appendMissingSpace(StringBuilder out) {
		int len = out.length();
		if (len == 0) {
			return;
		}
		len--;
		if (!CharUtil.isWhitespace(out.charAt(len))) {
			out.append(' ');
		}
	}

	/**
	 * Separates from previous chunk by comma if is of the same type.
	 */
	protected void separateByCommaOrSpace(StringBuilder out) {
		if (isPreviousChunkOfSameType()) {
			out.append(',').append(' ');
		} else {
			appendMissingSpace(out);
		}
	}

}