/*
 * Copyright 2007-2017 the original author or authors.
 *
 * Licensed 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.
 */
package net.ymate.platform.persistence.jdbc.base.impl;

import net.ymate.platform.core.util.ExpressionUtils;
import net.ymate.platform.persistence.base.Type;
import net.ymate.platform.persistence.jdbc.IConnectionHolder;
import net.ymate.platform.persistence.jdbc.base.*;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * 数据库存储过程操作器接口实现
 *
 * @param <T> 元素类型
 * @author 刘镇 (suninformation@163.com) on 16/12/8 上午1:04
 * @version 1.0
 */
public class DefaultProcedureOperator<T> extends AbstractOperator implements IProcedureOperator<T> {

    private static final Log _LOG = LogFactory.getLog(DefaultProcedureOperator.class);

    /**
     * 存储过程OUT参数类型集合
     */
    private final List<Integer> __outParams = new ArrayList<Integer>();

    private IOutResultProcessor __resultProcessor;

    private IResultSetHandler<T> __resultSetHandler;

    private final List<List<T>> __resultSets = new ArrayList<List<T>>();

    public DefaultProcedureOperator(String sql, IConnectionHolder connectionHolder) {
        super(sql, connectionHolder);
    }

    public DefaultProcedureOperator(String sql, IConnectionHolder connectionHolder, IAccessorConfig accessorConfig) {
        super(sql, connectionHolder, accessorConfig);
    }

    @Override
    public void execute() throws Exception {
        if (!this.executed) {
            StopWatch _time = new StopWatch();
            _time.start();
            try {
                __doExecute();
                // 执行过程未发生异常将标记已执行，避免重复执行
                this.executed = true;
            } finally {
                _time.stop();
                this.expenseTime = _time.getTime();
                //
                if (this.getConnectionHolder().getDataSourceCfgMeta().isShowSQL()) {
                    _LOG.info(ExpressionUtils.bind("[${sql}]${param}[${count}][${time}]")
                            .set("sql", StringUtils.defaultIfBlank(this.sql, "@NULL"))
                            .set("param", __doSerializeParameters())
                            .set("count", "N/A")
                            .set("time", this.expenseTime + "ms").getResult());
                }
            }
        }
    }

    @Override
    public IProcedureOperator<T> execute(IResultSetHandler<T> resultSetHandler) throws Exception {
        __resultSetHandler = resultSetHandler;
        this.execute();
        return this;
    }

    @Override
    public IProcedureOperator<T> execute(IOutResultProcessor resultProcessor) throws Exception {
        __resultProcessor = resultProcessor;
        this.execute();
        return this;
    }

    @Override
    protected int __doExecute() throws Exception {
        CallableStatement _statement = null;
        AccessorEventContext _context = null;
        boolean _hasEx = false;
        try {
            IAccessor _accessor = new BaseAccessor(this.getAccessorConfig());
            _statement = _accessor.getCallableStatement(this.getConnectionHolder().getConnection(), __doBuildCallSQL());
            __doSetParameters(_statement);
            __doRegisterOutParams(_statement);
            if (this.getAccessorConfig() != null) {
                this.getAccessorConfig().beforeStatementExecution(_context = new AccessorEventContext(_statement, Type.OPT.PROCEDURE));
            }
            boolean _flag = _statement.execute();
            if (_flag) {
                do {
                    ResultSet _result = _statement.getResultSet();
                    if (_result != null) {
                        __resultSets.add(__resultSetHandler.handle(_result));
                        _result.close();
                    }
                } while (_statement.getMoreResults());
            } else {
                int _idx = this.getParameters().size() + 1;
                for (Integer _paramType : __outParams) {
                    __resultProcessor.process(_idx, _paramType, _statement.getObject((_idx)));
                    _idx++;
                }
            }
            return -1;
        } catch (Exception ex) {
            _hasEx = true;
            throw ex;
        } finally {
            if (!_hasEx && this.getAccessorConfig() != null && _context != null) {
                this.getAccessorConfig().afterStatementExecution(_context);
            }
            if (_statement != null) {
                _statement.close();
            }
        }
    }

    /**
     * @return 构建存储过程CALL语句(根据不同的数据库, 可由子类重新实现)
     */
    protected String __doBuildCallSQL() {
        List<String> _params = new ArrayList<String>();
        for (int i = 0; i < this.getParameters().size() + this.__outParams.size(); i++) {
            _params.add("?");
        }
        this.sql = "{CALL " + this.getSQL() + (_params.isEmpty() ? "()" : "(" + StringUtils.join(_params, ',') + ")") + "}";
        return this.sql;
    }

    /**
     * 注册存储过程输出的参数(从最后一个输入参数后开始, 根据不同的数据库，可由子类重新实现)
     *
     * @param statement CallableStatement
     * @throws SQLException 可能产生的任何异常
     */
    protected void __doRegisterOutParams(CallableStatement statement) throws SQLException {
        int _idx = this.getParameters().size() + 1;
        for (Integer _type : __outParams) {
            statement.registerOutParameter(_idx++, _type);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public IProcedureOperator<T> addParameter(SQLParameter parameter) {
        return (IProcedureOperator<T>) super.addParameter(parameter);
    }

    @Override
    @SuppressWarnings("unchecked")
    public IProcedureOperator<T> addParameter(Object parameter) {
        return (IProcedureOperator<T>) super.addParameter(parameter);
    }

    @Override
    public IProcedureOperator<T> addOutParameter(Integer sqlParamType) {
        this.__outParams.add(sqlParamType);
        return this;
    }

    @Override
    public IProcedureOperator<T> setOutResultProcessor(IOutResultProcessor outResultProcessor) {
        __resultProcessor = outResultProcessor;
        return this;
    }

    @Override
    public IProcedureOperator<T> setResultSetHandler(IResultSetHandler<T> resultSetHandler) {
        __resultSetHandler = resultSetHandler;
        return this;
    }

    @Override
    public List<List<T>> getResultSets() {
        return __resultSets;
    }
}
