package com.freetmp.mbg.plugin.geom;

import com.freetmp.mbg.plugin.upsert.AbstractUpsertPlugin;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.OutputUtilities;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.xml.*;
import org.mybatis.generator.codegen.mybatis3.MyBatis3FormattingUtilities;
import org.mybatis.generator.internal.db.ConnectionFactory;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/*
 * Postgis的地理信息插件
 * @author Pin Liu
 */
public class PostgisGeoPlugin extends PluginAdapter {
    
    public static final String SRID_NAME = "srid";
	
    public static final String lineSeparator;

    static {
        String ls = System.getProperty("line.separator"); //$NON-NLS-1$
        if (ls == null) {
            ls = "\n"; //$NON-NLS-1$
        }
        lineSeparator = ls;
    }

	Connection connection;
	
	Field elementsList;
	
	String srid;
	
	@Override
	public boolean validate(List<String> warnings) {
		boolean valid = true;
		srid = properties.getProperty(SRID_NAME);
		if(StringUtils.isEmpty(srid)){
			srid = "3857";
		}
		if(connection == null){
	        try {
				connection = ConnectionFactory.getInstance().getConnection(context.getJdbcConnectionConfiguration());
			} catch (SQLException e) {
				e.printStackTrace();
				valid = false;
			}	
		}
		elementsList = FieldUtils.getField(XmlElement.class, "elements", true);
		return valid;
	}
	
	/*
	 * 在初始化阶段,检查所有字段的JdbcType为OTHER的字段,获取其类型名称(TYPE_NAME)
	 * 根据其类型名称确定其实际的地理信息类型
	 */
	@Override
	public void initialized(IntrospectedTable introspectedTable) {
		for(IntrospectedColumn introspectedColumn : introspectedTable.getAllColumns()){
			if(introspectedColumn.getJdbcType() == Types.OTHER){
				String typeName = fetchTypeName(introspectedColumn);
				//System.out.println("postgis type : "+typeName);
				switch(typeName.toLowerCase()){
				case "geometry":{
					introspectedColumn.setFullyQualifiedJavaType(new FullyQualifiedJavaType("org.geolatte.geom.Geometry"));
					//introspectedColumn.setJdbcTypeName(typeName.toUpperCase());
					break;
				}
				}
			}
		}
	}
	
	
	@Override
	public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
		
		List<IntrospectedColumn> columns = new ArrayList<IntrospectedColumn>();
		
		for(IntrospectedColumn introspectedColumn : introspectedTable.getAllColumns()){
			if(introspectedColumn.getFullyQualifiedJavaType().getPackageName().equalsIgnoreCase("org.geolatte.geom")){
				columns.add(introspectedColumn);
			}
		}
		
		if(columns.isEmpty()) return true;
		
