package org.apache.torque.templates.transformer;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.util.List;

import org.apache.commons.lang.ObjectUtils;
import org.apache.torque.generator.control.ControllerState;
import org.apache.torque.generator.source.SourceElement;
import org.apache.torque.generator.source.transform.SourceTransformerException;
import org.apache.torque.templates.TemplateOptionName;
import org.apache.torque.templates.TorqueSchemaAttributeName;
import org.apache.torque.templates.TorqueSchemaElementName;
import org.apache.torque.templates.platform.Platform;
import org.apache.torque.templates.platform.PlatformFactory;
import org.apache.torque.templates.typemapping.SchemaType;
import org.apache.torque.templates.typemapping.SqlType;

/**
 * Helper class for retrieving the schema type of a column.
 *
 * $Id: SchemaTypeHelper.java 1436342 2013-01-21 13:40:05Z tfischer $
 */
public final class SchemaTypeHelper
{
    /**
     * Private constructor for utility class.
     */
    private SchemaTypeHelper()
    {
    }

    /**
     * Determines the schema type of a column.
     *
     * @param columnElement the source element which defines the column
     *        for which the schema type should be determined; not null.
     * @param controllerState the controller state, not null.
     *
     * @return the schema type of the column, not null.
     *
     * @throws SourceTransformerException if the name attribute is not set
     *         in the column or if the type refers to an unknown type.
     */
    public static SchemaType getSchemaType(
            SourceElement columnElement,
            ControllerState controllerState)
        throws SourceTransformerException
    {
        String columnNameFromSchema
                = (String) columnElement.getAttribute(
                        TorqueSchemaAttributeName.NAME);
        if (columnNameFromSchema == null)
        {
            throw new SourceTransformerException("The attribute "
                    + TorqueSchemaAttributeName.NAME.getName()
                    + " on element "
                    + columnElement.getName()
                    + " is null");
        }

        SchemaType schemaType = null;
        SqlType domain = getDomain(columnElement, controllerState);
        if (domain != null && domain.getSqlTypeName() != null)
        {
            try
            {
                schemaType = SchemaType.valueOf(domain.getSqlTypeName());
            }
            catch (IllegalArgumentException e)
            {
                throw new SourceTransformerException("Unknown type "
                        + domain.getSqlTypeName()
                        + " in Domain definition");
            }
        }
        else
        {
            String schemaTypeString = (String) columnElement.getAttribute(
                    TorqueSchemaAttributeName.TYPE.getName());
            if (schemaTypeString == null)
            {
                throw new SourceTransformerException("type attribute not set"
                        + " in Column "
                        + columnNameFromSchema);
            }
            try
            {
                schemaType = SchemaType.valueOf(schemaTypeString);
            }
            catch (IllegalArgumentException e)
            {
                throw new SourceTransformerException("Unknown type "
                        + schemaTypeString
                        + " in Column "
                        + columnNameFromSchema);
            }
        }
        return schemaType;
    }

    /**
     * Returns the SQL type for a schema type and the specified target database.
     *
     * @param schemaType the schema type for which the SQL type should be
     *        determined, not null.
     * @param domainType the domain type which overrides the schema type,
     *        or null if no domain is defined.
     * @param controllerState the controller state, not null.
     * @param size overrides the size from schemaType and/or domainType,
     *        or null to use the default from domainType or schemaType.
     * @param scale overrides the scale from schemaType and/or domainType,
     *        or null to use the default from domainType or schemaType.
     * @param defaultValue overrides the defaultValue from schemaType
     *        and/or domainType, or null to use the default from domainType
     *        or schemaType.
     *
     * @return the the SQL type for the schema type, or null if no SQL type
     *         exists for the schema type.
     */
    public static SqlType getSqlType(
            SchemaType schemaType,
            SqlType domainType,
            ControllerState controllerState,
            String size,
            String scale,
            String defaultValue)
    {
        Platform platform = PlatformFactory.getPlatformFor(
                controllerState.getStringOption(TemplateOptionName.DATABASE));
        SqlType platformSqlType = platform.getSqlTypeForSchemaType(schemaType);
        if (domainType != null)
        {
            if (size == null)
            {
                size = domainType.getSize();
            }
            if (scale == null)
            {
                scale = domainType.getScale();
            }
            if (defaultValue == null)
            {
                defaultValue = domainType.getDefaultValue();
            }
        }
        SqlType result = platformSqlType.getNew(
                size,
                scale,
                defaultValue);
        return result;
    }

    public static SqlType getDomain(
                SourceElement columnElement,
                ControllerState controllerState)
            throws SourceTransformerException
    {
        String domainNameFromSchema
                = (String) columnElement.getAttribute(
                        TorqueSchemaAttributeName.DOMAIN);
        if (domainNameFromSchema == null)
        {
            // no domain specified
            return null;
        }
        SourceElement domainElement = null;
        {
            SourceElement databaseElement
                    = columnElement.getParent().getParent();
            List<SourceElement> domainElementList
                    = databaseElement.getChildren(
                            TorqueSchemaElementName.DOMAIN);
            for (SourceElement candidate : domainElementList)
            {
                if (domainNameFromSchema.equals(candidate.getAttribute(
                        TorqueSchemaAttributeName.NAME)))
                {
                    domainElement = candidate;
                    break;
                }
            }
        }
        if (domainElement == null)
        {
            throw new SourceTransformerException("The domain named "
                    + domainNameFromSchema
                    + " referenced by the column "
                    + columnElement.getParent().getAttribute(
                            TorqueSchemaAttributeName.NAME)
                    + " in the table "
                    + columnElement.getAttribute(TorqueSchemaAttributeName.NAME)
                    + " was not found in this schema");
        }
        String sqlType = ObjectUtils.toString(
                domainElement.getAttribute(TorqueSchemaAttributeName.TYPE),
                null);
        String defaultValue = ObjectUtils.toString(
                domainElement.getAttribute(TorqueSchemaAttributeName.DEFAULT),
                null);
        String size = ObjectUtils.toString(
                domainElement.getAttribute(TorqueSchemaAttributeName.SIZE),
                null);
        String scale = ObjectUtils.toString(
                domainElement.getAttribute(TorqueSchemaAttributeName.SCALE),
                null);
        return new SqlType(sqlType, size, scale, defaultValue);
    }
}
