/* 
* Copyright 2016 Agencia Estatal de Administración Tibutaria
*
* Licencia con arreglo a la EUPL, Versión 1.1 o –en cuanto
* sean aprobadas por la Comisión Europea– versiones
* posteriores de la EUPL (la «Licencia»);
* Solo podrá usarse esta obra si se respeta la Licencia.
* Puede obtenerse una copia de la Licencia en:
*  
* https://joinup.ec.europa.eu/software/page/eupl
*  
* Salvo cuando lo exija la legislación aplicable o se acuerde
* por escrito, el programa distribuido con arreglo a la
* Licencia se distribuye «TAL CUAL», SIN GARANTÍAS NI CONDICIONES
* DE NINGÚN TIPO, ni expresasni implícitas.
*
* Véase la Licencia en el idioma concreto que rige los permisos y 
* limitaciones que establece la Licencia.
*/ 

package monitor;

import java.util.regex.*;
import javax.sound.sampled.*;
import java.io.*;
import java.nio.*;
import java.util.*;

public final class ReproductorVoz {

    boolean dosIdiomas = true;
    Mixer mixer;
    SourceDataLine line;
    byte[] buferTicket = new byte[500000];
    int leidosTicket;
    byte[] buferZona = new byte[500000];
    int leidosZona;
    byte[] silencio = new byte[200000];

    public static String pathVoces = "voz";
    public static String pathVoces2 = "voz2";
    DatosIdioma idioma;
    DatosIdioma idioma1 = new DatosIdioma(1);
    DatosIdioma idioma2 = new DatosIdioma(2);
    public boolean reproduccionEnCurso = false;

    final static int[] correciones = {20, 10, 10, 10, 10, 20, 20, 20, 20, 0};
    //------------------------------   1   2   3   4   5   6   7,  8,  9,  10

    public ReproductorVoz() {
        String rutaRecursos = Configuracion.instanciaUnica().rutaRecursos;
        pathVoces = rutaRecursos + "\\" + pathVoces;
        pathVoces2 = rutaRecursos + "\\" + pathVoces2;
        Mixer.Info[] info = AudioSystem.getMixerInfo();
        mixer = AudioSystem.getMixer(info[0]);
        File voz2 = new File(pathVoces2);
        dosIdiomas = voz2.exists();
        cargaTablaSonidos(dosIdiomas);
        idioma = idioma1;
        idioma.largoPalabraMesa = leeWav("MESA", idioma.buferMesa, 0);
        if (dosIdiomas) {
            idioma = idioma2;
            idioma.largoPalabraMesa = leeWav("MESA", idioma.buferMesa, 0);
        }
    }

    private void leeTicket(String ticket) throws Exception {
        leidosTicket = 0;
        if (ticket.length() == 0) {
            return;
        }
        String[] tp = ticket.split("-");
        if (tp[0].length() == 1) {
            leidosTicket = leeWav(tp[0] + "2", buferTicket, 0);   // Leemos la letra
        } else {
            leidosTicket = leeWav(tp[0].substring(0, 1), buferTicket, 0);   // Leemos la 1ª letra
            String segundaLetra = tp[0].substring(1, 2);
            if (!Character.isDigit(segundaLetra.charAt(0))) {
                segundaLetra += "2";
            }
            leidosTicket += leeWav(segundaLetra, buferTicket, leidosTicket);   // Leemos la 2ª letra
        }
        if (leidosTicket == 0) {
            return;
        }
        int leidosNumero = leeNumero(tp[1], buferTicket, leidosTicket);  // Leemos el numero

        if (leidosNumero == 0) {
            leidosTicket = 0;
            return;
        }
        leidosTicket += leidosNumero;
    }

    private void leeMesa(String mesa) throws Exception {
        idioma.leidosMesa = 0;
        if (mesa.length() == 0) {
            return;
        }
        int leidosNumero = leeNumero(mesa, idioma.buferMesa, idioma.largoPalabraMesa);
        if (leidosNumero > 0) {
            idioma.leidosMesa = idioma.largoPalabraMesa + leidosNumero;
        }
    }

    boolean idZonaNumerico(String id) {
        try {
            int v = Integer.parseInt(id);
            if (v > -4 && v <= 999) {
                return true;
            }
        } catch (Exception exc) {
        }
        return false;
    }

    boolean idZonaTicket(String id) {
        Pattern regexp = Pattern.compile("[A-Z]{1}-[0-9]{1,2}");
        Matcher match = regexp.matcher(id);
        return match.matches();
    }

