/*
 * Propiedad.java
 *
 */

package com.uvigo.tMaestro.SCOCreator.tool.properties;
import com.uvigo.tMaestro.SCOCreator.tool.MainFrame;

/**
 * Se utiliza para almacenar los valores, tipos, y PropertiesDescriptor de todas
 * las propiedades de un bean.
 * @author raul
 * @version 0.1
 */
public class Propiedad {
    
    
    private java.beans.PropertyDescriptor oDescriptor; // Nota --> no se si hara falta
    
    /**
     * El bean al que pertenece esta propiedad
     */
    private Object oBean;
    
    /**
     * Los ficheros asociados a esta propiedad. Solo tiene sentido para propiedades
     * que sean una ruta a un fichero. Un ArrayList para cada opcion.
     */
    private java.util.ArrayList<java.util.ArrayList<java.io.File>> oPropertyFiles;
    
    /**
     * El nombre de la propiedad
     */
    private String sNombre;
    
    /**
     * La clase Java de la propiedad
     */
    private Class oClase;
    
    /**
     * Los valores de la propiedad en las diferentes opciones.
     */
    private java.util.ArrayList oValue;
    
    /**
     * Valor original de la propiedad en la plantilla
     */
    private Object  oOriginal;
    
    /**
     * El valor actual de la propiedad
     */
    private Object oCurrentValue;
    
    /**
     * El descriptor del metodo para leer el valor de la propiedad
     */
    private java.lang.reflect.Method oMetodoLectura;
    
    /**
     * El descriptor del metodo para escribir el valor de la propiedad
     */
    private java.lang.reflect.Method oMetodoEscritura;
    
    /**
     * El editor declarado por el dise�ador del bean para esta propiedad.
     * Vale null si no ha declarado ninguno.
     */
    private java.beans.PropertyEditor oEditor;
    
    /**
     * Indica si la propiedad es de solo lectura o puede ser escrita.
     */
    private boolean bReadOnly=false;
    
    
    /**
     * Indica que la propiedad referencia a un archivo
     */
    private boolean bFile=false;
    
    /**
     * Indica que esta propiedad no sera serializada, y que sera recuperada
     * a traves de su constructor sin parametros. Esto es asi, porque esta propiedad
     * no es un valor de una clase determinada, sino que su valor pueden ser objetos
     * de diferentes clases (en general, objetos obtenidos a traves de constructores sin
     * parametros). Las llamaremos propiedades de clases mutables.
     */
    private boolean bMutable=false;
    
    /**
     * Crea una instancia de esta clase.
     * @param oDescriptor El PropertyDescripor de la propiedad
     * @param oBean El bean al que pertenece la propiedad
     * @param iOpciones El numero de opciones que se consideran en el entorno
     * @param oValue Los valores del proyecto previo (null si no hay proyecto previo)
     */
    public Propiedad(java.beans.PropertyDescriptor oDescriptor,Object oBean,int iOpciones,java.util.ArrayList oValue
            ,String sNombrePlantilla,java.util.ArrayList<java.util.ArrayList<java.io.File>> oPropertyFiles){
        sNombre = oDescriptor.getName();
       
        oMetodoLectura = oDescriptor.getReadMethod();
        oMetodoEscritura = oDescriptor.getWriteMethod();
        if (oMetodoEscritura==null) bReadOnly=true;
        //Hay ciertas propiedades que interesa que no puedan ser editadas(guardadas) (aunque tengan metodos set)
        if (sNombre.equals("contextManager") || sNombre.equals("verticalScrollControl") || sNombre.equals("horizontalScrollControl")) bReadOnly=true;
        else if (sNombre.equals("look")) { //propiedades de clases mutables
            bMutable=true;
            if (oValue!=null) {
                try {
                    int i=0;
                    for (i=0;i<oValue.size();i++) oValue.set(i,((Class) oValue.get(i)).newInstance());
                } catch(Exception oE){oValue=null;}
            }
        }
        oEditor = oDescriptor.createPropertyEditor(oBean);
        this.oBean = oBean;
        this.oReadCurrentValue();
        if (!bMutable) {
            oOriginal=oCurrentValue;
        } else {
            if (oCurrentValue!=null) oOriginal=oCurrentValue.getClass();
        }
        oClase=oDescriptor.getPropertyType();
        if (oClase==null && (oCurrentValue!=null)) oClase = oCurrentValue.getClass();
        //Hay ciertas propiedades que interesa que no puedan ser editadas (aunque tengan metodos set)
        //HTextLook es una propiedad mutable con una �nica posibilidad, por tanto evitamos su edicion
        
        if (oClase!=null) if (oClase.equals(com.uvigo.mhp.looks.HPanelLook.class)) bReadOnly=true;
        if (oValue==null) {
            this.oValue = new java.util.ArrayList();
            int i=0;
            for (i=0;i<iOpciones;i++) {
                this.oValue.add(oCurrentValue);
            }
        } else {
            this.oValue=oValue;
            vWriteValue(MainFrame.oMainFrame.iGetOpcion());
        }
        if ((sNombre.contains("File") || sNombre.contains("URL")) && (oClase.equals(String.class))) { //Si es una propiedad que referencia a un archivo
            bFile = true;
            this.oPropertyFiles = oPropertyFiles;
            if (oPropertyFiles==null) {
                this.oPropertyFiles = new java.util.ArrayList<java.util.ArrayList<java.io.File>>();
                java.util.ArrayList<java.io.File> oFiles = defaultFiles(sNombrePlantilla);
                for (int i=0;i<iOpciones;i++) {
                    this.oPropertyFiles.add(oFiles);
                    
                }
            }
        }
        
        
    }
    
