/*
 * 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.
 */
package org.apache.camel.component.mybatis;

import org.apache.camel.Exchange;
import org.apache.camel.component.bean.BeanProcessor;
import org.apache.camel.support.DefaultProducer;
import org.apache.camel.support.ExchangeHelper;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;

public class MyBatisBeanProducer extends DefaultProducer {

    private final MyBatisBeanEndpoint endpoint;

    public MyBatisBeanProducer(MyBatisBeanEndpoint endpoint) {
        super(endpoint);
        this.endpoint = endpoint;
    }

    public void process(Exchange exchange) throws Exception {
        SqlSession session;

        ExecutorType executorType = endpoint.getExecutorType();
        if (executorType == null) {
            session = endpoint.getSqlSessionFactory().openSession();
        } else {
            session = endpoint.getSqlSessionFactory().openSession(executorType);
        }
        log.debug("Opened MyBatis SqlSession: {}", session);

        try {
            doProcess(exchange, session);
            // flush the batch statements and commit the database connection
            session.commit();
        } catch (Exception e) {
            // discard the pending batch statements and roll the database connection back
            session.rollback();
            throw e;
        } finally {
            // and finally close the session as we're done
            log.debug("Closing MyBatis SqlSession: {}", session);
            session.close();
        }
    }

    protected void doProcess(Exchange exchange, SqlSession session) throws Exception {
        log.trace("Invoking MyBatisBean on {}:{}", endpoint.getBeanName(), endpoint.getMethodName());

        // if we use input or output header we need to copy exchange to avoid mutating the
        Exchange copy = ExchangeHelper.createCopy(exchange, true);

        Object input = getInput(copy);
        copy.getMessage().setBody(input);

        BeanProcessor beanProcessor = createBeanProcessor(session);
        beanProcessor.start();
        beanProcessor.process(copy);
        beanProcessor.stop();

        Object result = copy.getMessage().getBody();
        if (result != input) {
            if (endpoint.getOutputHeader() != null) {
                // set the result as header for insert
                log.trace("Setting result as header [{}]: {}", endpoint.getOutputHeader(), result);
                exchange.getMessage().setHeader(endpoint.getOutputHeader(), result);
            } else {
                // set the result as body for insert
                log.trace("Setting result as body: {}", result);
                exchange.getMessage().setBody(result);
                exchange.getMessage().setHeader(MyBatisConstants.MYBATIS_RESULT, result);
            }
        }
    }

    private BeanProcessor createBeanProcessor(SqlSession session) throws Exception {
        // discover the bean and get the mapper
        // is the bean a alias type
        Class<?> clazz = session.getConfiguration().getTypeAliasRegistry().resolveAlias(endpoint.getBeanName());
        if (clazz == null) {
            // its maybe a FQN so try to use Camel to lookup the class
            clazz = getEndpoint().getCamelContext().getClassResolver().resolveMandatoryClass(endpoint.getBeanName());
        }

        log.debug("Resolved MyBatis Bean: {} as class: {}", endpoint.getBeanName(), clazz);

        // find the mapper
        Object mapper = session.getMapper(clazz);
        if (mapper == null) {
            throw new IllegalArgumentException("No Mapper with typeAlias or class name: " + endpoint.getBeanName() + " in MyBatis configuration.");
        }
        log.debug("Resolved MyBatis Bean mapper: {}", mapper);

        BeanProcessor answer = new BeanProcessor(mapper, getEndpoint().getCamelContext());
        answer.setMethod(endpoint.getMethodName());
        return answer;
    }

    private Object getInput(final Exchange exchange) {
        final String inputHeader = endpoint.getInputHeader();
        if (inputHeader != null) {
            return exchange.getIn().getHeader(inputHeader);
        } else {
            return exchange.getIn().getBody();
        }
    }

}
