package org.nico.ourbatis.loader;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.nico.ourbatis.Ourbatis;
import org.nico.ourbatis.el.Noel;
import org.nico.ourbatis.el.NoelResult;
import org.nico.ourbatis.entity.Table;
import org.nico.ourbatis.mapping.MapperMapping;
import org.nico.ourbatis.utils.ClassUtils;
import org.nico.ourbatis.utils.StreamUtils;

/**
 * We need a builder to help us implement the environment in which the processed mapper.xml 
 * is loaded into mybatis, 
 * @author nico
 * @time 2018-08-23 18:22
 */
public class OurbatisLoader {

	private static final Log log = LogFactory.getLog(OurbatisLoader.class);
	
	/**
	 * Template information queue
	 */
	private Map<Table, String> mappers;

	private MapperMapping mapper;

	private Noel noel;

	private SqlSessionFactory sqlSessionFactory;

	private Configuration configuration;

	private String baseTemplateUri;

	private String baseTemplateContent;

	private String mapperLocations;

	/**
	 * Get an instance of o through which mapper can be loaded into the mybatis container
	 * 
	 * @param sqlSessionFactory	
	 * 			Creates an SqlSession out of a connection or a DataSource
	 * @param baseTemplateUri
	 * 			The uri to the template, which is the relative path of the template under the classpath
	 * @param mapperPacketUri
	 * 			The path of mapper's package, example 'org.nico.ourbatis.mapper', Store the Mapper interface internally
	 */
	public OurbatisLoader(SqlSessionFactory sqlSessionFactory, String baseTemplateUri, String mapperLocations) {
		this.sqlSessionFactory = sqlSessionFactory;
		this.configuration = sqlSessionFactory.getConfiguration();
		this.mappers = new LinkedHashMap<Table, String>();
		this.mapper = new MapperMapping();
		this.noel = new Noel();
		this.baseTemplateUri = baseTemplateUri;
		this.mapperLocations = mapperLocations;
		this.baseTemplateContent = StreamUtils.convertToString(this.baseTemplateUri);
		log.debug("Start processing the data source session factory " + sqlSessionFactory);
	}

	/**
	 * After a class is parsed as a template, it is added to the load queue. 
	 * When the build method is called, the template content in the queue is 
	 * loaded in turn into the Mybatis container
	 * 
	 * @param clazz
	 * 			Target class
	 * @return This instance 		
	 */
	public OurbatisLoader add(Class<?> clazz) {
		Table entityInfo = mapper.mappingTable(clazz, mapperLocations);
		if(ClassUtils.forName(entityInfo.getMapperClassName()) != null) {
			NoelResult result = noel.el(baseTemplateContent, entityInfo);
			String putCallBack = mappers.putIfAbsent(entityInfo, result.getFormat());
			if(putCallBack == null) {
				log.debug(">>  Loading " + clazz.getName());
			}
		}
		return this;
	}

	/**
	 * Add all the class files under the package by scanning the package path
	 * 
	 * @param packet 
	 * 			Package path
	 * @return This instance
	 */
	public OurbatisLoader add(String packet) {
		try {
			List<Class<?>> classes = ClassUtils.getClasses(packet);
			if(classes != null && ! classes.isEmpty()) {
				classes.forEach(clazz -> {
					add(clazz);
				});
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return this;
	}

	/**
	 * Calling this method will load the template information from the queue into the Mybatis container in turn
	 */
	public void build() {
		if (!this.mappers.isEmpty()) {
			boolean openPrint = Ourbatis.print != null;
			this.mappers.forEach((entityInfo, mapper) -> {
				try {
					if (openPrint) {
						StreamUtils.write(Ourbatis.print, entityInfo.getDomainClassName(), ".xml", mapper);
						log.debug(">>  Writing  " + entityInfo.getDomainClassName());
					}
				} catch (Exception arg6) {
					arg6.printStackTrace();
				}
				log.debug(">>  Building " + entityInfo.getDomainClassName());
				ByteArrayInputStream mapperStream = new ByteArrayInputStream(mapper.getBytes());
				XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperStream, this.configuration,
						mapperStream.toString(), this.configuration.getSqlFragments());
				xmlMapperBuilder.parse();
			});
		}
	}

	public Map<Table, String> getMappers() {
		return mappers;
	}

	public void setMappers(Map<Table, String> mappers) {
		this.mappers = mappers;
	}

	public SqlSessionFactory getSqlSessionFactory() {
		return sqlSessionFactory;
	}

	public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
		this.sqlSessionFactory = sqlSessionFactory;
	}

}