/*
 * Adaptation.java
 * Este fichero se ha generado de forma que compile con la version 1.3 de Java
 */
package com.uvigo.tMaestro.SCO;

import com.uvigo.tMaestro.SCO.adaptation.SourceValue;
import org.osgi.framework.BundleContext;

/**
 * Clase que decide en base a los datos proporcionados por el LMS y el contenido
 * del fichero de adaptacion cual es la mejor opcion para el SCO.
 * @author raul
 * @version 0.1
 */
public class Adaptation {
    
    
    /**
     * Esta constante se utilizan para localizar al LMS.
     * Su valor es el nombre completo de la clase.
     */
    final private static String sNameDebugAPILMSClass="com.uvigo.tMaestro.SCO.APILMS";
    
    final private static String sNameAPILMSClass="com.uvigo.tMaestro.API.API";
    
    /*
     * Constantes
     *
     */
    final private static int ANY=0;
    final private static int ALL=1;
    
    /**
     * Alamacena pares (nombre del parametro, valor del parametro)
     */
    private java.util.HashMap/*<String,String>*/ oParameters;
    
    /**
     * Clase de la API del LMS con el SCO
     */
    private Class oAPILMSClass;
    
    /**
     * Referencia a una instancia de la API del LMS con el SCO
     */
    private Object oAPILMS;
    
    /**
     * Contexto pasado por el entorno OSCAR en el que puede estar lanzado un SCO
     */
    private BundleContext oContext;
    
    /**
     * Constructor de la clase
     */
    public Adaptation(BundleContext oContext) {
        this.oContext=oContext;
    }
    
    /**
     * Esta funcion tiene que localizar una instancia de la API del LMS.
     * @throws Exception Si no se encuentra la clase del la API del LMS con el SCO
     */
    private void locateLMSAPIInstance() throws Exception{
        try { //Intentamos obtener la referencia a la API a traves del framework OSGI
            oAPILMS = oContext.getService(oContext.getServiceReference(sNameAPILMSClass));
            oAPILMSClass = oAPILMS.getClass();
            if (oAPILMS==null) throw new Exception();
        } catch (Exception e) { //Si no es posible, es porque estamos en entorno de depuracion, utilizamos la API de prueba
            oAPILMSClass = Class.forName(sNameDebugAPILMSClass);
            oAPILMS = oAPILMSClass.newInstance();
        }
        
        
        
    }
    
    /**
     * Devuelve una referencia  a la API del LMS
     */
    public Object getAPILMS() {
        return oAPILMS;
    }
    
    
    /**
     * Objeto Class de la API del LMS
     */
    public Class getAPILMSClass() {
        return oAPILMSClass;
    }
    
    
    
    /**
     * Este metodo consulta los valores que los parametros indicados en el adaptation.xml
     * tienen en el LMS. Para ello utiliza la API con el LMS, consultando los valores
     * y almacenandolos en el HashMap de parametros.
     * @param oAdapatationElement Referencia al elemento raiz del adaptation.xml
     * @param oAPI Referencia a la API con el LMS
     * @param oAPIClass El objeto Class de la API con el LMS.
     */
    private void readLMSValueParameters(com.uvigo.tMaestro.SCO.adaptation.Adaptation oAdaptationElement) throws Exception {
        oParameters = new java.util.HashMap/*<String,String>*/();
        
        //Obtenemos referencia al m�todo getValue de la API del LMS con el SCO
        Class[] oClasses = {String.class};
        java.lang.reflect.Method oGetValueMethod = oAPILMSClass.getMethod("GetValue", oClasses);
        
        //Leemos del fichero .xml los parametros y vamos consultando su valor al
        //LMS y almacen�ndolos en la estructura oParameters.
        java.util.List oAdaptationParameters = oAdaptationElement.getAdaptationParameters().getAdaptationParameter();
        for (int i=0;i<oAdaptationParameters.size();i++) {
            SourceValue oAdaptationParameter = (SourceValue) oAdaptationParameters.get(i);
            String sParameterName = oAdaptationParameter.getSource().concat("."+oAdaptationParameter.getValue());
            Object[] oArgs = new Object[1];
            oArgs[0]=translate(sParameterName,oGetValueMethod); //El nombre del parametro segun se debe presentar al LMS
            if (oArgs[0]==null) continue;
            String sValue = (String) oGetValueMethod.invoke(oAPILMS, oArgs);
            if (sValue==null) continue;
            oParameters.put(sParameterName, sValue);
        }
        
    }
    
