package com.ibatis.sqlmap.engine.config;

import com.ibatis.common.beans.*;
import com.ibatis.common.resources.*;
import com.ibatis.sqlmap.client.*;
import com.ibatis.sqlmap.engine.cache.*;
import com.ibatis.sqlmap.engine.impl.*;
import com.ibatis.sqlmap.engine.mapping.parameter.*;
import com.ibatis.sqlmap.engine.mapping.result.*;
import com.ibatis.sqlmap.engine.mapping.sql.*;
import com.ibatis.sqlmap.engine.mapping.sql.dynamic.*;
import com.ibatis.sqlmap.engine.mapping.sql.simple.*;
import com.ibatis.sqlmap.engine.mapping.sql.stat.*;
import com.ibatis.sqlmap.engine.mapping.statement.*;
import com.ibatis.sqlmap.engine.scope.*;
import com.ibatis.sqlmap.engine.type.*;

import java.sql.ResultSet;
import java.util.*;

public class MappedStatementConfig {
  private static final Probe PROBE = ProbeFactory.getProbe();
  private static final InlineParameterMapParser PARAM_PARSER = new InlineParameterMapParser();
  private ErrorContext errorContext;
  private SqlMapClientImpl client;
  private TypeHandlerFactory typeHandlerFactory;
  private MappedStatement mappedStatement;
  private MappedStatement rootStatement;

  MappedStatementConfig(SqlMapConfiguration config, String id, MappedStatement statement, SqlSource processor,
                        String parameterMapName, Class parameterClass, String resultMapName,
                        String[] additionalResultMapNames, Class resultClass, Class[] additionalResultClasses,
                        String cacheModelName, String resultSetType, Integer fetchSize, boolean allowRemapping,
                        Integer timeout, Integer defaultStatementTimeout, String xmlResultName) {
    this.errorContext = config.getErrorContext();
    this.client = config.getClient();
    SqlMapExecutorDelegate delegate = client.getDelegate();
    this.typeHandlerFactory = config.getTypeHandlerFactory();
    errorContext.setActivity("parsing a mapped statement");
    errorContext.setObjectId(id + " statement");
    errorContext.setMoreInfo("Check the result map name.");
    if (resultMapName != null) {
      statement.setResultMap(client.getDelegate().getResultMap(resultMapName));
      if (additionalResultMapNames != null) {
        for (int i = 0; i < additionalResultMapNames.length; i++) {
          statement.addResultMap(client.getDelegate().getResultMap(additionalResultMapNames[i]));
        }
      }
    }
    errorContext.setMoreInfo("Check the parameter map name.");
    if (parameterMapName != null) {
      statement.setParameterMap(client.getDelegate().getParameterMap(parameterMapName));
    }
    statement.setId(id);
    statement.setResource(errorContext.getResource());
    if (resultSetType != null) {
      if ("FORWARD_ONLY".equals(resultSetType)) {
        statement.setResultSetType(new Integer(ResultSet.TYPE_FORWARD_ONLY));
      } else if ("SCROLL_INSENSITIVE".equals(resultSetType)) {
        statement.setResultSetType(new Integer(ResultSet.TYPE_SCROLL_INSENSITIVE));
      } else if ("SCROLL_SENSITIVE".equals(resultSetType)) {
        statement.setResultSetType(new Integer(ResultSet.TYPE_SCROLL_SENSITIVE));
      }
    }
    if (fetchSize != null) {
      statement.setFetchSize(fetchSize);
    }

    // set parameter class either from attribute or from map (make sure to match)
    ParameterMap parameterMap = statement.getParameterMap();
    if (parameterMap == null) {
      statement.setParameterClass(parameterClass);
    } else {
      statement.setParameterClass(parameterMap.getParameterClass());
    }

    // process SQL statement, including inline parameter maps
    errorContext.setMoreInfo("Check the SQL statement.");
    Sql sql = processor.getSql();
    setSqlForStatement(statement, sql);

    // set up either null result map or automatic result mapping
    ResultMap resultMap = (ResultMap) statement.getResultMap();
    if (resultMap == null && resultClass == null) {
      statement.setResultMap(null);
    } else if (resultMap == null) {
      resultMap = buildAutoResultMap(allowRemapping, statement, resultClass, xmlResultName);
      statement.setResultMap(resultMap);
      if (additionalResultClasses != null) {
        for (int i = 0; i < additionalResultClasses.length; i++) {
          statement.addResultMap(buildAutoResultMap(allowRemapping, statement, additionalResultClasses[i], xmlResultName));
        }
      }

    }
    statement.setTimeout(defaultStatementTimeout);
    if (timeout != null) {
      try {
        statement.setTimeout(timeout);
      } catch (NumberFormatException e) {
        throw new SqlMapException("Specified timeout value for statement " + statement.getId() + " is not a valid integer");
      }
    }
    errorContext.setMoreInfo(null);
    errorContext.setObjectId(null);
    statement.setSqlMapClient(client);
    if (cacheModelName != null && cacheModelName.length() > 0 && client.getDelegate().isCacheModelsEnabled()) {
      CacheModel cacheModel = client.getDelegate().getCacheModel(cacheModelName);
      mappedStatement = new CachingStatement(statement, cacheModel);
    } else {
      mappedStatement = statement;
    }
    rootStatement = statement;
    delegate.addMappedStatement(mappedStatement);
  }

