package com.fp.hbm.bgenerator;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.SQLQuery;
import org.hibernate.exception.DataException;

import com.fp.common.exception.APPException;
import com.fp.hbm.helper.HqlStatementHQL;
import com.fp.hbm.helper.PersistenceHelperHQL;
import com.fp.hbm.persistence.TgeneEntity;
import com.fp.hbm.persistence.TgeneFields;
import com.fp.persistence.commondb.exception.CommondbException;

public abstract class AbstractMapper {

    /**Nombre de la clase*/
    protected String class_name;
    protected String entity;
    protected String table;
    protected String packageBase;
    protected HBMapper parent;
    protected List<AbstractColumn> columns;
    protected List<AbstractColumn> primary;
    protected List<AbstractColumn> notNullable;
    protected List<Reference> references;
    protected String createReferences;
    protected String packageName;
    protected boolean optimistic = false;
    protected String sExtends = null;
    protected String sImplements = null;
    protected boolean history = false;
    protected boolean inmanual = false;
    protected boolean cache = false;
    protected boolean log = false;
    protected TgeneEntity ent = null;

    public HBMapper getParent() {
        return parent;
    }

    public void setParent(HBMapper pParent) {
        parent = pParent;
    }

    protected void safe(String pData, String pPath, String pFile) throws IOException {
        File f = new File(pPath);
        f.mkdirs();
        FileOutputStream fout = new FileOutputStream(pPath + "/" + pFile);
        fout.write(pData.getBytes());
        fout.close();
        f = new File(pPath + "/" + pFile);
    }

    public abstract void safeEntity() throws IOException, APPException;

    public String getEntity() {
        return entity;
    }

    public void setEntity(String pEntity) throws HibernateException, CommondbException, ParseException, InstantiationException, IllegalAccessException,
    ClassNotFoundException {
        entity = pEntity;
        try {
            this.prepareEntity(pEntity);
        } catch (CommondbException e) {
            throw new CommondbException("HM00", "NO SE ENCONTRO LA TABLA {0}", this.table);
        }
        try {
            this.findColumns();
        } catch (DataException e) {
            System.out.println("Error en el mapero de la Tabla " + this.table);
            throw e;
        }
    }

    public String getPackageBase() {
        return packageBase;
    }

    public void setPackageBase(String pPackageBase) {
        packageBase = pPackageBase;
        this.packageName = packageBase;
    }

    public String getTable() {
        return table;
    }

    public void setTable(String pTable) {
        table = pTable;
    }

    @SuppressWarnings("unchecked")
    protected void findColumns() throws HibernateException, CommondbException, InstantiationException, IllegalAccessException, ClassNotFoundException,
    ParseException {
        columns = new ArrayList<AbstractColumn>();
        primary = new ArrayList<AbstractColumn>();
        notNullable = new ArrayList<AbstractColumn>();
        String sql = "select distinct t.COLUMN_NAME as NAME," + "       t.DATA_TYPE as TYPE,"
                + "      coalesce(t.DATA_PRECISION ,t.DATA_LENGTH) as LENGTH,"
                + "       t.DATA_PRECISION as PRECISION,"
                + "       t.DATA_SCALE as SCALE,"
                + "       t.NULLABLE as NULLABLE,"
                + "       c.comments as COMMENTS,"
                + "       (select distinct c2.position"
                + "          from all_constraints c1, all_cons_columns c2"
                + "         where c1.table_name = t.TABLE_NAME and c1.constraint_type = 'P' and"
                + "               c2.constraint_name = c1.constraint_name and"
                + "               c1.table_name = c2.table_name and"
                + "               c2.column_name = c.column_name)as  PK,"
                /*+ "       (select '1'"
                + "          from all_constraints c1, all_cons_columns c2"
                + "         where c1.table_name = t.TABLE_NAME and c1.constraint_type = 'R' and"
                + "               c2.constraint_name = c1.constraint_name and" + "               c1.table_name = c2.table_name and"
                + "               c2.column_name = c.column_name ) as FK"*/
                + "  '1' as FK "
                + "  from all_tab_columns t, all_col_comments c"
                + " where t.TABLE_NAME = :table and t.TABLE_NAME = c.table_name and"
                + "       t.COLUMN_NAME = c.column_name";
              //  + " order by PK,t.COLUMN_ID";
        SQLQuery oSQL = PersistenceHelperHQL.getSession().createSQLQuery(sql);
        oSQL.addScalar("NAME");
        oSQL.addScalar("TYPE");
        oSQL.addScalar("LENGTH", Hibernate.INTEGER);
        oSQL.addScalar("PRECISION", Hibernate.INTEGER);
        oSQL.addScalar("SCALE", Hibernate.INTEGER);
        oSQL.addScalar("NULLABLE");
        oSQL.addScalar("COMMENTS");
        oSQL.addScalar("PK", Hibernate.INTEGER);
        oSQL.addScalar("FK", Hibernate.INTEGER);
        oSQL.setString("table", this.table);
        List columnData = oSQL.list();
        for (Object object : columnData) {
            AbstractColumn mc = record2AbstractColumn(object, this.parent.getType());
            if (!mc.isNullable()) {
                this.notNullable.add(mc);
            }
            if (mc.isPk()) {
                this.primary.add(mc);
            } else {
                columns.add(mc);
            }
        }
        if ((this.createReferences != null) && (this.createReferences.compareTo("0") != 0)) {
            String hql = "from com.fp.hbm.persistence.TgeneFields t where t.pk.tname=:table and t.pk.entity=:entity and t.fromdb='0' and t.fktname is not null";
            if ((this.createReferences.compareTo("S") == 0) || (this.createReferences.compareTo("M") == 0)) {
                hql += " and t.type='" + this.createReferences + "'";
            }
            HqlStatementHQL pst = new HqlStatementHQL(hql);
            pst.setString("table", this.getEnt().getPk().getTname());
            pst.setString("entity", this.getEnt().getPk().getEntity());
            List<TgeneFields> fields = new ArrayList<TgeneFields>();
            try {
                fields = pst.getList();
            } catch (CommondbException e) {
                fields = new ArrayList<TgeneFields>();
            }
            this.references = new ArrayList<Reference>();
            for (TgeneFields f : fields) {
                this.references.add(new Reference(f, this.packageBase));
            }
        }
    }