    /**
     * Este metodo lee el valor que presenta actualmente
     * la propiedad en el bean --> fuerza la relectura de la propiedad
     * @return El valor actual de la propiedad
     */
    public Object oReadCurrentValue() {
        try {
            oCurrentValue = oMetodoLectura.invoke(oBean);
        } catch(Exception oE){}
        return oCurrentValue;
    }
    
    /**
     * Este metodo escribe el valor de la opcion iOpcion en el bean, es decir,
     * llama al metodo Set de la propiedad
     * @param iOpcion La opcion cuyo valor se desea establecer en el bean
     * que el entorno esta mostrando en pantalla.
     */
    public void vWriteValue(int iOpcion) {
        try {
            //NOTA: realmente no hace falta hacer el cast al valor para llamar al metodo invoke, de eso ya se encarga el.
            //Class oClaseTemp;
            //Estas lineas son necesarias poque el metodo cast de la clase "Class"
            //lanza una excepcion si se intenta hacer un cast de un tipo compuesto
            //a uno primitivo, por ejemplo de Integer a int
            //if (oClase.isPrimitive()) oClaseTemp = oCurrentValue.getClass();
            //else oClaseTemp = oClase;
            //Invocamos el metodo de escritura con la Clase adecuada
            Object oOptionValue = oValue.get(iOpcion);
            //if (oOptionValue==null) return;
            //if (oCurrentValue.equals(oOptionValue)) return; //Si bien esta linea parece adecuada, hay ciertos
            //casos en los que no cambia el valor de la propiedad, pero cambia algo. Ej: una propiedad que denota
            //una ruta a un fichero, no cambia la ruta, pero si el contenido del fichero, en este caso es necesario llamar a invoke
            //En este contexto la siguiente linea parece ser valida
            //Algunas propiedades de texto dan problemas si no se escribe la propiedad (el texto de un JRadioButton por ejemplo)
            if (oCurrentValue!=null && oCurrentValue.equals(oOptionValue) && (!sNombre.toLowerCase().contains("file")) && (!sNombre.toLowerCase().contains("text"))) return;
            if (oMetodoEscritura!= null) oMetodoEscritura.invoke(oBean,/*oClaseTemp.cast(*/oOptionValue);
            oCurrentValue = oOptionValue;
            if (oBean instanceof java.awt.Component) ((java.awt.Component) oBean).repaint();
        } catch(Exception oE){
            oE.printStackTrace();
        }
    }
    
    
    /**
     * Devuelve el valor actual de la propiedad
     * @return El Valor
     */
    public Object oGetValue() {
        return oCurrentValue;
    }
    
    
    /**
     * Devuelve el nombre de la propiedad
     * @return El nombre.
     */
    public String oGetName(){
        return sNombre;
    }
    
    /**
     * Devuelve el valor de la propiedad contemplado para la opcion iOpcion
     * @param iOpcion La opcion cuyo valor de propiedad se desea conocer
     * @return El valor de la propiedad
     */
    public Object oGetOptionValue(int iOpcion) {
        return oValue.get(iOpcion);
    }
    