  public void setSelectKeyStatement(SqlSource processor, String resultClassName, String keyPropName, boolean runAfterSQL, String type) {
    if (rootStatement instanceof InsertStatement) {
      InsertStatement insertStatement = ((InsertStatement) rootStatement);
      Class parameterClass = insertStatement.getParameterClass();
      errorContext.setActivity("parsing a select key");
      SelectKeyStatement selectKeyStatement = new SelectKeyStatement();
      resultClassName = typeHandlerFactory.resolveAlias(resultClassName);
      Class resultClass = null;

      // get parameter and result maps
      selectKeyStatement.setSqlMapClient(client);
      selectKeyStatement.setId(insertStatement.getId() + "-SelectKey");
      selectKeyStatement.setResource(errorContext.getResource());
      selectKeyStatement.setKeyProperty(keyPropName);
      selectKeyStatement.setRunAfterSQL(runAfterSQL);
      // process the type (pre or post) attribute
      if (type != null) {
        selectKeyStatement.setRunAfterSQL("post".equals(type));
      }
      try {
        if (resultClassName != null) {
          errorContext.setMoreInfo("Check the select key result class.");
          resultClass = Resources.classForName(resultClassName);
        } else {
          if (keyPropName != null && parameterClass != null) {
            resultClass = PROBE.getPropertyTypeForSetter(parameterClass, selectKeyStatement.getKeyProperty());
          }
        }
      } catch (ClassNotFoundException e) {
        throw new SqlMapException("Error.  Could not set result class.  Cause: " + e, e);
      }
      if (resultClass == null) {
        resultClass = Object.class;
      }

      // process SQL statement, including inline parameter maps
      errorContext.setMoreInfo("Check the select key SQL statement.");
      Sql sql = processor.getSql();
      setSqlForStatement(selectKeyStatement, sql);
      ResultMap resultMap;
      resultMap = new AutoResultMap(client.getDelegate(), false);
      resultMap.setId(selectKeyStatement.getId() + "-AutoResultMap");
      resultMap.setResultClass(resultClass);
      resultMap.setResource(selectKeyStatement.getResource());
      selectKeyStatement.setResultMap(resultMap);
      errorContext.setMoreInfo(null);
      insertStatement.setSelectKeyStatement(selectKeyStatement);
    } else {
      throw new SqlMapException("You cant set a select key statement on statement named " + rootStatement.getId() + " because it is not an InsertStatement.");
    }
  }

  private void setSqlForStatement(MappedStatement statement, Sql sql) {
    if (sql instanceof DynamicSql) {
      statement.setSql(sql);
    } else {
      applyInlineParameterMap(statement, sql.getSql(null, null));
    }
  }

  private void applyInlineParameterMap(MappedStatement statement, String sqlStatement) {
    String newSql = sqlStatement;
    errorContext.setActivity("building an inline parameter map");
    ParameterMap parameterMap = statement.getParameterMap();
    errorContext.setMoreInfo("Check the inline parameters.");
    if (parameterMap == null) {
      ParameterMap map;
      map = new ParameterMap(client.getDelegate());
      map.setId(statement.getId() + "-InlineParameterMap");
      map.setParameterClass(statement.getParameterClass());
      map.setResource(statement.getResource());
      statement.setParameterMap(map);
      SqlText sqlText = PARAM_PARSER.parseInlineParameterMap(client.getDelegate().getTypeHandlerFactory(), newSql, statement.getParameterClass());
      newSql = sqlText.getText();
      List mappingList = Arrays.asList(sqlText.getParameterMappings());
      map.setParameterMappingList(mappingList);
    }
    Sql sql;
    if (SimpleDynamicSql.isSimpleDynamicSql(newSql)) {
      sql = new SimpleDynamicSql(client.getDelegate(), newSql);
    } else {
      sql = new StaticSql(newSql);
    }
    statement.setSql(sql);

  }

  private ResultMap buildAutoResultMap(boolean allowRemapping, MappedStatement statement, Class firstResultClass, String xmlResultName) {
    ResultMap resultMap;
    resultMap = new AutoResultMap(client.getDelegate(), allowRemapping);
    resultMap.setId(statement.getId() + "-AutoResultMap");
    resultMap.setResultClass(firstResultClass);
    resultMap.setXmlName(xmlResultName);
    resultMap.setResource(statement.getResource());
    return resultMap;
  }


  public MappedStatement getMappedStatement() {
    return mappedStatement;
  }
}