    private AbstractColumn record2AbstractColumn(Object pRst, MappingType pType) throws InstantiationException, IllegalAccessException,
    ClassNotFoundException, CommondbException {
        AbstractColumn m = pType.getColumn();
        m.setMapper(this);
        m.setTable(this.table);
        m.setPacBase(this.packageBase);
        Object[] rec = (Object[]) pRst;
        m.setName((String) rec[0]);
        m.setType((String) rec[1]);
        m.setLength((Integer) rec[2]);
        m.setPrecision((Integer) rec[3]);
        m.setScale((Integer) rec[4]);
        m.setNullable(((String) rec[5]).compareTo("Y") == 0);
        m.setComments((String) rec[6]);
        Integer aux = (Integer) rec[7];
        boolean bAux = false;
        if ((aux != null) && (aux != 0)) {
            bAux = true;
            m.setPkOrder(aux);
        }
        m.setPk(bAux);
        aux = (Integer) rec[8];
        bAux = false;
        if ((aux != null) && (aux != 0)) {
            bAux = true;
        }
        m.setFk(bAux);
        return m;
    }

    public String getCreateReferences() {
        return createReferences;
    }

    public void setCreateReferences(String pCreateReferences) {
        createReferences = pCreateReferences;
    }

    public boolean isHistory() {
        return history;
    }

    public void setHistory(boolean pHistory) {
        history = pHistory;
    }

    public boolean isOptimistic() {
        return optimistic;
    }

    public void setOptimistic(boolean pOptimistic) {
        optimistic = pOptimistic;
    }

    public String getPackageName() {
        return packageName;
    }

    public void setPackageName(String pPackageName) {
        packageName = pPackageName;
    }

    public String getSExtends() {
        return sExtends;
    }

    public void setSExtends(String pExtends) {
        sExtends = pExtends;
    }

    public String getSImplements() {
        return sImplements;
    }

    public void setSImplements(String pImplements) {
        sImplements = pImplements;
    }

    @SuppressWarnings("unchecked")
    protected void prepareEntity(String pEntity) throws CommondbException, HibernateException, ParseException {
        String hql = "from TgeneEntity p where p.pk.tname=:tname";
        HqlStatementHQL stm = new HqlStatementHQL(hql);
        stm.setString("tname", this.table);
        List l = stm.getList();
        Iterator it = l.iterator();
        String sEntity = null;
        String sPac = null;
        String sProject = null;
        if (it.hasNext()) {
            ent = (TgeneEntity) it.next();
            this.optimistic = (ent.getOptimisticlocking().compareTo("1") == 0);
            this.createReferences = ent.getCreatereferences();
            try {
                this.history = (ent.getHistory().compareTo("1") == 0);
            } catch (NullPointerException e) {
                this.history = false;
            }
            try {
                this.inmanual = (ent.getInmanual().compareTo("1") == 0);
            } catch (NullPointerException e) {
                this.inmanual = false;
            }
            try {
                this.cache = (ent.getCache().compareTo("1") == 0);
            } catch (NullPointerException e) {
                this.cache = false;
            }
            try {
                this.log = (ent.getLog().compareTo("1") == 0);
            } catch (NullPointerException e) {
                this.log = false;
            }
            sEntity = ent.getPk().getEntity();
            sPac = ent.getPac();
            sProject = ent.getProject();
            this.sExtends = ent.getJavaextends();
            if ((sExtends != null) && (sExtends.trim().compareTo("") == 0)) {
                this.sExtends = null;
            }
            this.sImplements = ent.getJavaimplements();
            if ((sImplements != null) && (sImplements.trim().compareTo("") == 0)) {
                this.sImplements = null;
            }
        }
        if (sEntity != null) {
            this.entity = sEntity;
        } else {
            this.entity = pEntity.substring(0, 1).toUpperCase() + pEntity.substring(1).toLowerCase();

        }
        if (sPac != null) {
            this.packageName += "." + sProject;
            this.packageName += "." + sPac;
        }
    }

