/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Inc.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.hibernate.mapping;

import java.io.Serializable;

import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.SQLFunctionRegistry;
import org.hibernate.engine.Mapping;
import org.hibernate.sql.Template;
import org.hibernate.util.StringHelper;

/**
 * A column of a relational database table
 * @author Gavin King
 */
public class Column implements Selectable, Serializable, Cloneable {

	public static final int DEFAULT_LENGTH = 255;
	public static final int DEFAULT_PRECISION = 19;
	public static final int DEFAULT_SCALE = 2;

	private int length=DEFAULT_LENGTH;
	private int precision=DEFAULT_PRECISION;
	private int scale=DEFAULT_SCALE;
	private Value value;
	private int typeIndex = 0;
	private String name;
	private boolean nullable=true;
	private boolean unique=false;
	private String sqlType;
	private Integer sqlTypeCode;
	private boolean quoted=false;
	int uniqueInteger;
	private String checkConstraint;
	private String comment;
	private String defaultValue;
	private String customWrite;
	private String customRead;

	public Column() {
	}

	public Column(String columnName) {
		setName(columnName);
	}

	public int getLength() {
		return length;
	}
	public void setLength(int length) {
		this.length = length;
	}
	public Value getValue() {
		return value;
	}
	public void setValue(Value value) {
		this.value= value;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		if (
			name.charAt(0)=='`' ||
			Dialect.QUOTE.indexOf( name.charAt(0) ) > -1 //TODO: deprecated, remove eventually
		) {
			quoted=true;
			this.name=name.substring( 1, name.length()-1 );
		}
		else {
			this.name = name;
		}
	}

	/** returns quoted name as it would be in the mapping file. */
	public String getQuotedName() {
		return quoted ?
				"`" + name + "`" :
				name;
	}

	public String getQuotedName(Dialect d) {
		return quoted ?
			d.openQuote() + name + d.closeQuote() :
			name;
	}
	
	/**
	 * For any column name, generate an alias that is unique
	 * to that column name, and also 10 characters or less
	 * in length.
	 */
	public String getAlias(Dialect dialect) {
		String alias = name;
		String unique = Integer.toString(uniqueInteger) + '_';
		int lastLetter = StringHelper.lastIndexOfLetter(name);
		if ( lastLetter == -1 ) {
			alias = "column";
		}
		else if ( lastLetter < name.length()-1 ) {
			alias = name.substring(0, lastLetter+1);
		}
		if ( alias.length() > dialect.getMaxAliasLength() ) {
			alias = alias.substring( 0, dialect.getMaxAliasLength() - unique.length() );
		}
		boolean useRawName = name.equals(alias) && 
			!quoted && 
			!name.toLowerCase().equals("rowid");
		if ( useRawName ) {
			return alias;
		}
		else {
			return alias + unique;
		}
	}
	
	/**
	 * Generate a column alias that is unique across multiple tables
	 */
	public String getAlias(Dialect dialect, Table table) {
		return getAlias(dialect) + table.getUniqueInteger() + '_';
	}

	public boolean isNullable() {
		return nullable;
	}

	public void setNullable(boolean nullable) {
		this.nullable=nullable;
	}

	public int getTypeIndex() {
		return typeIndex;
	}
	public void setTypeIndex(int typeIndex) {
		this.typeIndex = typeIndex;
	}

	public int getSqlTypeCode(Mapping mapping) throws MappingException {
		org.hibernate.type.Type type = getValue().getType();
		try {
			int sqlTypeCode = type.sqlTypes(mapping)[ getTypeIndex() ];
			if(getSqlTypeCode()!=null && getSqlTypeCode().intValue()!=sqlTypeCode) {
				throw new MappingException("SQLType code's does not match. mapped as " + sqlTypeCode + " but is " + getSqlTypeCode() );
			}
			return sqlTypeCode;
		}
		catch (Exception e) {
			throw new MappingException(
					"Could not determine type for column " +
					name +
					" of type " +
					type.getClass().getName() +
					": " +
					e.getClass().getName(),
					e
				);
		}
	}

