// MorseSpeaker, by Seb Luca, UNamur, June 2018

package com.projet.sluca.morsespeaker;

import android.content.Context;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.support.v7.app.AppCompatActivity;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.util.HashMap;
import java.util.Map;

public class TranslateActivity extends AppCompatActivity
{
    public Map<Character, String> dictionnary = dictionnary();

    public String phrase;      // String qui retiendra la phrase entrée.
    public String phraseMorse; // String qui retiendra la phrase morse obtenue.
    public int phraseLengh;    // Entier qui retiendra la longueur de la phrase.
    public int numCellCara;    // Entier qui retiendra quel caractère de la phrase est en cours d'affichage.
    public String codeMorse;   // String qui retiendra le signe traité dans un caractère morse.
    public int numCellCode;    // Entier qui retiendra quel signe est traité dans un caractère morse.
    public int codeLengh;      // Entier qui retiendra la longueur d'un caractère morse traité.
    public boolean playing;    // Boléen signifiant si le Player est ou non en cours de lecture.
    public boolean working;    // Boléen signifiant si un sous-traitement du Player est en cours.

    private MediaPlayer sound = new MediaPlayer(); // Instanciation d'un lecteur audio.

    GlobalDatas globalDatas;   // Appel de la classe traitant les variables globales.

    private Button buttonBack;
    private Button buttonNew;
    private Button buttonPlay;
    private Context context;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        // Appel du layout.
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_translate);
        context = this;

        // Identification des boutons.
        buttonBack = findViewById(R.id.btn_back);
        buttonNew = findViewById(R.id.btn_new);
        buttonPlay = findViewById(R.id.btn_play);

        buttons(); // Appel de la gestion des boutons.

        // Etablissement de la liaison avec les variables globales.
        globalDatas = (GlobalDatas) getApplication();
        phrase    = globalDatas.getphrase(); // Récupération de la phrase.

        // Affichage de la phrase en alphabet latin.
        TextView latin = (TextView) findViewById(R.id.phrase);
        latin.setMovementMethod(new ScrollingMovementMethod()); // Activation du défilement vertical.
        latin.setText("\"" + phrase + "\"");

        refresh(" ", " "); // Vidage des indicateurs de caractère (les deux petits cadres, en bas à gauche).


        // ========[ CONVERSION LATIN -> MORSE ]========

        char[] phraseTab = phrase.toCharArray(); // Mise en tableau de la phrase latine.
        int length = 0;     // Entier qui retiendra la longueur de la phrase.
        int lineLength = 0; // Entier qui retiendra la longueur de la ligne.

        for(char letter : phraseTab) // Pour chaque lettre de la phrase latine ...
        {
            if(letter != ' ') // -> Si est un caractère alpha numérique :
            {
                codeMorse = convertor(letter);                    // Appel de "convertor()" pour obtenir le caractère foreaché en morse.
                phraseMorse = phraseMorse + codeMorse + " , ";    // Ajout du code obtenu dans la phrase en morse, + un espacement et une virgule.

                for(char sign : codeMorse.toCharArray())          // Pour chaque point/trait du code, incrémenter la longueur de la ligne.
                {
                         if(sign == '▪') lineLength += 1;        // +1 pour un point
                    else if(sign == '▬') lineLength += 2;        // +2 pour une barre
                }

                if(lineLength > 20)                               // Si la ligne a une longueur actuelle de plus de 20 unités :
                {
                    phraseMorse = phraseMorse + "\n";             // Ajouter un retour à la ligne dans la phrase en morse.
                    lineLength = 0;                               // Réinitialiser la longueur de la ligne.
                }                                                 // NB : Ceci pour éviter qu'un code morse soit scindé par un retour-ligne.
            }
            else // -> Si est un espace, pour marquer la fin d'un mot :
            {
                phraseMorse = phraseMorse.substring(0, phraseMorse.length() - 3); // Supression de la dernière virgule dans la phrase en morse.
                phraseMorse = phraseMorse + "\n";                                 // Remplacement par un retour-ligne.
                lineLength = 0;                                                   // En conséquence : réinitialiser la longueur de la ligne.
            }

            length++; // Incrémentation de la longueur de la phrase.

            // Lorsque la phrase sera complète, Supression de la dernière virgule et des espacements qui l'encadrent.
            if(length == phraseTab.length) phraseMorse = phraseMorse.substring(0, phraseMorse.length() - 3);

            // Nettoyage de la mention "null" qui s'affiche inopinément en début de string.
            phraseMorse = phraseMorse.replace("null","");

            // Affichage de la phrase en alphabet morse :
            TextView coded = (TextView) findViewById(R.id.morse);
            coded.setMovementMethod(new ScrollingMovementMethod()); // Activation du défilement vertical.
            coded.setText(phraseMorse);
        }

        // =============================================
    }


    public Map<Character, String> dictionnary() // ====> Renvoie un tableau contenant le dictionnaire morse <====
    {
        Map<Character, String> grid = new HashMap<Character, String>();

        grid.put('A',"▪▬"); grid.put('B',"▬▪▪▪"); grid.put('C',"▬▪▬▪"); grid.put('D',"▬▪▪"); grid.put('E',"▪");
        grid.put('F',"▪▪▬▪"); grid.put('G',"▬▬▪"); grid.put('H',"▪▪▪▪"); grid.put('I',"▪▪"); grid.put('J',"▪▬▬▬");
        grid.put('K',"▬▪▬"); grid.put('L',"▪▬▪▪"); grid.put('M',"▬▬"); grid.put('N',"▬▪"); grid.put('O',"▬▬▬");
        grid.put('P',"▪▬▬▪"); grid.put('Q',"▬▬▪▬"); grid.put('R',"▪▬▪"); grid.put('S',"▪▪▪"); grid.put('T',"▬");
        grid.put('U',"▪▪▬"); grid.put('V',"▪▪▪▬"); grid.put('W',"▪▬▬"); grid.put('X',"▬▪▪▬"); grid.put('Y',"▬▪▬▬");
        grid.put('Z',"▬▬▪▪");

        grid.put('1',"▪▬▬▬▬"); grid.put('2',"▪▪▬▬▬"); grid.put('3',"▪▪▪▬▬"); grid.put('4',"▪▪▪▪▬"); grid.put('5',"▪▪▪▪▪");
        grid.put('6',"▬▪▪▪▪"); grid.put('7',"▬▬▪▪▪"); grid.put('8',"▬▬▬▪▪"); grid.put('9',"▬▬▬▬▪"); grid.put('0',"▬▬▬▬▬");

        return grid;
    }


    public String convertor(char character) // ====> Convertit un caractère latin entré en son équivalent morse <====
    {
        String result = ""; // String qui retiendra le résultat.

        // Pour chaque case du tableau créé par "dictionnary()" ...
        for (Map.Entry<Character, String> entry : dictionnary.entrySet())
        {
            Character key = entry.getKey();   // récupération de la clef
            String  value = entry.getValue(); // récupération de la valeur

            // Si le caractère en paramètre correspond à la clef, renvoyer la valeur correspondante.
            if(character == key) result = value;
        }
        return result;
    }


    public void buttons() // ====> Gestion des deux boutons du haut <====
    {
        // --> Gestion bouton "BACK".

        buttonBack.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                if(!playing) finish(); // Si le Player n'est pas en action : fin de cette activity, donc retour à la précédente.
            }
        });

        // --> Gestion bouton "NEW PHRASE".

        buttonNew.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                if(!playing) // Si le Player n'est pas en action :
                {
                    // Réinitialisation de l'appli et retour à la "MainActivity".
                    Intent i = getBaseContext().getPackageManager().getLaunchIntentForPackage( getBaseContext().getPackageName() );
                    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                    startActivity(i);
                }
            }
        });
    }


    public void buttonPlay(View view) // ====> Gestion du  bouton PLAY (NB : bouton lié à l'activity via le layout par "onClick") <====
    {
        if(!playing) // --> Lancement d'une lecture (si player incatif, = clic sur "PLAY") :
        {
            String partition = phrase.replace(' ','#'); // Ciblage des espacements.

            phraseLengh = phrase.length(); // Retenir la longueur de la phrase pour le traitement à venir.
            numCellCara = 0;               // Déclarer à 0 le n° du caractère de la phrase en cours de traitement.

            playing = true;                // Déclarer une activité en cours pour le Player.

            playing(partition);            // Lancement du traitement sur la string "partition" via la fonction "playing()".
        }
        else // --> Interruption de la lecture (= clic sur "STOP") :
        {
            refresh(" ", " ");  // Vidage des indicateurs de caractère.
            playing = false;               // Déclarer l'arrêt du Player.
        }
        buttonState();                     // Mise à jour de l'apparence des boutons, dans tous les cas.
    }


    public void playing(String partition) // ====> Centre de contrôle du Player, en lien avec différentes méthodes plus bas <====
    {
        // La phrase est parcourue, et chaque caractère est analysé. Il est affiché, en latin et en morse, dans les indicateurs de caractères,
        // dans la partie gauche du Player. Les bips, tout comme les silences relatifs aux espacements et les latences laissées entre chaque
        // caractères, seront joués. Les méthodes compétentes boucleront jusqu'à finir de traiter la chaîne qui leur incombent. D'où l'utilisation
        // des booléens "playing" et "working".

        if(playing) // [BOUCLE] : processus reproduit à chaque appel de la fonction, lorsque "working" est playing.
        {
            if(phraseLengh > 0) // --> Si le Player est actif et que la phrase n'a pas encore été entièrement lue ...
            {
                // Traitement des caractères :

                final String phrase = partition;         // Récupération du paramètre pour une utilisation ultérieure plus large.
                char ch = phrase.charAt(numCellCara);    // Caractère latin qui retiendra celui est actuellement traité.
                long time = 0;                           // Variable qui retiendra la longueur d'une attente.

                String letter = " ";                     // Retiendra plus loin la lettre latine en cours de traitement, sera un espacement sinon.

                if(ch == '#') // -> Si le caractère est un espace :
                {
                    refresh(" ", " "); // Vidage des indicateurs de caractère.
                    time = 200;                   // Latence de 200 ms.

                    // Timer réalisant une attente d'arrière-plan.
                    Handler handler = new Handler();
                    handler.postDelayed(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            latence(); // Le temps écoulé, appeler "latence()".
                        }
                    }, time); // Variable "time".
                }
                else // -> Sinon, le caractère est alphanumérique :
                {
                    letter = String.valueOf(ch); // Lettre latine en cours mise en string.
                    codeMorse = convertor(ch);   // Son équivalent en morse (récupéré par la méthode "convertor()").

                    refresh(letter, codeMorse);  // Mise à jour des indicateurs de caractère (affichage).

                    codeLengh = codeMorse.length();  // Mémorisation de la longueur du code morse pour le caractère traité.
                    numCellCode = 0;                 // Spécifier qu'on commencera par le premier caractère de la chaîne "code".

                    working = false;                // Spécifier qu'aucun travail n'est en cours dans "looping()" ...
                    looping();                      // ... et appeler ladite fonction en y passant le caractère morse en cours de traitement.
                }
            }
            else // --> Sinon, lorsque la phrase a été entièrement traitée, stopper l'activité du Player ...
            {
                refresh(" ", " "); // Vidage des indicateurs de caractère.
                playing = false;              // Déclarer l'arrêt du Player pour faire cesser la boucle.
                buttonState();                // Mise à jour de l'apparence des boutons.
            }
        }
    }


    public void looping() // ====> Décortique un caractère morse pour faire biper et attendre selon chaque signe qui le compose <====
    {
        int time = 0; // Variable qui retiendra la longueur d'une attente.

        if(numCellCode < codeLengh)  // --> Pour chaque signe du caractère morse ...
        {
            char bit = codeMorse.charAt(numCellCode); // Récupération du signe de la chaîne "codeMorse" en cours de traitement.

            if(!working && playing) // [BOUCLE] : processus reproduit à chaque appel de la fonction, lorsque "working" est faux et "playing" vrai.
            {
                working = true; // Ceci permet de boucler la fonction sans que ses actions ne se répètent de façon incontrôlée.

                if(bit == '▪')        // SI POINT :
                {
                    bipping(1); // Appel d'un bip court.
                    time = 300;       // Appel d'une pause courte.
                }
                else if(bit == '▬')   // SI TRAIT :
                {
                    bipping(2); // Appel d'un bip long.
                    time = 600;       // Appel d'une pause longue.
                }

                // Timer réalisant une attente d'arrière-plan.
                Handler handler2 = new Handler();
                handler2.postDelayed(new Runnable()
                {
                    @Override
                    public void run() // Le temps écoulé, relancer la boucle.
                    {
                        sound.release(); // Arrêt du lecteur de son.
                        numCellCode++;   // Ceci fera passer au signe suivant.
                        working = false; // Rouvrir la boucle.
                        looping();       // Relancer la boucle.
                    }
                }, time); // Variable "time".
            }
        }
        else latence(); // -> Sinon, c'est que le caractère a fini d'être traité. Appel d'une latence.
    }


    public void reboot() // ====> Signifie la relance de la méthode "playing()" à la fin du traitement d'un caractère <====
    {
        numCellCara++;    // Ceci fera passer au caractère suivant.
        phraseLengh--;    // Ceci établi l'avancement du traitement dans la phrase traitée.

        playing(phrase);  // Reprise du traitement.
    }


    public void latence() // ====> Etablit un court temps d'arrêt entre l'affichage de chaque caractère ou se cumule avec celui d'un espacement <====
    {
        // Timer réalisant une attente d'arrière-plan.
        Handler handler = new Handler();
        handler.postDelayed(new Runnable()
        {
            @Override
            public void run() // Le temps écoulé, appel de la méthode "reboot()" .
            {
                numCellCode++;
                working = false; // Ceci réinitailise l'état d'activité de "looping()" pour les traitements à venir.
                reboot();
            }
        }, 400); // 0,4 sec de délai
    }


    public void refresh(String latin, String morse) // ====> Rafraîchissement de l'affichage du Player, selon les paramètres entrés <====
    {
        TextView latinchar = findViewById(R.id.latinchar); // Caractère latin en cours de traitement.
        latinchar.setText(latin);

        TextView morsechar = findViewById(R.id.morsechar); // Caractère morse en cours de traitement.
        morsechar.setText(morse);
    }


    public void bipping(int lengh) // ====> Appel d'un bip, de longueur différente selon que le paramètre soit 1 ou 2 <====
    {
        if(lengh == 1) sound = MediaPlayer.create(this,R.raw.shorter); // 1 = bip court
        else           sound = MediaPlayer.create(this,R.raw.longer);  // 2 = bip long

        if(sound.isPlaying()) sound.stop(); // Evite une éventuelle superposition des sons, pour ne pas trop surcharger la mémoire.
        sound.start();

        vibrator(lengh); // Appel d'une vibration de même longueur.
    }


    public void vibrator(int lengh) // ====> Déclenchement du vibreur du téléphone, en parallèle avec le bip <====
    {
        Vibrator shake = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);

        if(lengh == 1) shake.vibrate(250); // 1 = vibration courte
        else           shake.vibrate(500); // 2 = vibration longue
    }


    public void buttonState() // ====> Selon que le Player soit (in)actif, décide de l'apparence des boutons <====
    {
        if(playing == true) // Si lecture en cours :
        {
            buttonBack.getBackground().setAlpha(160);
            buttonNew.getBackground().setAlpha(160);
            buttonPlay.setText("STOP");
        }
        else // Sinon :
        {
            buttonBack.getBackground().setAlpha(255);
            buttonNew.getBackground().setAlpha(255);
            buttonPlay.setText("PLAY");
        }
    }
}