    protected void formatClassCompare(PrintWriter pw) {
        pw.println("public boolean equals(Object rhs){");
        pw.println("    if (rhs == null)return false;");
        pw.println("    if (! (rhs instanceof " + this.entity + "))return false;");
        pw.println("    " + this.entity + " that = (" + this.entity + ") rhs;");
        pw.println("    if (this.getPk() == null || that.getPk() == null)");
        pw.println("        return false;");
        pw.println("    return (this.getPk().equals(that.getPk()));");
        pw.println("}");
    }

    protected void formatClassCloneable(PrintWriter pw) {
        pw.println("/**Clona la entidad " + this.entity);
        pw.println("@see com.fp.dto.hb.HibernateBean#cloneMe()");
        pw.println("*/");
        pw.println("public Object cloneMe() throws CloneNotSupportedException{");
        pw.println("  " + this.entity + " p=(" + this.entity + ")this.clone();");
        if (this.isKeyClassNeeded()) {
            pw.println("  p.setPk((" + this.entity + "Key)this.pk.cloneMe());");
        }
        pw.println("  return p;");
        pw.println("}");

    }

    public boolean isKeyClassNeeded() {
        return (this.primary.size() > 1);
    }

    public String getKeyClasss() throws APPException {
        String data = "";
        if (!this.isKeyClassNeeded()) {
            data = this.primary.get(0).getJavaSimple();
        } else {
            data = this.entity + "Key";
        }
        return data;
    }

    protected void formatHistoryNeededMethods(PrintWriter pw) {
        pw.println("public Object getId() {");
        pw.println("  return this.pk;");
        pw.println("}");
    }

    protected void formatCreateInstance(PrintWriter pw) {
        pw.println("/**Implementacion de la creacion de un bean en blanco " + this.entity);
        pw.println("*/");
        pw.println("public Object createInstance(){");
        pw.println(" " + this.entity + " instance=new " + this.entity + "();	");
        if (this.isKeyClassNeeded()) {
            pw.println(" instance.setPk(new " + this.entity + "Key());");
        }
        pw.println(" return instance;");
        pw.println("}");
    }

    protected void formatToString(PrintWriter pw, String pPrefix) {
        pw.println("/**Implementacion toString");
        pw.println("*/");
        pw.println("public String toString() {");
        pw.println("	Field[]fs=this.getClass().getDeclaredFields();");
        pw.println("	String data=\"\";");
        pw.println("	for(Field f:fs){");
        pw.println("	 try{	");
        pw.println("	 String name=f.getName();");
        pw.println("   if(f.getType().getName().compareTo(\"java.util.Set\")==0)continue;");
        pw.println("	 if(name.compareTo(\"hashValue\")==0||name.compareTo(\"serialVersionUID\")==0)continue;");
        pw.println("		data+=" + ((pPrefix.compareTo("") == 0) ? "" : "\"pk.\"+") + "name+\"=\"+f.get(this)+\";\";");
        pw.println("	 }catch(Exception e){");
        pw.println("		 continue;");
        pw.println("	 }");
        pw.println("		}");
        pw.println("		if(data.compareTo(\"\")==0){");
        pw.println("		data=super.toString();");
        pw.println("		}");
        pw.println("	return data;");
        pw.println("	}");

    }

    protected void formatClassHash(PrintWriter pw) {
        pw.println("/**Implementacion del metodo hashCode de la la entidad " + this.entity);
        pw.println("@return el hashCode la instancia");
        pw.println("*/");
        pw.println("public int hashCode()    {");
        pw.println("    if (this.hashValue == 0){");
        pw.println("        int result = 17;");
        pw.println("        if (this.getPk() == null){");
        pw.println("            result = super.hashCode();");
        pw.println("        }else{");
        pw.println("            result = this.getPk().hashCode();");
        pw.println("        }");
        pw.println("        this.hashValue = result;");
        pw.println("    }");
        pw.println("    return this.hashValue;");
        pw.println("}");
    }

