package com.uva.rafael.tfg_goniometer.presenter;

import android.app.Fragment;
import android.content.res.Resources;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;

import com.uva.rafael.tfg_goniometer.R;
import com.uva.rafael.tfg_goniometer.interfaces.PresenterFunctions;
import com.uva.rafael.tfg_goniometer.model.MainModel;
import com.uva.rafael.tfg_goniometer.view.fragments.NuevaMedicionFragment;

import java.util.ArrayList;

/**
 * Este es el Presentador asociado al <tt>Fragment NuevaMedicionFragment</tt> de la aplicación. Se
 * encarga de llevar a cabo toda la lógica asociada a las acciones del usuario realizadas en la IU.
 * <p>
 * <p>En concreto, se encarga de "cargar" los <tt>Spinners</tt> con el array de ítems que le
 * corresponde a a cada uno, obtener toda la información sobre el paciente al que se le va a
 * asignar la medición para enviar esa información a la Vista (<tt>NuevaMedicionFragment</tt>),
 * y que la muestre al usuario.</p>
 * <p>
 * <p>Se encarga, ademas, de gestionar cuando se cargan los <tt>Spinners</tt>, así como las opciones
 * con las que lo hacen en función del ítem seleccionado por el usuario en anteriores
 * <tt>Spinners</tt>.</p>
 * <p>
 * <p>Por último, filtra toda la información introducida por el usuario con respecto a la nueva
 * medición y se la envía al Modelo, con el fin de introducirla finalmente en la Base de Datos.
 * También se encarga de indicar a la Vista que muestre al usuario si se ha producido algún error
 * durante el filtrado o almacenamiento de esta información.</p>
 * <p>
 * <p>Esta clase forma parte de la aplicación TFG-Goniometer, desarrollada para el Trabajo de
 * Fin de Grado - Grado en Ingeniería Informatica (Universidad de Valladolid)</p>
 *
 * @author Rafael Matamoros Luque
 * @version 1.0
 * @see NuevaMedicionFragment
 * @see MainModel
 */
