package com.uvigo.mhp;

import com.uvigo.mhp.ui.*;
import com.uvigo.mhp.xml.test.*;
import java.awt.*;
import java.beans.*;
import java.lang.reflect.*;
import java.net.*;
import java.util.*;
import javax.xml.bind.*;

// Un objeto Test lee toda la configuraci�n referente a las preguntas de un fichero XML,
// y las a�ade al contenedor que se le indique. Si el contenedor es tal que los objetos
// que se le a�aden no son visibles todos simult�neamente, el dise�ador de la p�gina habr�
// de proporcionar controles para secuenciarlas (en un HMultiPanel, botones para cambiar
// a la siguiente vista o a la anterior...)

public class Test extends Component implements TestElement
{
    private String questionsDefaultClassName;
    private String questionGroupsDefaultClassName;
    private String optionsDefaultClassName;
    private java.util.List questionsCommonParameters;
    private java.util.List questionGroupsCommonParameters;
    private java.util.List optionsCommonParameters;
    private int minimumCorrectAnswers;
    
    private Vector questions;
    private Vector questionGroups; // lo que se a�ade al contenedor de preguntas
    private PropertyChangeSupport changes;
    private TestManager testManager;
    private Vector listeners;
    
    private Container questionsContainer;
    
    private String configurationFile;
    private JAXBContext jc;
    private TestConfiguration conf;
    private ClassLoader loader;
    