	/**
	 * Returns the underlying columns sqltypecode.
	 * If null, it is because the sqltype code is unknown.
	 * 
	 * Use #getSqlTypeCode(Mapping) to retreive the sqltypecode used
	 * for the columns associated Value/Type.
	 * 
	 * @return sqltypecode if it is set, otherwise null.
	 */
	public Integer getSqlTypeCode() {
		return sqlTypeCode;
	}
	
	public void setSqlTypeCode(Integer typecode) {
		sqlTypeCode=typecode;
	}
	
	public boolean isUnique() {
		return unique;
	}


	public String getSqlType(Dialect dialect, Mapping mapping) throws HibernateException {
		return sqlType==null ?
			dialect.getTypeName( getSqlTypeCode(mapping), getLength(), getPrecision(), getScale() ) :
			sqlType;
	}

	public boolean equals(Object object) {
		return object instanceof Column && equals( (Column) object );
	}

	public boolean equals(Column column) {
		if (null == column) return false;
		if (this == column) return true;

		return isQuoted() ? 
			name.equals(column.name) :
			name.equalsIgnoreCase(column.name);
	}

	//used also for generation of FK names!
	public int hashCode() {
		return isQuoted() ? 
			name.hashCode() : 
			name.toLowerCase().hashCode();
	}

	public String getSqlType() {
		return sqlType;
	}

	public void setSqlType(String sqlType) {
		this.sqlType = sqlType;
	}

	public void setUnique(boolean unique) {
		this.unique = unique;
	}

	public boolean isQuoted() {
		return quoted;
	}

	public String toString() {
		return getClass().getName() + '(' + getName() + ')';
	}

	public String getCheckConstraint() {
		return checkConstraint;
	}

	public void setCheckConstraint(String checkConstraint) {
		this.checkConstraint = checkConstraint;
	}

	public boolean hasCheckConstraint() {
		return checkConstraint!=null;
	}

	public String getTemplate(Dialect dialect, SQLFunctionRegistry functionRegistry) {
		return hasCustomRead()
				? Template.renderWhereStringTemplate( customRead, dialect, functionRegistry )
				: Template.TEMPLATE + '.' + getQuotedName( dialect );
	}

	public boolean hasCustomRead() {
		return ( customRead != null && customRead.length() > 0 );
	}

	public String getReadExpr(Dialect dialect) {
		return hasCustomRead() ? customRead : getQuotedName( dialect );
	}
	
	public String getWriteExpr() {
		return ( customWrite != null && customWrite.length() > 0 ) ? customWrite : "?";
	}
	
	public boolean isFormula() {
		return false;
	}

	public String getText(Dialect d) {
		return getQuotedName(d);
	}
	public String getText() {
		return getName();
	}
	
	public int getPrecision() {
		return precision;
	}
	public void setPrecision(int scale) {
		this.precision = scale;
	}

	public int getScale() {
		return scale;
	}
	public void setScale(int scale) {
		this.scale = scale;
	}

	public String getComment() {
		return comment;
	}

	public void setComment(String comment) {
		this.comment = comment;
	}

	public String getDefaultValue() {
		return defaultValue;
	}

	public void setDefaultValue(String defaultValue) {
		this.defaultValue = defaultValue;
	}

	public String getCustomWrite() {
		return customWrite;
	}

	public void setCustomWrite(String customWrite) {
		this.customWrite = customWrite;
	}

	public String getCustomRead() {
		return customRead;
	}

	public void setCustomRead(String customRead) {
		this.customRead = customRead;
	}

	public String getCanonicalName() {
		return quoted ? name : name.toLowerCase();
	}

	/**
	 * Shallow copy, the value is not copied
	 */
	protected Object clone() {
		Column copy = new Column();
		copy.setLength( length );
		copy.setScale( scale );
		copy.setValue( value );
		copy.setTypeIndex( typeIndex );
		copy.setName( getQuotedName() );
		copy.setNullable( nullable );
		copy.setPrecision( precision );
		copy.setUnique( unique );
		copy.setSqlType( sqlType );
		copy.setSqlTypeCode( sqlTypeCode );
		copy.uniqueInteger = uniqueInteger; //usually useless
		copy.setCheckConstraint( checkConstraint );
		copy.setComment( comment );
		copy.setDefaultValue( defaultValue );
		copy.setCustomRead( customRead );
		copy.setCustomWrite( customWrite );
		return copy;
	}

}