    /**
     * Esta funcion traduce el nombre de un parametro de forma que se pueda
     * pedir el valor al LMS. Basicamente realiza la traduccion necesaria para
     * poder pedir al LMS un parametro de tipo coleccion o que forme parte de
     * un parametro de tipo coleccion. Devuelve null si no es posible la traducci�n
     * @param sParameter El nombre completo del parametro
     * @param oGetValueMethod Referencia al metodo get de la API del LMS
     */
    private String translate(String sParameter,java.lang.reflect.Method oGetValueMethod) {
        try {
            Object[] oArgs = new Object[1];
            String sId;
            String sEnd;
            String sCollection;
            int iIndex;
            bucle: while ((iIndex=sParameter.indexOf(".n."))!=-1) {
                sCollection = sParameter.substring(0,iIndex);
                //System.out.println(sCollection);
                sEnd = sParameter.substring(iIndex+3);
                //System.out.println(sEnd);
                oArgs[0]=sCollection+"._count";
                //Obtenemos el identificador del elemento que forma parte de la colecci�n
                sId = sEnd.substring(0,sEnd.indexOf("."));
                //System.out.println(sId);
                //System.out.println(oArgs[0]);
                int iNum = Integer.parseInt((String) oGetValueMethod.invoke(oAPILMS, oArgs));
                if (iNum==0) return null;
                for (int i=0;i<iNum;i++) {
                    oArgs[0]=sCollection+"."+i+".id";
                    //System.out.println(oArgs[0]);
                    if (sId.equals((String) oGetValueMethod.invoke(oAPILMS, oArgs))) {
                        sParameter = sCollection+"."+i+"."+sEnd.substring(sEnd.indexOf(".")+1);
                        //System.out.println(sParameter);
                        continue bucle;
                    }
                }
                return null;
            }
            return sParameter;
        } catch(Exception oE) {
            oE.printStackTrace();
            return null;
        }
        
        
    }
    