    public Test()
    {
        changes=new PropertyChangeSupport(this);
        configurationFile="";
        questionsDefaultClassName=null;
        optionsDefaultClassName=null;
        questionsCommonParameters=null;
        optionsCommonParameters=null;
        minimumCorrectAnswers=0;
        loader=ClassLoader.getSystemClassLoader();
        listeners=new Vector();
        
        try
        {
            jc=JAXBContext.newInstance("com.uvigo.mhp.xml.test");
            conf=null;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }
    
    public String getConfigurationFile()
    {
        return configurationFile;
    }
    
    public void setConfigurationFile(String s)
    {
        //System.out.println("Se llama a setConfigurationFile");
        String old=configurationFile;
        if (!(s.equals(old)))
        {
            questions=new Vector();
            questionGroups=new Vector();
            
            try
            {
                if ((s==null) || (s=="")) return;
                
                //System.out.println("Cargando configuracion");
                URL url=new URL(s);
                URLConnection con=url.openConnection();
                
                Unmarshaller u=jc.createUnmarshaller();
                u.setValidating(true);
                conf=(TestConfiguration) u.unmarshal(con.getInputStream());
                
                questionsDefaultClassName=conf.getQuestionsDefaultClassName();
                questionGroupsDefaultClassName=conf.getQuestionGroupsDefaultClassName();
                optionsDefaultClassName=conf.getOptionsDefaultClassName();;
                questionsCommonParameters=conf.getQuestionsCommonParameters();
                questionGroupsCommonParameters=conf.getQuestionGroupsCommonParameters();
                optionsCommonParameters=conf.getOptionsCommonParameters();
                minimumCorrectAnswers=new Integer(conf.getMinimumCorrectAnswers()).intValue();
                
                // Ahora hay que leer los questionGroup y construir las preguntas con sus
                // opciones para anadirlas al contenedor especificado.
                java.util.List groupList=conf.getQuestionGroup();
                for (int i=0;i<groupList.size();i++)
                {
                    //System.out.println("Grupo "+i);
                    QuestionGroupType qgt=(QuestionGroupType) groupList.get(i);
                    
                    // Creamos un contenedor para las preguntas del grupo. Si no se indica
                    // una clase en el fichero, ni siquiera una por defecto, se utiliza un HGroupContainer
                    // basico, que es un HPanel que se devuelve a si mismo como contenedor para las preguntas.
                    QuestionGroupContainer groupContainer;
                    String groupContainerClassName=qgt.getClassName();
                    if ((groupContainerClassName==null) || (groupContainerClassName.equals("")))
                    {
                        if ((questionGroupsDefaultClassName==null) || (questionGroupsDefaultClassName.equals("")))
                            groupContainer=new QuestionGroupContainer();
                        else
                        {
                            Class groupContainerClass=loader.loadClass(questionGroupsDefaultClassName);
                            groupContainer=(QuestionGroupContainer) groupContainerClass.newInstance();
                        }
                    }
                    else
                    {
                        Class groupContainerClass=loader.loadClass(groupContainerClassName);
                        groupContainer=(QuestionGroupContainer) groupContainerClass.newInstance();
                    }
                    
                    if (questionGroupsCommonParameters!=null)
                        applyParameters(groupContainer, questionGroupsCommonParameters);
                    if (qgt.getParameters()!=null)
                        applyParameters(groupContainer, qgt.getParameters());
                    
                    java.util.List questionList=qgt.getQuestion();
                    for (int j=0;j<questionList.size();j++)
                    {
                        //System.out.println("Pregunta "+j);
                        QuestionType qt=(QuestionType) questionList.get(j);
                        String questionClassName=qt.getClassName();
                        if ((questionClassName==null) || (questionClassName.equals("")))
                            questionClassName=questionsDefaultClassName;
                        
                        Class questionClass=loader.loadClass(questionClassName);
                        TestQuestion q=(TestQuestion) questionClass.newInstance(); // se llama al constructor sin par�metros
                        
                        if (questionsCommonParameters!=null)
                            applyParameters(q, questionsCommonParameters);
                        if (qt.getParameters()!=null)
                            applyParameters(q, qt.getParameters());
                        
                        q.setMinimumCorrectOptionsToTick(qt.getMinimumCorrectOptionsToTick());
                        if (qt.getAllowMultipleSelection().equals("yes"))
                            q.setAllowMultipleSelection(true);
                        
                        // configurada la pregunta, pasamos a las opciones
                        Container optionsContainer=q.getOptionsContainer();
                        if (optionsContainer==null)
                            throw new Exception("Container for options not configured in Question class "+questionClassName);
                        
                        java.util.List optionList=qt.getOption();
                        for (int k=0;k<optionList.size();k++)
                        {
                            //System.out.println("Opcion "+k);
                            OptionType ot=(OptionType) optionList.get(k);
                            String optionClassName=ot.getClassName();
                            if ((optionClassName==null) || (optionClassName.equals("")))
                                optionClassName=optionsDefaultClassName;
                            
                            Class optionClass=loader.loadClass(optionClassName);
                            TestOption o=(TestOption) optionClass.newInstance();
                            
                            if (optionsCommonParameters!=null)
                                applyParameters(o, optionsCommonParameters);
                            if (ot.getParameters()!=null)
                                applyParameters(o, ot.getParameters());
                            
                            if (ot.getCorrect().equals("yes"))
                                o.setCorrect(true);
                            
                            o.setOwnerQuestion(q);
                            addComponentToContainer(o, optionsContainer, new Integer(k).toString());
                        }
                        
                        // Anadimos la pregunta al contenedor y al vector de preguntas
                        addComponentToContainer(q, groupContainer.getQuestionsContainer(), new Integer(j).toString());
                        questions.add(q);
                    }
                    // Anadimos el grupo de preguntas al vector de elementos que
                    // se anaden al contenedor de preguntas.
                    questionGroups.add(groupContainer);
                }
                
                // Si ya hay un contenedor de preguntas configurado, se le anaden los
                // objetos que se crearon para albergar los grupos de preguntas.
                if (questionsContainer!=null)
                {
                    //System.out.println("A�adiendo "+questionGroups.size()+" elementos al contenedor "+questionsContainer);
                    for (int i=0;i<questionGroups.size();i++)
                        addComponentToContainer((Component) questionGroups.get(i), questionsContainer, new Integer(i).toString());
                }
                
                for (int i=0;i<listeners.size();i++)
                {
                    TestListener c=(TestListener) listeners.get(i);
                    c.testInitialized(new TestEvent(this));
                }
                
                configurationFile=s;
                changes.firePropertyChange("configurationFile",old,s);
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
    }
    
    public Container getQuestionsContainer()
    {
        return questionsContainer;
    }
    
    public void setQuestionsContainer(Container c)
    {
        //System.out.println("Se llama a setQuestionsContainer "+questionsContainer+" "+c);
        if (c!=questionsContainer)
        {
            Container old=questionsContainer;
            questionsContainer=c;
            if (questionGroups!=null)
                for (int i=0;i<questionGroups.size();i++)
                    addComponentToContainer((Component) questionGroups.get(i), questionsContainer, new Integer(i).toString());
            changes.firePropertyChange("questionsContainer", old, c);
        }
    }
    
    private void addComponentToContainer(Component cmp, Container cnt, String constraints)
    {
        cnt.add(cmp,constraints);
/*        if (cnt.getLayout() instanceof CardLayout)
        {
            cnt.add(cmp,constraints);
        }
        else
            cnt.add(cmp);*/
    }
    
    /////////////////////////////////////////////////////////////////////////
    // M�todos de reflexi�n (modificaci�n din�mica de propiedades)
    /////////////////////////////////////////////////////////////////////////
    
    // CAMBIAR ESTO PARA QUE TAMBIEN LOCALICE OBJETOS QUE NO HEREDEN DE
    // COMPONENT, COMO PUEDEN SER BEANS NO VISUALES!!! Hay que utilizar
    // el mecanismo de reflexion: se listan todos los objetos que tiene
    // un objeto dado, se ve si tienen metodo getName... Como solo se puede
    // acceder a objetos publicos, hay que hacer que el Form Editor haga
    // publicas las variables generadas (SETTINGS->FORM EDITOR).
    private Component getByName(Container c, String name)
    {
        for (int i=0;i<c.getComponentCount();i++)
        {
            Component cmp=c.getComponent(i);
            if (name.equals(cmp.getName()))
                return cmp;
            
            if (cmp instanceof Container)
            {
                Component cmp2=getByName((Container) cmp, name);
                if (cmp2!=null)
                    return cmp2;
            }
        }
        return null;
    }
    
    private void applyParameters(Container c, java.util.List params) throws Exception
    {
        for (int i=0;i<params.size();i++)
        {
            ParameterType pt=(ParameterType) params.get(i);
            String name=pt.getComponentName();
            String property=pt.getProperty();
            String value=pt.getValue();
            //System.out.println(value);
            
            String methodName="set"+property.substring(0,1).toUpperCase()+property.substring(1);
            Component cmp=getByName(c, name);
            Method mth=cmp.getClass().getMethod(methodName,new Class[]
            {String.class});
            Object temp[]=new Object[]
            {value};
            mth.invoke(cmp, temp);
        }
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // M�todos de la interfaz TestElement
    ///////////////////////////////////////////////////////////////////////////////
    
    public int getMinimumCorrectAnswers()
    {
        return minimumCorrectAnswers;
    }
    
    public void setMinimumCorrectAnswers(int i)
    {
        int old=minimumCorrectAnswers;
        minimumCorrectAnswers=i;
        changes.firePropertyChange("minimumCorrectAnswers", new Integer(old), new Integer(i));
    }
    
    public void checkAnswers()
    {
        if (isPassed())
        {
            System.out.println("Test superado -> avisando a los listeners "+listeners.size());
            for (int i=0;i<listeners.size();i++)
            {
                TestListener c=(TestListener) listeners.get(i);
                c.testPassed(new TestEvent(this));
            }
        }
        else
        {
            System.out.println("Test fallido -> avisando a los listeners "+listeners.size());
            for (int i=0;i<listeners.size();i++)
            {
                TestListener c=(TestListener) listeners.get(i);
                c.testFailed(new TestEvent(this));
            }
        }
        
        // Por �ltimo, se lanza un evento indicando que se han evaluado las respuestas
        for (int i=0;i<listeners.size();i++)
        {
            TestListener c=(TestListener) listeners.get(i);
            c.testChecked(new TestEvent(this));
        }
    }
    
    public int countCorrectAnswers()
    {
        int correct=0;
        for (int i=0;i<questions.size();i++)
        {
            TestQuestion q=(TestQuestion) questions.get(i);
            if (q.isPassed())
                correct++;
        }
        return correct;
    }
    
    public int countIncorrectAnswers()
    {
        return questions.size()-countCorrectAnswers();
    }
    
    public int countQuestions()
    {
        return questions.size();
    }
    
    public boolean isPassed()
    {
        if (countCorrectAnswers()>=minimumCorrectAnswers)
            return true;
        return false;
    }
    
    public boolean isFailed()
    {
        return !isPassed();
    }
    
    public void addTestListener(TestListener c)
    {
        if (!(listeners.contains(c)))
        {
            System.out.println("A�adido un TestListener");
            listeners.add(c);
        }
    }
    
    public void removeTestListener(TestListener c)
    {
        if (listeners.contains(c))
            listeners.remove(c);
    }
    
    public void reset()
    {
        for (int i=0;i<questions.size();i++)
        {
            TestQuestion q=(TestQuestion) questions.get(i);
            q.reset();
        }
    }
    
    public TestManager getTestManager()
    {
        return testManager;
    }
    
    public void setTestManager(TestManager c)
    {
        TestManager old=testManager;
        
        // si se indica el mismo testManager que estaba configurado, no se hace nada
        if (c==old)
            return;
        
        // si hab�a alguno configurado, nos damos de baja
        if (old!=null)
            old.removeTestElement(this);
        
        // nos registramos en el nuevo ContextManager, pero s�lo si no es null
        if (c!=null)
            c.addTestElement(this);
        
        testManager=c;
        changes.firePropertyChange("testManager",old,c);
    }
    
    public void showCorrection()
    {
        for (int i=0;i<questions.size();i++)
        {
            TestQuestion q=(TestQuestion) questions.get(i);
            q.showCorrection();
        }
    }
}