    private void leeZona(String zona) throws Exception {
        leidosZona = 0;
        zona = zona.trim().toUpperCase();
        if (zona.length() == 0) {
            return;
        }
        String[] pzona = zona.split("[ ]+");
        if (pzona.length != 2) {
            return;
        }
        leidosZona = leeWav(pzona[0], buferZona, 0);
        if (leidosZona == 0) {
            return;
        }
        if (idZonaNumerico(pzona[1])) {  // id zona es un numero
            leidosZona += leeNumero(pzona[1], buferZona, leidosZona);
        } else if (idZonaTicket(pzona[1])) {
            leeTicket(pzona[1]);
            if (leidosTicket > 0) {
                System.arraycopy(buferTicket, 0, buferZona, leidosZona, leidosTicket);
                leidosZona += leidosTicket;
            } else {
                leidosZona = 0;
            }

        } else {
            int leidosId = leeWav(pzona[1], buferZona, leidosZona);
            if (leidosId == 0) {
                leidosZona = 0;
            } else {
                leidosZona += leidosId;
            }
        }
    }

    private void reproduceTicket() throws Exception {
        if (leidosTicket > 0) {
            line.write(buferTicket, 0, leidosTicket);
        }
    }

    private void reproduceMesa() throws Exception {
        if (idioma.leidosMesa > 0) {
            line.write(idioma.buferMesa, 0, idioma.leidosMesa);
        }
    }

    private void reproduceZona() throws Exception {
        if (leidosZona > 0) {
            line.write(buferZona, 0, leidosZona);
        }
    }

    private void reproduceSilencio(int miliSecs) throws Exception {
        float frameRate = idioma.frameRate;
        int duracion = (int) ((float) miliSecs * frameRate);
        line.write(silencio, 0, duracion * 2);
    }

    private int factorCorreccion(char c) {
        return correciones[c - '1'];
    }

    private int leeNumero(String n, byte[] bufer, int pos) {
        int leidos = 0;
        if (n.charAt(0) == '-') {
            return leeWav(n, bufer, pos);
        }
        if (n.length() > 2) {
            if (n.charAt(0) != '0') {
                if (n.charAt(1) == '0' && n.charAt(2) == '0') {
                    return leeWav(n, bufer, pos);
                }
                leidos = leeWav(n.substring(0, 1) + "00Y", bufer, pos);
                if (leidos == 0) {
                    return 0;
                }
                pos += leidos;
            }
            n = n.substring(1);
        }
        if (n.length() > 1 && n.charAt(0) == '0') {
            n = n.substring(1);
        }
        if (n.length() > 1) {
            if (n.charAt(0) != '1' && n.charAt(1) != '0') {
                leidos += lee2Wav(n.substring(0, 1) + "0Y", factorCorreccion(n.charAt(1)), n.substring(1), bufer, pos);
            } else {
                leidos += leeWav(n, bufer, pos);
            }
        } else {
            leidos += leeWav(n, bufer, pos);
        }
        return leidos;
    }