		XmlElement xmlElement = document.getRootElement();
		for(Element element : xmlElement.getElements()){
			if(element instanceof XmlElement){
				XmlElement xe = (XmlElement) element;
				switch (xe.getName().toLowerCase()) {
				case "sql":
					if(containsAttribute(xe, "id", introspectedTable.getBaseColumnListId())){
						checkAndReplaceOutput(columns,xe);						
					}else if(containsAttribute(xe, "id", AbstractUpsertPlugin.IDENTIFIERS_ARRAY_CONDITIONS)){
						checkAndReplaceInput(columns, xe);
					}
					break;
				case "insert":
				case "update":
					checkAndReplaceInput(columns, xe);
					break;
				default:
					break;
				}			
			}
		}
		return true;
	}
	
	/*
	 * 检查并替换输入的地理信息相关参数,使用PostGis提供的ST_GeomFromText函数
	 * @author Pin Liu
	 */
	protected void checkAndReplaceInput(List<IntrospectedColumn> columns, XmlElement xe){
	  for(Element element : xe.getElements()){
	    if(element instanceof XmlElement){
	      checkAndReplaceInput(columns, (XmlElement) element);
	    }
	    if(element instanceof TextElement){
	      TextElement te = (TextElement) element;
    		checkAndReplaceInput(columns, te);
	    }
	  }
	}

  protected void checkAndReplaceInput(List<IntrospectedColumn> columns, TextElement te) {
    String sql = te.getContent();
    for(IntrospectedColumn column : columns){
    	if(column.getFullyQualifiedJavaType().getShortName().equals("Geometry")){
    		String paramStr = MyBatis3FormattingUtilities.getParameterClause(column);
    		sql = StringUtils.replace(sql, paramStr, "ST_GeomFromText(" + paramStr + ","+srid+")"); //replace no prefix geo relate column
    		paramStr = MyBatis3FormattingUtilities.getParameterClause(column, "record.");
    		sql = StringUtils.replace(sql, paramStr, "ST_GeomFromText(" + paramStr + ","+srid+")"); //replace mbg generate prefix geo relate column
    		paramStr = MyBatis3FormattingUtilities.getParameterClause(column, "item.");
    		sql = StringUtils.replace(sql, paramStr, "ST_GeomFromText(" + paramStr + ","+srid+")"); //replace mbg batch plugin generate prefix geo relate column				
  //				System.out.println();
  //				System.out.println(sql);
    	}
    }
    try {
      FieldUtils.writeDeclaredField(te, "content", sql, true);
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    }
  }
	
	/*
	 * 检查并替换输出的地理信息相关参数,使用PostGis提供的ST_AsText函数
	 * @author Pin Liu
	 */
	protected void checkAndReplaceOutput(List<IntrospectedColumn> columns, XmlElement xe) {
	  for(Element element : xe.getElements()){
	    if(element instanceof XmlElement){
	      checkAndReplaceOutput(columns, (XmlElement) element);
	    }
	    if(element instanceof TextElement){
    	  TextElement te = (TextElement) element;
    		checkAndReplaceOutput(columns, te);
	    }
	  }
	}

  protected void checkAndReplaceOutput(List<IntrospectedColumn> columns, TextElement te) {
    String sql = te.getContent();
		for(IntrospectedColumn column : columns){
			if(column.getFullyQualifiedJavaType().getShortName().equals("Geometry")){
				String columnStr = null;
				if(column.isColumnNameDelimited()){
					columnStr = "\""+column.getActualColumnName()+"\"";
				}else{
					columnStr = column.getActualColumnName();
				}
				sql = StringUtils.replaceOnce(sql, columnStr, "ST_AsText("+columnStr+") as " + columnStr);
				//sql = sql.replace(column.getActualColumnName(), "ST_AsText("+column.getActualColumnName()+")");
//				System.out.println();
//				System.out.println(sql);
			}
		}
    try {
      FieldUtils.writeDeclaredField(te, "content", sql, true);
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    }		
  }

	/*
	 * 使用新的sql语句替换原来的xml内容
	 * @author Pin Liu
	 */
	protected void replaceOriginChildElements(XmlElement xe, String sql) {
		sql = sql.trim();
		String[] lines = sql.split(lineSeparator);
		List<Element> elements = Lists.newArrayList();
		for(String line : lines){
			elements.add(new TextElement(line));
		}
		try {
			elementsList.set(xe, elements);
		} catch (IllegalArgumentException | IllegalAccessException e) {
			e.printStackTrace();
		}
	}
	
	/*
	 * 获取xml标签内部的内容
	 * @author Pin Liu
	 */
	protected String getContentWithoutOuterTags(XmlElement xe){
		int indentLevel = 0;
		StringBuilder sb = new StringBuilder();
		Iterator<Element> iter = xe.getElements().iterator();
		while(iter.hasNext()){
			sb.append(iter.next().getFormattedContent(indentLevel));
			if(iter.hasNext()){
	            OutputUtilities.newLine(sb);				
			}
		}
        return sb.toString();
	}

	/*
	 * 检查xml标签元素是否包含指定属性
	 * @author Pin Liu
	 */
	public boolean containsAttribute(XmlElement xe,String key, String value){
		if(xe.getAttributes() != null){
			for(Attribute attribute : xe.getAttributes()){
				if(attribute.getName().equalsIgnoreCase(key) && attribute.getValue().equalsIgnoreCase(value)){
					return true;
				}
			}
		}
		return false;
	}

	/*
	 * 根据MBG探测到的数据库表的元数据获取字段的类别名称
	 * @author Pin Liu
	 */
	public String fetchTypeName(IntrospectedColumn introspectedColumn){
		
		String columnName = introspectedColumn.getActualColumnName();
		String catalog = introspectedColumn.getIntrospectedTable().getTableConfiguration().getCatalog();
		String schema = introspectedColumn.getIntrospectedTable().getTableConfiguration().getSchema();
		String table = introspectedColumn.getIntrospectedTable().getTableConfiguration().getTableName();
		String dataType = null;
		try {
			ResultSet set = connection.getMetaData().getColumns(catalog, schema, table, columnName);
			while(set.next()){
				dataType = set.getString("TYPE_NAME");
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		return dataType;
	}

}