    /**
     * Este metodo examina las reglas del fichero de adaptacion y en funcion de los
     * valores de los parametros anteriormente obtenidos por la funcion readLMSValueParameters
     * busca la primera que se cumple, devolviendo la ruta al fichero de la opcion asociada a
     * la mencionada regla.
     * @param oAdapatationElement Referencia al elemento raiz del adaptation.xml
     * @return La ruta al fichero de la opci�n asociada a la primera regla que se verifique
     */
    private String verifyRules(com.uvigo.tMaestro.SCO.adaptation.Adaptation oAdaptationElement) throws Exception{
        //La lista de reglas
        java.util.List oRulesList =oAdaptationElement.getAdaptationRules().getRule();
        
        //Recorremos la lista de reglas hasta encontrar la primera que se cumpla. Buscamos tambien la regla por defecto
        com.uvigo.tMaestro.SCO.adaptation.AdaptationType.AdaptationRulesType.RuleType oRuleDefault=null;
        rules: for (int i=0;i<oRulesList.size();i++) {
            com.uvigo.tMaestro.SCO.adaptation.AdaptationType.AdaptationRulesType.RuleType oRuleElement = (com.uvigo.tMaestro.SCO.adaptation.AdaptationType.AdaptationRulesType.RuleType) oRulesList.get(i);
            //Vemos si esta es la regla por defecto
            if (oRuleElement.getRuleConditions().isDefault()) oRuleDefault=oRuleElement;
            //Vemos si las RuleConditions se combinan en un AND o en un OR
            int iCombination=ALL;
            if (oRuleElement.getRuleConditions().getConditionCombination().equals("any")) iCombination=ANY;
            //La lista de ruleConditions
            java.util.List oRuleConditionsList = oRuleElement.getRuleConditions().getRuleCondition();
            //Recorremos cada una de las RuleConditions. Si se combinan con un OR y una es cierta --> la regla se cumple
            //Si se combinan con un AND y una no es cierta --> la regla no se cumple
            for (int y=0;y<oRuleConditionsList.size();y++) {
                com.uvigo.tMaestro.SCO.adaptation.AdaptationType.AdaptationRulesType.RuleType.RuleConditionsType.RuleConditionType oRuleConditionElement = (com.uvigo.tMaestro.SCO.adaptation.AdaptationType.AdaptationRulesType.RuleType.RuleConditionsType.RuleConditionType) oRuleConditionsList.get(y);
                boolean bTrue = verifyCondition(oRuleConditionElement.getAdaptationParameter().getSource(),oRuleConditionElement.getAdaptationParameter().getValue(),oRuleConditionElement.getComparative().getOperator(),oRuleConditionElement.getComparative().getValue(),oRuleConditionElement.getOperator());
                if (bTrue) { //es cierta
                    if (iCombination==ANY) return getRuleAction(oRuleElement);
                } else { //no es cierta
                    if (iCombination==ALL) continue rules; //pasamos a la siguiente regla
                }
            }
            //Si llegamos aqui hemos comprobado todas las RuleComditions de esta regla
            //Si iCombination es ALL es porque todas se ha cumplido, la regla se ha cumplido devolvemos la accion de la regla
            //Si iCombination es ANY es porque ninguna se ha cumplido, debemos seguir comprobando reglas
            if (iCombination==ALL) return getRuleAction(oRuleElement);
        }
        //Si llegamos a este punto es que ninguna regla se ha cumplido. Devolvemos la accion de la regla por defecto
        if (oRuleDefault!=null) return getRuleAction(oRuleDefault);
        
        return null; //No deberia llegar nunca aqui en un funcionamiento normal. De hecho solo saldria por aqui en una Excepci�n o
        //si no se encontrase la regla por defecto y ninguna de las otras reglas se hubiese cumplido
    }
    
    
    /**
     * Devuelve la ruta al fichero de aquella opci�n asociada a la primera regla
     * que se verifica.
     */
    public String adapt() {
        try {
            //Localizamos la API del LMS con el SCO
            locateLMSAPIInstance();
            
            //Creamos el JAXBContext y el Unmarshaller que nos serviran de ayuda
            //para leer los el fichero .xml de adaptacion
            javax.xml.bind.JAXBContext oJC = javax.xml.bind.JAXBContext.newInstance("com.uvigo.tMaestro.SCO.adaptation");
            javax.xml.bind.Unmarshaller oUn = oJC.createUnmarshaller();
            com.uvigo.tMaestro.SCO.adaptation.Adaptation oAdaptationElement;
            
            //Obtenemos el elemento ra�z "Adaptation" del fichero de adaptacion.
            oAdaptationElement = (com.uvigo.tMaestro.SCO.adaptation.Adaptation) oUn.unmarshal(this.getClass().getResource("/Adaptation/adaptation.xml").openConnection().getInputStream());
            
            readLMSValueParameters(oAdaptationElement);
            return verifyRules(oAdaptationElement);
            
        } catch(Exception oE) {
            oE.printStackTrace();
            return null;
        }
    }
    
    /**
     * Metodo que indica si la RuleCondition de par�metros indicados
     * se cumple en el LMS
     * @param sPrefix El vocabulario al que pertenece el parametro
     * @param sParameter El nombre del parametro de esta ruleCondition
     * @param sOperator El operador de la condicion
     * @param sValue El valor para la condicion
     * @param sNot Si la condici�n debe ser cierta o falsa respecto a la del LMS
     */
    private boolean verifyCondition(String sPrefix,String sParameter,String sOperator,String sValue,String sNot) throws Exception{
        //Obtenemos el valor del par�metro en el LMS
        String sLMSValue=(String)oParameters.get(sPrefix+"."+sParameter);
        if (sLMSValue==null) return false;
        boolean bState=false;
        if (sOperator.equals("equals")) bState = sValue.equals(sLMSValue) ? true:false;
        else { //Si se usan operadores de comparaci�n convertimos los Valores a n�meros (double)
            double dValue = Double.parseDouble(sValue);
            double dLMSValue = Double.parseDouble(sLMSValue);
            if (sOperator.equals("moreThan")) bState=dLMSValue>dValue ? true:false;
            else if (sOperator.equals("lessThan")) bState=dLMSValue<dValue? true:false;
            else if (sOperator.equals("moreEqualThan")) bState=dLMSValue>=dValue ? true:false;
            else bState=dLMSValue<=dValue ? true:false;
        }
        if (sNot.equals("not")) bState=!bState;
        return bState;
    }
    
    /**
     * Devuelve la ruta al fichero de opci�n que corresponde a la rela oRule
     */
    private String getRuleAction(com.uvigo.tMaestro.SCO.adaptation.AdaptationType.AdaptationRulesType.RuleType oRule) {
        return oRule.getRuleAction().getFile();
    }
    
    
}