    /**
     * Anade una nueva opcion al Arraylist de opciones
     * @param bCurrent true si la nueva opcion se copia a partir de la opcion actualmente cargada
     * o false si se copia a partir de la opcion de plantilla original
     */
    public void addOption(boolean bCurrent) {
        if (!bCurrent) {
            if (!bMutable) oValue.add(oOriginal);
            else {
                try {oValue.add(((Class)oOriginal).newInstance());} catch(Exception oE){oE.printStackTrace();}
            }
            if (bFile) {
                oPropertyFiles.add(defaultFiles(MainFrame.oMainFrame.getTemplateName()));
            }
        } else {
            oValue.add(oCurrentValue);
            if (bFile) { //
                if (!isPropertyChanged(MainFrame.oMainFrame.iGetOpcion())) {
                    oPropertyFiles.add(defaultFiles(MainFrame.oMainFrame.getTemplateName()));
                } else {
                    java.util.ArrayList<java.io.File> oFiles = oPropertyFiles.get(MainFrame.oMainFrame.iGetOpcion());

                    String sCurrentValue= (String) oCurrentValue;
                    if (sCurrentValue.contains("(")) { //Debemos cambiar el numero de la opcion del fichero y crear un nuevo fichero
                        int iIndex=sCurrentValue.indexOf(")");
                        if (iIndex==-1) return;
                        String sNewCurrentValue=sCurrentValue.substring(0,sCurrentValue.lastIndexOf("(")+1)+String.valueOf(oValue.size()-1)+sCurrentValue.substring(iIndex);
                        oValue.set(oValue.size()-1,sNewCurrentValue);
                        java.io.File oFile = oFiles.get(0); //Fichero .xml
                        java.io.File oNewFile = new java.io.File(oFile.getParent(),sNewCurrentValue.substring(sNewCurrentValue.lastIndexOf("(")));
                        java.util.ArrayList<java.io.File> oNewFiles = new java.util.ArrayList<java.io.File>();
                        oNewFiles.add(oNewFile);
                        for (int i=1;i<oFiles.size();i++) oNewFiles.add(oFiles.get(i));
                        for (int i=0;i<oNewFiles.size();i++) MainFrame.oMainFrame.incUses(oNewFiles.get(i).getName());
                        oPropertyFiles.add(oNewFiles);
                        try {
                            MainFrame.copyFile(oFile,oNewFile);
                        } catch(Exception oE) {
                            oE.printStackTrace();
                            System.out.println("Error al crear fichero para la nueva opci�n");
                        }
                    } else {
                        oPropertyFiles.add(oFiles);
                        for (int i=0;i<oFiles.size();i++) MainFrame.oMainFrame.incUses(oFiles.get(i).getName());
                    }
                    
                }
            }
        }
    }
    
    /**
     * Elimina la opcion de indice iIndex
     * @param iIndex El indice de la opcion a eliminar
     */
    public void removeOption(int iIndex) {
        if (bFile) {
            //Debemos descontar los ficheros aportados por esta opcion y si es posible borrarlos (ver metodo putOptionFiles de esta clase)
            if (isPropertyChanged(iIndex)) for (int i=0;i<oPropertyFiles.get(iIndex).size();i++) {
                java.io.File oFile = oPropertyFiles.get(iIndex).get(i);
                MainFrame.oMainFrame.decUses(oFile.getName());
                if (MainFrame.oMainFrame.getUses(oFile.getName())==0) {
                    if (!oFile.delete()) {
                        //NOTA: solo por motivos de depuracion. Eliminarlo
                        System.out.println("NO PUEDE ELIMINARSE EL FICHERO: "+oFile);
                    }
                }
            }
            oPropertyFiles.remove(iIndex);
        }
        oValue.remove(iIndex);
    }
    
    
    /**
     * Esta funcion devuelve el objeto Class que representa la clase
     * de la propiedad
     * @return La Clase de la propiedad
     */
    public Class oGetClase() {
        return oClase;
    }
    
    /**
     * Establece un nuevo valor para una propiedad en la opcion indicada por
     * parametro.
     * @param oValue El nuevo valor
     * @param iOpcion La nueva opcion
     */
    public void vSetOptionValue(Object oValue,int iOpcion) {
        this.oValue.set(iOpcion,oValue);
    }
    
    
    /**
     * Devuelve el editor de la propiedad
     * declarado por el disenador del Bean.
     * @return El editor de la propiedad
     */
    public java.beans.PropertyEditor oGetEditor() {
        return oEditor;
    }
    
    /**
     * Dice si la propiedad puede ser escrita o no.
     * @return False si se puede escribir, true en caso contrario
     */
    public boolean isRedOnly() {
        return bReadOnly;
    }
    
    /**
     * Devuelve el ArrayList con los valores de las opciones. Las propiedades de clases mutables
     * en lugar del valor (es decir una referencia al objeto) devolveran los objetos
     * class de cada una de las opciones.
     */
    public java.util.ArrayList getPropertyOptions() {
        if (bMutable) {
            java.util.ArrayList oClases = new java.util.ArrayList();
            int i=0;
            for (i=0;i<oValue.size();i++) oClases.add(oValue.get(i).getClass());
            return oClases;
        } else return oValue;
    }
    
    
    /**
     * Devuelve el ArrayList oPropertyFiles
     */
    public java.util.ArrayList<java.util.ArrayList<java.io.File>> getPropertyFiles() {
        return oPropertyFiles;
    }
    
    /**
     * Devuelve el Bean al que pertenece esta propiedad
     * @return Un object que representa al Bean
     */
    public Object getBean() {
        return this.oBean;
    }
    