public class NuevaMedicionPresenter implements PresenterFunctions,
        PresenterFunctions.NuevaMedicionFunctions {

    private final MainModel model;
    // Referencias al fragmento (Vista) con el que esta asociado y al Modelo de la aplicación
    private NuevaMedicionFragment fragment;

    /**
     * Constructor principal de la clase
     *
     * @param fragment Fragmento (Vista) con la que mantiene una relación 1-a-1.
     * @param model    Modelo (único) de la aplicación.
     */
    public NuevaMedicionPresenter(Fragment fragment, MainModel model) {
        this.fragment = (NuevaMedicionFragment) fragment;
        this.model = model;
    }

    /**
     * Método que se encarga de enviar a la Vista (<tt>NuevaMedicionFragment</tt>) los nombres
     * de las constantes para obtener los parámetros de tipo <tt>Bundle</tt> recibidos en la
     * creación del <tt>Fragment</tt>.
     */
    @Override
    public void onCreate() {
        // ArrayList a devolver con las constantes necesarias
        ArrayList<String> values = new ArrayList<>();

        values.add(MainModel.NOMBRE_PACIENTE);
        values.add(MainModel.ID_PACIENTE);
        values.add(MainModel.LECTURA_GONIOMETRO);
        values.add(MainModel.DATETIME);

        // Enviar las constantes a la Vista
        fragment.setConstantesBundle(values);
    }

    /**
     * Método que se encarga de "cargar" con contenido cada uno de los <tt>Spinners</tt> inicialmente.
     * <p>
     * Sólo se "cargan" por completo los <tt>Spinners</tt> que indican el lado del cuerpo en el que
     * se hace la medición, y el que indica si el movimiento ha sido realizado única y
     * exclusivamente por el paciente, o ha necesitado ayuda para realizarlo.
     * <p>
     * El resto de <tt>Spinners</tt> se "cargan" con un valor por defecto para que no aparezan
     * vacíos en un principio.
     *
     * @param joint_side    <tt>Spinner</tt> en el que se indica el lado del cuerpo en el que se
     *                      encuentra la articulación sobre la que se ha realizado la medición
     * @param joint         <tt>Spinner</tt> en el que se indica la articulación en concreto que ha
     *                      realizado el movimiento
     * @param movement      <tt>Spinner</tt> en el que se indica el movimiento que ha realizado dicha
     *                      articulación
     * @param movement_type <tt>Spinner</tt> en el que se indica si el movimiento ha sido realizado
     *                      única y exclusivamente por el usuario, o ha necesitado ayuda
     */
    @Override
    public void loadSpinners(Spinner joint_side, Spinner joint, Spinner movement,
                             Spinner movement_type) {
        // joint_side y movement_type se cargan con todos los elementos desde un principio
        loadSpinner(joint_side, R.array.lado);
        loadSpinner(movement_type, R.array.modo);

        // joint y movement se cargan con un único valor por defecto para evitar que queden vacíos
        loadSpinner(joint, R.array.seleccionar_articulacion);
        loadSpinner(movement, R.array.seleccionar_movimiento);
    }

    /*
     * Método que se encarga de crear un ArrayAdapter para el string array recibido como parámetro y
     * adjunta un recurso de tipo layout que define cómo se muestra la opción seleccionada en el
     * control del Spinner.
     *
     * Ademas, especifica el layout que el adapter usara para mostrar la lista de opciones del
     * Spinner.
     *
     * Por último, aplica el adaptador al Spinner en cuestión.
     */
    private void loadSpinner(Spinner spinner, int array) {
        // Create an ArrayAdapter using the string array and a default spinner layout
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(fragment.getActivity(),
                array, R.layout.spinner_item);

        // Specify the layout to use when the list of choices appears
        adapter.setDropDownViewResource(R.layout.spinner_dropdown_item);

        // Apply the adapter to the spinner
        spinner.setAdapter(adapter);
    }

    /**
     * Método que se encarga de obtener la información relativa a un paciente a partir del nombre e
     * ID del mismo, mediante una consulta a la Base de Datos.
     * <p>
     * Posteriormente, envía esta información a la Vista (<tt>NuevaMedicionFragment</tt>) para que
     * se la muestre al usuario.
     *
     * @param nombre            Nombre del paciente
     * @param id                ID del paciente
     * @param fechaHora         Fecha y hora a la que se realizó la medición
     * @param lecturaGoniometro Lectura final del goniómetro (angulo de desplazamiento)
     */
    @Override
    public void getInformacionPaciente(String nombre, int id, String fechaHora,
                                       String lecturaGoniometro) {
        // Obtención de la información del paciente de la BD
        ArrayList<String> informacionPaciente = model.getPacienteBy(nombre, id);

        if (informacionPaciente != null) {
            // Si se dispone de la información, enviarla a la Vista para que la muestre
            fragment.setText(0, nombre);
            fragment.setText(1, informacionPaciente.get(0));
            fragment.setText(2, informacionPaciente.get(1));
            fragment.setText(3, String.valueOf(id));
            fragment.setText(4, informacionPaciente.get(2));
            fragment.setText(5, informacionPaciente.get(3));
            fragment.setText(6, informacionPaciente.get(4));
        }

        fragment.setText(7, fechaHora);
        fragment.setText(8, lecturaGoniometro);
    }

    /**
     * Evento <tt>ItemSelectedListener</tt> que se llama cuando el usuario ha pulsado sobre alguno
     * de los elementos de uno de los <tt>Spinner</tt> en <tt>NuevaMedicionFragment</tt>.
     * <p>
     * <p>Se encarga de, en función de cual de los 4 <tt>Spinner</tt> es el que recibe el evento,
     * "cargar" el siguiente <tt>Spinner</tt> con un determinado <tt>Array</tt> en función del ítem
     * que se haya seleccionado sobre el primero para, así, limitar la articulación y el movimiento
     * que puede introducir el usuario, evitando, de esta manera, introducir valores absurdos en
     * articulaciones que no pueden realizar un determinado movimiento.
     * <p>
     * Ademas, se encarga de mostrar u ocultar el <tt>LinearLayout</tt> que contiene el
     * <tt>EditText</tt> en el que el usuario puede escribir, en caso de haber seleccionado en alguno
     * de los <tt>Spinner</tt> la opción "Otro/a".</p>
     *
     * @param spinner1 <tt>Spinner</tt> que recibe el evento <tt>ItemSelected</tt>
     * @param spinner2 <tt>Spinner</tt> que, opcionalmente, se "cargara" en función del valor
     *                 seleccionado en el primer <tt>Spinner</tt>
     */
    @Override
    public void onItemSelectedListener(final Spinner spinner1, final Spinner spinner2) {
        // Variable empleada para recuperar Strings del fichero res/values/strings.xml
        final Resources resources = fragment.getResources();

        // Selección múltiple en función del Spinner que recibe el evento
        switch (spinner1.getId()) {

            case R.id.spinner_joint_side:
                /*
                 * El Spinner que contiene el lado en el que se encuentra la articulación ha
                 * recibido el evento
                 */
                spinner1.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
                    @Override
                    public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                        // Se obtiene el texto del elemento seleccionado
                        String lado = spinner1.getSelectedItem().toString();

                        /*
                         * En función de si se ha seleccionado el lado derecho/izquierdo, o la
                         * columna vertebral, el Spinner que contiene las articulaciones, se
                         * carga con unos elementos u otros
                         */
                        if (lado.equals(resources.getString(R.string.lado_izq)) ||
                                (lado.equals(resources.getString((R.string.lado_dcho)))))
                            loadSpinner(spinner2, R.array.articulacion_izq_dcha);
                        else if (lado.equals(resources.getString(R.string.columna)))
                            loadSpinner(spinner2, R.array.articulacion_columna);

                        // El Layout que contiene el Spinner con las articulaciones se hace visible
                        fragment.setLayoutVisibility(0, View.VISIBLE);
                    }

                    @Override
                    public void onNothingSelected(AdapterView<?> adapterView) {
                        // No se implementa esta funcionalidad
                    }
                });
                break;

            case R.id.spinner_joint:
                // El Spinner que contiene la articulación en sí ha recibido el evento
                spinner1.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
                    @Override
                    public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                        // Se obtiene el texto del elemento seleccionado
                        String articulacion = spinner1.getSelectedItem().toString();

                        /*
                         * En función de la articulación que se haya seleccionado, se "carga" el
                         * Spinner que contiene los movimientos de las articulaciones con el
                         * array correspondiente a esa articulación.
                         *
                         * Ademas, se mostrara u ocultara el Layout que contiene el EditText
                         * donde el usuario puede escribir la articulación, si no viene en la
                         * lista predefinida, en función de si se selecciona (o no) "Otra" como
                         * articulación
                         */
                        if (articulacion.equals(
                                resources.getString(R.string.hombro))) {
                            fragment.setLayoutVisibility(1, View.GONE);

                            loadSpinner(spinner2, R.array.movimientos_hombro);
                        } else if (articulacion.equals(
                                resources.getString(R.string.codo))) {
                            fragment.setLayoutVisibility(1, View.GONE);

                            loadSpinner(spinner2, R.array.movimientos_codo);
                        } else if (articulacion.equals(
                                resources.getString(R.string.muñeca))) {
                            fragment.setLayoutVisibility(1, View.GONE);

                            loadSpinner(spinner2, R.array.movimientos_muñeca);
                        } else if (articulacion.equals(
                                resources.getString(R.string.cadera))) {
                            fragment.setLayoutVisibility(1, View.GONE);

                            loadSpinner(spinner2, R.array.movimientos_cadera);
                        } else if (articulacion.equals(
                                resources.getString(R.string.rodilla))) {
                            fragment.setLayoutVisibility(1, View.GONE);

                            loadSpinner(spinner2, R.array.movimientos_rodilla);
                        } else if (articulacion.equals(
                                resources.getString(R.string.tobillo))) {
                            fragment.setLayoutVisibility(1, View.GONE);

                            loadSpinner(spinner2, R.array.movimientos_tobillo);
                        } else if (articulacion.equals(
                                resources.getString(R.string.otra))) {
                            fragment.setLayoutVisibility(1, View.VISIBLE);

                            loadSpinner(spinner2, R.array.movimientos_otra);
                        } else if (articulacion.equals(
                                resources.getString(R.string.cervical))
                                || articulacion.equals(
                                resources.getString(
                                        R.string.toracica))
                                || articulacion.equals(
                                resources.getString(
                                        R.string.lumbar))) {
                            fragment.setLayoutVisibility(1, View.GONE);

                            loadSpinner(spinner2,
                                    R.array.movimientos_columna);
                        }
                    }

                    @Override
                    public void onNothingSelected(AdapterView<?> adapterView) {
                        // No se implementa esta funcionalidad
                    }
                });
                break;

            case R.id.spinner_movement:
                // El Spinner que contiene el movimiento de la articulación ha recibido el evento
                spinner1.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
                    @Override
                    public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                        // Se obtiene el texto del elemento seleccionado
                        String movimiento = spinner1.getSelectedItem().toString();

                        /*
                         * En este caso, sólo es necesario tener en cuenta el elemento seleccionado
                         * en este Spinner para mostrar o no el EditText donde el usuario puede
                         * introducir un movimiento que no venga predefinido en el Spinner.
                         */
                        if (movimiento.equals(resources.getString(R.string.otro)))
                            fragment.setLayoutVisibility(2, View.VISIBLE);
                        else
                            fragment.setLayoutVisibility(2, View.GONE);
                    }

                    @Override
                    public void onNothingSelected(AdapterView<?> adapterView) {
                        // No se implementa esta funcionalidad
                    }
                });
                break;

            case R.id.spinner_movement_type:
                /*
                 * El Spinner que contiene el tipo de movimiento de la articulación ha recibido el
                 * evento
                 */
                spinner1.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
                    @Override
                    public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
                        // Se obtiene el texto del elemento seleccionado
                        String tipoMovimiento = spinner1.getSelectedItem().toString();

                        /*
                         * En este caso, sólo es necesario tener en cuenta el elemento seleccionado
                         * en este Spinner para mostrar o no el EditText donde el usuario puede
                         * introducir un tipo de movimiento que no venga predefinido en el Spinner.
                         */
                        if (tipoMovimiento.equals(resources.getString(R.string.otro)))
                            fragment.setLayoutVisibility(3, View.VISIBLE);
                        else
                            fragment.setLayoutVisibility(3, View.GONE);
                    }

                    @Override
                    public void onNothingSelected(AdapterView<?> adapterView) {
                        // No se implementa esta funcionalidad
                    }
                });
                break;
        }
    }

    /**
     * Evento <tt>ClickListener</tt> que se llama cuando el usuario ha pulsado sobre el botón
     * "HECHO" en <tt>NuevaMedicionFragment</tt>.
     * <p>
     * <p>Se encarga de:
     * <p>
     * 1.- Verificar que el usuario ha introducido toda la información obligatoria (marcada con *).
     * 2.- Enviar la información al Modelo para que se introduzca en la Base de Datos.
     * 3.- Solicitar a la Vista (<tt>NuevaMedicionFragment</tt>) que muestre un mensaje dependiendo
     * de si la inserción en la Base de Datos ha sido exitosa o no.
     * 4.- Si la inserción se realizó con éxito, borrar el <tt>Fragment</tt> actual y mostrar el
     * perfil del paciente actualizado con la nueva medición ya insertada.</p>
     *
     * @param nombre              Nombre del paciente
     * @param id                  ID del paciente
     * @param fecha_hora          Fecha y hora de la medición
     * @param joint_side          Lado en el que se encuentra la articulación (o columna vertebral)
     * @param joint               Articulación sobre la que se ha realizado la medición
     * @param joint_other         Articulación (en caso de haber seleccionado la opción "Otra" en el
     *                            <tt>Spinner</tt> joint
     * @param movement            Movimiento que ha realizado la articulación
     * @param movement_other      Movimiento en caso de haber seleccionado la opción "Otro" en el
     *                            <tt>Spinner</tt> movement
     * @param movement_type       Modo en el que se ha realizado el movimiento (con/sin ayuda)
     * @param movement_type_other Modo en caso de haber seleccionado la opción "Otro" en el
     *                            <tt>Spinner</tt> movement_type
     * @param lectura_goniometro  Lectura final del goniómetro (angulo de desplazamiento)
     */
    @Override
    public void onDoneClicked(String nombre, int id, String fecha_hora, String joint_side,
                              String joint, String joint_other, String movement,
                              String movement_other, String movement_type,
                              String movement_type_other, String lectura_goniometro) {

        // Variable empleada para recuperar Strings del fichero res/values/strings.xml
        Resources resources = fragment.getResources();

        /*
         * Variables locales donde almacenar la información introducida por el usuario después de
         * filtrarla y posteriormente insertarla en la BD
         */
        String nombrePaciente, fechaHora, lado, articulacion, movimiento, tipoMovimiento;
        double lecturaGoniometro;
        int idPaciente;

        // Filtrado de la información recibida por parámetros
        nombrePaciente = nombre;
        fechaHora = fecha_hora;
        idPaciente = id;
        /*
         * Si no se ha seleccionado ningun valor del Spinner, asignar null a la variable local,
         * para devolver un error al usuario
         */
        lado = (!joint_side.equals(fragment.getText(R.string.seleccionar_lado))) ? joint_side : null;
        articulacion = (!joint.equals(fragment.getText(R.string.seleccionar_articulacion))) ? joint : null;
        movimiento = (!movement.equals(fragment.getText(R.string.seleccionar_movimiento))) ? movement : null;
        tipoMovimiento = (!movement_type.equals(fragment.getText(R.string.seleccionar_tipo_movimiento))) ? movement_type : null;
        lecturaGoniometro = Double.parseDouble(lectura_goniometro);

        // Comprobación de que se ha introducido un valor para el lado de la articulación
        if (lado == null) {
            fragment.displayErrorUser();
            return;
        }

        // Comprobación de que se ha introducido un valor para la articulación
        if (articulacion != null) {
            // Comprobar si el usuario ha seleccionado la opción "Otra" en "Articulacion"
            if (articulacion.matches(resources.getString(R.string.other)))
                /*
                 * Comprobar que el usuario ha rellenado el cuadro de texto que se muestra después
                 * de haber seleccionado la opción "Otra" en articulación
                 */
                if (joint_other.trim().equals("")) {
                    fragment.displayErrorUser();
                    return;
                }
        } else {
            // No se ha seleccionado ningún valor para la articulación
            fragment.displayErrorUser();
            return;
        }

        // Comprobación de que se ha introducido un valor para el movimiento de la articulación
        if (movimiento != null) {
            // Comprobar si el usuario ha seleccionado la opción "Otro" en "Movimiento"
            if (movimiento.matches(resources.getString(R.string.other)))
                /*
                 * Comprobar que el usuario ha rellenado el cuadro de texto que se muestra después
                 * de haber seleccionado la opción "Otro" en movimiento
                 */
                if (movement_other.trim().equals("")) {
                    fragment.displayErrorUser();
                    return;
                }
        } else {
            // No se ha seleccionado ningún valor para el movimiento
            fragment.displayErrorUser();
            return;
        }

        /*
         * Comprobación de que se ha introducido un valor para el modo de movimiento de la
         * articulación
         */
        if (tipoMovimiento != null) {
            // Comprobar si el usuario ha seleccionado la opción "Otro" en "Modo"
            if (tipoMovimiento.matches(resources.getString(R.string.other)))
                /*
                 * Comprobar que el usuario ha rellenado el cuadro de texto que se muestra después
                 * de haber seleccionado la opción "Otro" en tipo_movimiento
                 */
                if (movement_type_other.trim().equals("")) {
                    fragment.displayErrorUser();
                    return;
                }
        } else {
            // No se ha seleccionado ningún valor para el tipo de movimiento
            fragment.displayErrorUser();
            return;
        }

        // Se crea un nuevo ArrayList con la información de la medición
        ArrayList<String> medicion = new ArrayList<>();
        medicion.add(0, fechaHora); // Fecha y hora de la medición
        medicion.add(1, String.valueOf(lecturaGoniometro)); // Lectura del goniómetro
        medicion.add(2, lado); // Lado de la articulación
        medicion.add(3, articulacion); // Articulación
        medicion.add(4, movimiento); // Movimiento de la articulación
        medicion.add(5, tipoMovimiento); // Modo de movimiento de la articulación
        medicion.add(6, nombrePaciente); // Nombre del paciente
        medicion.add(7, String.valueOf(idPaciente)); // ID del paciente
        medicion.add(8, joint_other); // EditText de "Otra" articulación
        medicion.add(9, movement_other); // EditText de "Otro" movimiento
        medicion.add(10, movement_type_other); // EditText de "Otro" modo de movimiento

        // Se envía la información al Modelo para insertar la medición en la BD
        long resultado = model.storeMeasurement(medicion);

        if (resultado > 0) {
            /*
             * La medición se ha podido insertar con éxito. Notificar a la vista que muestre un
             * mensaje indicando esta situación al usuario
             */
            fragment.displaySuccess();

            /*
             * Ya no es necesario que se sigan mostrando las opciones de almacenar una medición
             * en el perfil del paciente
             */
            model.setMostrarOpcionesAlmacenarMedicion(false);
            // Ya se puede mostrar el botón para borrar el perfil de un paciente
            model.setMostrarOpcionesBorrarPaciente(true);

            /*
             * Recuperar el Fragment que se encontraba en primera posición en el BackStack con el
             * perfil del paciente actualizado (incluyendo esta nueva medición)
             */
            fragment.getFragmentManager().popBackStack();

            // Borrar el Fragment actual
            fragment.getFragmentManager()
                    .beginTransaction()
                    .remove(fragment)
                    .commit();
        } else
            /*
             * Se ha producido un error al introducir la medición en la BD. Notificar a la Vista
             * para que informe de esta situación al usuario
             */
            fragment.displayErrorDatabase();
    }

    /**
     * Perform any final cleanup before an activity is destroyed.
     * <p>
     * Se encarga de liberar la referencia al <tt>Fragment</tt> con el que esta asociado.
     */
    @Override
    public void onDestroy() {
        fragment = null;
    }
}