    protected void createClassConstructor(PrintWriter pw) throws APPException {
        pw.println("/**Contructor por defecto*/");
        pw.println("public " + class_name + "(){");
        //pw.println("  pcs.addPropertyChangeListener(this);");
        pw.println("}");
        pw.println("/**Contructor de " + class_name);
        String parameters = this.getKeyClasss() + " pPk";
        pw.println("@param pPk Clave Primaria del entity");
        for (AbstractColumn mc : this.notNullable) {
            if (mc.isPk()) {
                continue;
            }
            mc.formatJavaDocParam(pw);
            String name = mc.getName();
            name = name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
            String par = mc.getParameterFormat();
            if (par.compareTo("") == 0) {
                continue;
            }
            parameters += "," + par;
        }
        pw.println("*/");
        pw.println("public " + class_name + "(" + parameters + "){");
        pw.println(" this();");
        pw.println(" pk=pPk;");
        for (AbstractColumn mc : this.notNullable) {
            if (mc.isPk()) {
                continue;
            }
            mc.formatAsign(pw);
        }
        pw.println("}");
    }

    protected void createKeyConstructor(PrintWriter pw) throws APPException {
        pw.println("/**Contructor por defecto*/");
        pw.println("public " + this.entity + "Key(){}");
        pw.println("/**Contructor de " + this.entity + "Key");
        String parameters = "";
        boolean first = true;
        for (AbstractColumn mc : this.primary) {
            mc.formatJavaDocParam(pw);
            String name = mc.getName();
            name = name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
            String par = mc.getParameterFormat();
            if (par.compareTo("") == 0) {
                continue;
            }
            parameters += ((first) ? "" : ",") + par;
            first = false;
        }
        pw.println("*/");
        pw.println("public " + this.entity + "Key(" + parameters + "){");
        for (AbstractColumn mc : this.primary) {
            mc.formatAsign(pw);
        }
        pw.println("}");
    }

    /**
     * Metodo que entrega datos de una sucursal definidos en TgeneBranch.
     * @param pBranch	Codigo de sucursal.
     * @param pCompany	Compania a la que pertenece la cuenta.
     * @return	TgeneBranch
     * @throws Exception
     */
    protected void addFindMethod(PrintWriter pw) {
        pw.println("/**");
        pw.println("* Metodo que entrega datos de la tabla dada la clave primaria.");
        pw.println("* @param pEntityManager referencia de la session a obtener datos del bean.");
        pw.println("* @param pKey Caleve primaria del bean.");
        pw.println("* @return " + this.entity);
        pw.println("*/");
        pw.println("public static " + this.entity + " find(EntityManager pEntityManager," + (isKeyClassNeeded() ? this.entity + "Key" : "Object") + " pKey) throws Exception{");
        pw.println("    " + this.entity + " obj = pEntityManager.find(" + this.entity + ".class,pKey);");
        pw.println("    return obj;");
        pw.println("}");
    }

    protected void formatKeyClone(PrintWriter pw) {
        pw.println("public Object cloneMe() throws CloneNotSupportedException {");
        pw.println("  return this.clone();");
        pw.println("}");
    }

    protected void formatKeyHistoryNeededMethods(PrintWriter pw) {

        pw.println("public Object getId() {");
        pw.println("  return null;");
        pw.println("}");
    }

    protected void formatBasicsJava(PrintWriter pw) {
        pw.println("/**");
        pw.println("* HashCode asociado con la Instancia");
        pw.println("*/");
        pw.println("@Transient");
        pw.println("private int hashValue = 0;");
        pw.println("/**");
        pw.println("* Version de la Clase");
        pw.println("*/");
        pw.println("private static final long serialVersionUID = 1L;");
    }

    protected void formatKeyEquals(PrintWriter pw) {
        pw.println("/**Implementacion de la comparacion de " + this.entity + "Key");
        pw.println("@param o Objeto de comparacion");
        pw.println("*/");
        pw.println("public boolean equals(Object o){");
        pw.println("  if (o == null)return false;");
        pw.println("  if (! (o instanceof " + this.entity + "Key))return false;");
        pw.println("  " + this.entity + "Key that = (" + this.entity + "Key) o;");
        for (AbstractColumn mc : this.primary) {
            mc.formatCompare(pw);
        }
        pw.println("  return true;");
        pw.println("}");
    }

    protected void formatKeyHash(PrintWriter pw) {
        pw.println("/**Implementacion del metodo hashCode bajo el patron de Bloch");
        pw.println("@return hashCode de la instancia " + this.entity + "Key");
        pw.println("*/");
        pw.println("public int hashCode(){");
        pw.println("  if (this.hashValue == 0){");
        pw.println("    int result = 17;");
        for (AbstractColumn mc : this.primary) {
            mc.formatHash(pw);
        }
        pw.println("    this.hashValue = result;");
        pw.println("  }");
        pw.println("  return this.hashValue;");
        pw.println("}");
    }

    public TgeneEntity getEnt() {
        return ent;
    }
}