    /**
     * Devuelve el valor original de la propiedad en la plantilla
     */
    public Object getOriginalValue() {
        return oOriginal;
    }
    
    /**
     * Convierte la propiedad en una de solo lectura o escritura/lectura
     */
    public void setReadOnly(boolean bState) {
        bReadOnly=bState;
    }
    
    /**
     * Indica si el valor de esta propiedad para la opcion i
     * ha cambiado respecto al valor original de plantilla
     * @param iOpcion La opcion
     * @return True si la propiedad ha cmabiado
     */
    public boolean isPropertyChanged(int iOpcion) {
        if (oValue.get(iOpcion)==null) return false;
        if (oOriginal==null) return true;
        if (bMutable) {
            if (!oOriginal.equals(oValue.get(iOpcion).getClass())) return true;
            else return false;
        } else {
            if (!oOriginal.equals(oValue.get(iOpcion))) return true;
            else return false;
        }
    }
    
    /**
     * Indica si la propiedad es mutable, es decir, si es
     * una propiedad cuyo valor pueden ser objetos de
     * diferentes clases.
     * @return True si es mutable.
     */
    public boolean isMutable() {
        return bMutable;
    }
    
    /**
     * Devuelve un ArrayList con los ficheros asociados a esta
     * propiedad en la plantilla original.
     * Este metodo solo deberia ser llamado por las
     * propiedades en las que proceda.
     */
    private java.util.ArrayList<java.io.File> defaultFiles(String sNombrePlantilla)  {
        if (!bFile) return null;
        if (oOriginal==null || oOriginal.equals("")) return null;
        //Obtenemos el nombre del fichero sin extension.
        String[] sDivision = oOriginal.toString().split("/");
        String sLast = sDivision[sDivision.length-1];
        String sPrefix = sLast.substring(0,sLast.lastIndexOf('.'));
        java.util.ArrayList<java.io.File> oArray = new java.util.ArrayList<java.io.File>();
        //El directorio de la plantilla
        java.io.File oDirTemplate = new java.io.File(MainFrame.oMainFrame.getToolDir()+"/templates/"+sNombrePlantilla+"/");
        //Anadimos primero el fichero de la propiedad
        oArray.add(new java.io.File(oDirTemplate,sLast));
        //Ficheros en el directorio de la plantilla
        java.io.File[] oFiles = oDirTemplate.listFiles();
        for (java.io.File oFile: oFiles) { //A�adimos s�lo los ficheros que se refieren a esta propiedad.
            String sName = oFile.getName();
            int iIndex = sName.lastIndexOf('_');
            if (iIndex==-1) continue;
            if (sName.substring(0,iIndex).equals(sPrefix)){
                oArray.add(oFile);
            }
        }
        
        return oArray;
    }
    
    /**
     * Establece los nuevos ficheros para una opcion asociados a esta propiedad.
     * Solo tiene sentido con propiedades que denoten una ruta a un fichero.
     * @param oFiles Los nuevos ficheros asociados a la propiedad
     * @param iOpcion La opcion
     */
    public void putOptionFiles(java.util.ArrayList<java.io.File> oFiles, int iOpcion) {
        if (!bFile) return;
        //Primeramente eliminamos los ficheros de la configuraci�n anterior (siempre y cuando no
        //sean los de la opcion por defecto y no se mantengan en la nueva configuracion, y ademas no sean utilizados por ningun otro
        //componente en ninguna opcion)
        //Podemos llamar a isPropertyChanged pues se llama antes a esta funcion que a la que establece el nuevo valor para la propiedad
        if (isPropertyChanged(iOpcion)) for (int i=0;i<oPropertyFiles.get(iOpcion).size();i++) {
            java.io.File oFile = oPropertyFiles.get(iOpcion).get(i);
            MainFrame.oMainFrame.decUses(oFile.getName());
            if (!oFiles.contains(oFile)) {
                if (MainFrame.oMainFrame.getUses(oFile.getName())==0) {
                    if (!oFile.delete()) {
                        //NOTA: solo por motivos de depuracion. Eliminarlo
                        System.out.println("NO PUEDE ELIMINARSE EL FICHERO: "+oFile);
                    }
                }
            }
        }
        //Anadimos los nuevos ficheros
        oPropertyFiles.set(iOpcion, oFiles);
        //Incrementamos su uso en una unidad
        for (int i=0;i<oFiles.size();i++) MainFrame.oMainFrame.incUses(oFiles.get(i).getName());
    }
    
    /**
     * Devuelve true si el valor de la propiedad denota un fichero.
     * @return True si la propiedad se refiere a un fichero.
     */
    public boolean isFileRelated() {
        return bFile;
    }
    
    
}