    private void preparaLinea() {
        try {
            line = (SourceDataLine) mixer.getLine(new DataLine.Info(SourceDataLine.class, idioma.aff));
            line.open(idioma.aff);
            line.start();
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }

    private void cierraLinea() {
        try {
            line.drain();
            line.close();
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }

    private int leeWav(String nombreFichero, byte[] bufer, int pos) {
        int leidos = 0;
        try {
            nombreFichero = nombreFichero.toUpperCase();
            byte[] sonido = idioma.tabla.get(nombreFichero);
            leidos = sonido.length;
            System.arraycopy(sonido, 0, bufer, pos, leidos);
        } catch (Exception exc) {
        }
        return leidos;
    }

    private int leeSilencio(int tama, byte[] bufer, int pos) {
        int leidos = 0;
        try {
            byte[] sonido = new byte[tama];
            leidos = sonido.length;
            System.arraycopy(sonido, 0, bufer, pos, leidos);
        } catch (Exception exc) {
        }
        return leidos;
    }

    private int lee2Wav(String fichero1, int miliSegsFinal, String fichero2, byte[] bufer, int pos) {
        try {
            byte[] sonido = idioma.tabla.get(fichero1);
            int bytesNecesarios = sonido.length - ((int) ((float) miliSegsFinal * idioma.frameRate) * 2);
            System.arraycopy(sonido, 0, bufer, pos, bytesNecesarios);
            int leidos = bytesNecesarios;

            // Pequeño morfing de bufers de audio:
            // el primero hace un 'fadeout' que se monta con
            // el segundo que hace un 'fadein' de miliSegsFinal * 2
            ByteBuffer bb = ByteBuffer.wrap(bufer, pos, leidos);
            bb.order(ByteOrder.LITTLE_ENDIAN);
            pos += leidos;
            bb.position(pos - (miliSegsFinal * 2 * (int) idioma.frameRate));
            ShortBuffer sb = bb.asShortBuffer();
            int cuantos = sb.capacity();
            for (int i = 0; i < cuantos; i++) {
                short s = sb.get(i);
                float f = 1f - ((float) i / cuantos);
                if (f < 0.0f) {
                    f = 0f;
                }
                f *= (float) s;
                sb.put(i, (short) f);
            }
            sonido = idioma.tabla.get(fichero2);
            int leidos2 = sonido.length;
            System.arraycopy(sonido, 0, bufer, pos, leidos2);
            bb = ByteBuffer.wrap(bufer, pos - (cuantos * 2), cuantos * 4);
            bb.order(ByteOrder.LITTLE_ENDIAN);
            sb = bb.asShortBuffer();
            for (int i = cuantos; i < cuantos * 2; i++) {
                short s = sb.get(i);
                float f = (float) i / cuantos;
                if (f < 0.0f) {
                    f = 0f;
                }
                f *= (float) s;
                sb.put(i, (short) (f + sb.get(i - cuantos)));
            }
            System.arraycopy(bufer, pos, bufer, pos - (cuantos * 2), leidos2);
            return leidos + leidos2 - (cuantos * 2);

        } catch (Exception exc) {
            System.out.println("Error al leer ficheros de Audio...");
            return 0;
        }
    }

    String nombreSinExtension(File f) {
        String s = f.getName();
        return s.substring(0, s.length() - 4).toUpperCase();
    }

    void cargaTablaSonidos(boolean dosIdiomas) {
        idioma1.tabla = new HashMap<>();
        cargaTablaSonido(pathVoces, idioma1);
        if (dosIdiomas) {
            idioma2.tabla = new HashMap<>();
            cargaTablaSonido(pathVoces2, idioma2);
        }
    }

    void cargaTablaSonido(String pathVoces, DatosIdioma idioma) {
        File dir = new File(pathVoces);
        File[] files = dir.listFiles(new FileFilter() {

            @Override
            public boolean accept(File pathName) {
                return pathName.isFile() && pathName.getName().toLowerCase().endsWith("wav");
            }
        });
        for (File file : files) {
            try {
                String nombre = nombreSinExtension(file);
                idioma.tabla.put(nombre, nuevoBufer(file));
            } catch(Exception exc) {                
            }
        }
        try {
            AudioInputStream ais = AudioSystem.getAudioInputStream(files[0]);
            idioma.aff = ais.getFormat();
            idioma.frameRate = idioma.aff.getFrameRate() / 1000f;
        } catch (Exception exc) {
            exc.printStackTrace();
        }
    }

    byte[] nuevoBufer(File f) {
        try {
            AudioInputStream ais = AudioSystem.getAudioInputStream(f);
            int leidos = ais.read(buferTicket);
            ais.close();
            byte[] nuevo = new byte[leidos];
            System.arraycopy(buferTicket, 0, nuevo, 0, leidos);
            return nuevo;

        } catch (Exception exc) {
            exc.printStackTrace();
            return null;
        }
    }

    public void reproduce(String ticket, String numMesa, String zona) throws Exception {
        Runnable task = () -> {
            try {
                reproduccionEnCurso = true;
                if (dosIdiomas) {
                    reproduce(ticket, numMesa, zona, 1, idioma1);
                    reproduce(ticket, numMesa, zona, 1, idioma2);
                } else {
                    reproduce(ticket, numMesa, zona, 2, idioma1);
                }
                reproduccionEnCurso = false;
            } catch (Exception exc) {
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }

    void reproduce(String ticket, String numMesa, String zona, int repeticiones, DatosIdioma idiomaActual) throws Exception {
        ticket = ticket.toUpperCase();
        idioma = idiomaActual;
        preparaLinea();
        leeZona(zona);
        leeTicket(ticket);
        leeMesa(numMesa);
        int pausaEntreIdiomas;
        if (repeticiones == 1 && idioma.numIdioma == 1) {
            pausaEntreIdiomas = Configuracion.instanciaUnica().pausaEntreIdiomas;
        } else {
            pausaEntreIdiomas = 0;
        }
        while (repeticiones-- > 0) {
            reproduceTicket();
            reproduceMesa();
            reproduceZona();
            if (pausaEntreIdiomas > 0) {
                reproduceSilencio(pausaEntreIdiomas);
            } else {
                reproduceSilencio(repeticiones > 1 ? 500 : 250);
            }
        }
        cierraLinea();
    }

    class DatosIdioma {

        DatosIdioma(int n) {
            numIdioma = n;
        }
        byte[] buferMesa = new byte[400000];
        int leidosMesa;
        HashMap<String, byte[]> tabla;
        AudioFormat aff;
        float frameRate;
        int largoPalabraMesa = 0;
        int numIdioma;
    }
}
