package com.uvigo.mhp;

import com.uvigo.mhp.ui.*;
import java.awt.*;
import java.util.*;

// La clase TestQuestion da soporte a preguntas tipo Test, construidas a partir de un
// n�mero indefinido de objetos Option. Permite selecciones m�ltiples y preguntas
// con varias opciones correctas.
// Las opciones pueden ser objetos cualesquiera, con tal de que exporten un HSwitch
// para identificar si est�n o no seleccionadas.

// Una clase de objetos extendiendo Question define el marco en que se plantean las
// opciones. Puede incluir un objeto con el enunciado de la pregunta, definir caracter�sticas
// del fondo o el layout de las opciones...
// Para que la clase Question funcione tanto si se crean preguntas desde un fichero XML
// como si se crean ad-hoc en el entorno de programaci�n, las opciones deben a�adirse
// siempre al contenedor designado al efecto.

public class TestQuestion extends HPanel implements TestElement
{
    private Container c; // el contenedor donde se muestran las opciones
    private Vector options;
    private Vector listeners;
    private TestManager testManager;
    
    private boolean allowMultipleSelection;
    // Indica si se permite seleccionar varias opciones. Si no se permite, hay varias
    // opciones correctas y hay que marcar varias, es un error que no se comprueba, dado
    // que es cosa de quien programa los tests.
    
    private int minimumCorrectOptionsToTick;
    // En caso de que haya varias respuestas correctas, indica cuantas de ellas se deben marcar;
    // si no se indica ningun valor, se asume que hay que marcar una.
    
    public TestQuestion()
    {
        allowMultipleSelection=false;
        minimumCorrectOptionsToTick=1;
        listeners=new Vector();
        options=new Vector();
        c=this;
    }
    
    public Container getOptionsContainer()
    {
        return c;
    }
    
    public void setOptionsContainer(Container c)
    {
        // hay que mover las preguntas desde el contenedor actual hasta el nuevo!!!
        
        Container old=this.c;
        this.c=c;
        firePropertyChange("optionsContainer", old, c);
    }
    
    public boolean getAllowMultipleSelection()
    {
        return allowMultipleSelection;
    }
    
    public void setAllowMultipleSelection(boolean b)
    {
        boolean old=allowMultipleSelection;
        allowMultipleSelection=b;
        firePropertyChange("allowMultipleSelection", new Boolean(old), new Boolean(b));
    }
    
    public int getMinimumCorrectOptionsToTick()
    {
        return minimumCorrectOptionsToTick;
    }
    
    public void setMinimumCorrectOptionsToTick(int i)
    {
        int old=minimumCorrectOptionsToTick;
        minimumCorrectOptionsToTick=i;
        firePropertyChange("minimumCorrectOptionsToTick", new Integer(old), new Integer(i));
    }
    
    // Cuando se marca una opcion, si no esta permitida la seleccion multiple, se
    // desmarcan las restantes.
    public void tick(TestOption opt)
    {
        lookForOptions(); // actualiza el vector de opciones, por si acaso (puede venirse por aqui
        // sin haberse inicializado completamente la pregunta con todas sus opciones)
        
        int correct=howManyOptionsAreCorrectAnswers();
        if (allowMultipleSelection)
            return;
        
        for (int i=0;i<options.size();i++)
        {
            TestOption tmp=(TestOption) options.get(i);
            if (tmp==opt) continue;
            tmp.getAnswerSwitch().setSwitchableState(false);
        }
    }
    
    private void lookForOptions()
    {
        for (int i=0;i<c.getComponentCount();i++)
        {
            Component comp=c.getComponent(i);
            if (comp instanceof TestOption)
                if (!(options.contains(comp)))
                    options.add(comp);
        }
    }
    
    private int howManyOptionsAreCorrectAnswers()
    {
        int correct=0;
        for (int i=0;i<options.size();i++)
        {
            TestOption opt=(TestOption) options.get(i);
            if (opt.isCorrect())
                correct++;
        }
        
        return correct;
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // Metodos de la interfaz TestElement
    ///////////////////////////////////////////////////////////////////////////////
    
    public int countCorrectAnswers()
    {
        return isPassed() ? 1 : 0;
    }
    
    public int countIncorrectAnswers()
    {
        return isPassed() ? 0 : 1;
    }
    
    public int countQuestions()
    {
        return 1;
    }
    
    public int getMinimumCorrectAnswers()
    {
        return 1;
    }
    
    public boolean isPassed()
    {
        lookForOptions();
        int correctAndTicked=0;
        for (int i=0;i<options.size();i++)
        {
            TestOption opt=(TestOption) options.get(i);
            boolean state=opt.getAnswerSwitch().getSwitchableState();
            if ((state) && (!opt.isCorrect()))
                return false; // si se marca una opcion incorrecta, la pregunta esta mal
            
            // la opcion es una respuesta correcta; si esta marcada, se suma a correctAndTicked
            if (state)
                correctAndTicked++;
        }
        
        // si se han marcado suficientes respuestas correctas, la pregunta esta bien
        return (correctAndTicked>=minimumCorrectOptionsToTick) ? true : false;
    }
    
    public boolean isFailed()
    {
        return !isPassed();
    }
    
    public void addTestListener(TestListener c)
    {
        if (!(listeners.contains(c)))
            listeners.add(c);
    }
    
    public void removeTestListener(TestListener c)
    {
        if (listeners.contains(c))
            listeners.remove(c);
    }
    
    public void reset()
    {
        lookForOptions();
        for (int i=0;i<options.size();i++)
        {
            TestOption opt=(TestOption) options.get(i);
            opt.getAnswerSwitch().setSwitchableState(false);
        }
    }
    
    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 habia 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;
        firePropertyChange("testManager",old,c);
    }
    
    public void showCorrection()
    {
        // seria conveniente mostrar, ademas de las respuestas correctas, las que estaban marcadas
        lookForOptions();
        for (int i=0;i<options.size();i++)
        {
            TestOption opt=(TestOption) options.get(i);
            opt.getAnswerSwitch().setSwitchableState(opt.isCorrect());
        }
    }
    
    public void checkAnswers()
    {
        if (isPassed())
            for (int i=0;i<listeners.size();i++)
            {
                TestListener c=(TestListener) listeners.get(i);
                c.testPassed(new TestEvent(this));
            }
        else
            for (int i=0;i<listeners.size();i++)
            {
                TestListener c=(TestListener) listeners.get(i);
                c.testFailed(new TestEvent(this));
            }
        
        for (int i=0;i<listeners.size();i++)
        {
            TestListener c=(TestListener) listeners.get(i);
            c.testChecked(new TestEvent(this));
        }
        
    }
}