package com.diampark.test.Fragment;

import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.text.Spannable;
import android.text.method.ScrollingMovementMethod;
import android.text.style.ForegroundColorSpan;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.diampark.test.Class.AudioRecordUtil;
import com.diampark.test.Class.CustomPhoneme;
import com.diampark.test.Class.MyFileManager;
import com.diampark.test.Class.SuiviConcentration;
import com.diampark.test.Class.UserVocalSession;
import com.diampark.test.Class.Word;
import com.diampark.test.Class.WordFiller;
import com.diampark.test.Interface.StateDialogInterface;
import com.diampark.test.MainActivity;
import com.diampark.test.R;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.visualizer.amplitude.AudioRecordView;

import org.json.JSONException;
import org.json.JSONObject;
import org.vosk.LibVosk;
import org.vosk.LogLevel;
import org.vosk.Model;
import org.vosk.Recognizer;
import org.vosk.android.RecognitionListener;
import org.vosk.android.SpeechService;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class VocalFragment extends BottomNavigationFragment implements
        RecognitionListener, StateDialogInterface {

    static private final int STATE_DONE = 1;
    static private final int STATE_MIC = 2;

    private Model model;
    private String currentWord;
    private SpeechService speechService;
    private TextView resultView;
    private TextView percentView;
    private AudioRecordView audioRecordView;
    private TextView wordView;
    private Integer count = 0;
    private Double succeed = 0.0;
    private Integer attempt = 0;
    private Integer numbOfWord = 0;
    private ArrayList<String> wordsList;
    private UserVocalSession userVocalSession = null;
    private Word currentWordPerf;
    private Context myContext;
    private int ifPressed = 0;
    private MainActivity mActivity;
    private Button openOrCloseSessionButton;
    private Button startButton;
    private Button changeWordButton;
    private String modalite;

    public VocalFragment() {
        super(R.layout.fragment_vocal);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        myContext = this.getContext();

        modalite = "reconnaissance_vocale";

        wordView = view.findViewById(R.id.word_text);
        resultView = view.findViewById(R.id.result_text);
        percentView = view.findViewById(R.id.percent_text);
        audioRecordView = view.findViewById(R.id.visualizer);
        openOrCloseSessionButton = view.findViewById(R.id.button);
        changeWordButton = view.findViewById(R.id.button2);
        startButton = view.findViewById(R.id.button3);

        resultView.setMovementMethod(new ScrollingMovementMethod());

        mActivity = (MainActivity) getActivity();

        reviveViewIfNeeded();
        changeWordButton.setOnClickListener(v -> userChangeWord());
        openOrCloseSessionButton.setOnClickListener(v -> openOrCloseSession());
        startButton.setOnClickListener(v -> {
            if (mActivity != null && ifPressed == 0) {
                String[] answers = {"Pas du tout (aucun problème).", "Mon élocution est faible, peu articulée et " +
                        "inégale, mais ne conduit pas les autres à me demander de me répéter.", "Mon élocution " +
                        "conduit les gens à me demander de me répéter occasionnellement, mais pas tous les jours.",
                        "Mon élocution est assez peu claire pour que les autres me demandent de répéter chaque jour, " +
                                "même si la plupart de mon discours est compris.", "La plupart ou tout mon discours ne peut être compris."};
                mActivity.popupStartSession(myContext, "Au cours de la semaine précédente, avez-vous eu des problèmes" +
                        " avec votre élocution ?", answers, this);
            }
            ifPressed = 1;
        });

    }


    private void reviveViewIfNeeded() {
        if (userVocalSession != null) {
            updateScore();
            if (speechService != null) {
                stopSpeechService();
                createSpeechService();
                setUiState(STATE_MIC);
            } else
                setUiState(STATE_DONE);

            currentWordPerf.setWordAsked(currentWord);
            wordView.setText(currentWord);
        }
    }

    private void createSpeechService() {
        try {
            Recognizer rec = new Recognizer(model, 16000.0f);
            speechService = new SpeechService(rec, 16000.0f);
            speechService.startListening(this);
        } catch (IOException e) {
            setErrorState(e.getMessage());
        }

    }

    private void launchSession() {
        Activity currActivity = this.getActivity();
        if (currActivity != null && currActivity.getIntent() != null) {
            this.model = ((MainActivity) this.getActivity()).model;
            if (model != null) {
                changeWordToSay();
                openOrCloseSession();
            }
        }
    }

    private void resetSession() {
        if (attempt != 0) {
            if (mActivity.accord_partage == true) {
                percentView.setText("Session terminée !\nVotre pourcentage de reussite est de " + new DecimalFormat(
                        "##.#").format((succeed * 100) / (count - 1)) + "%\nVos données ont bien été envoyées à DiamPark");
            } else {
                percentView.setText("Session terminée !\nVotre pourcentage de reussite est de " + new DecimalFormat("#" +
                        ".##").format((succeed * 100) / (count - 1)) + "%");
            }
        }
        count = 0;
        succeed = 0.0;
        attempt = 0;
        changeWordToSay();
        stopSpeechService();
    }

    private void openOrCloseSession() {
        wordsList = new WordFiller().fillPrevSessionWordLexique(wordsList,
                new MyFileManager().readFileForVocalSession(myContext));

        if (speechService != null) {
            setUiState(STATE_DONE);
            stopRecording();
            resetSession();
            downloadPerf();
            setStartButtonVisible();
        } else if (model != null) {
            percentView.setText("");
            setUiState(STATE_MIC);
            userVocalSession = new UserVocalSession();
            userVocalSession.setState(mActivity.state);
            userVocalSession.setHashUser(mActivity.androidUniqueId);
            userVocalSession.setLevo(mActivity.currentSuivi.concentration());
            createSpeechService();
            startRecording();
        }
    }

    private void downloadPerf() {
        if (!userVocalSession.isEmpty()) {
            userVocalSession.setDate(Calendar.getInstance().getTime());

            if (mActivity.accord_partage)
                preparePost();

            Gson gson = new Gson();
            new MyFileManager().writePerfOnInternalStorage(myContext, gson.toJson(userVocalSession),
                    MyFileManager.directoryVocalName, MyFileManager.fileVocalBaseName);
        }
    }

    private void setUiState(int state) {
        View v = getView();
        if (v != null) {
            switch (state) {
                case STATE_MIC:
                    resultView.setText(R.string.say_something);
                    ((Button) v.findViewById(R.id.button)).setText(R.string.button_record_text_on);
                    break;
                case STATE_DONE:
                    resultView.setText(R.string.session_end);
                    ((Button) v.findViewById(R.id.button)).setText(R.string.button_record_text_off);
                    break;
            }
        }
    }

    private void updateScore() {
        if (count != 1) {
            Double res = (succeed * 100) / (count - 1);
            String roundRes = String.format(java.util.Locale.US, "%.1f", res);
            String roundSuccess = String.format(java.util.Locale.US, "%.1f", succeed);
            String percent = roundRes + getString(R.string.result_word_number_text) + " " + roundSuccess + '/' + (count - 1);
            if (userVocalSession != null)
                userVocalSession.setMark(res);
            percentView.setText(percent);
        }
    }

    private void setPause(boolean v) {
        if (speechService != null) {
            speechService.setPause(v);
        }
    }

    private void changeWordToSay() {
        Random rand = new Random();
        count++;
        updateUserPerf();
        setPause(true);
        currentWord = wordsList.get(rand.nextInt(numbOfWord));
        currentWordPerf = new Word();
        currentWordPerf.setWordAsked(currentWord);
        wordView.setText(currentWord);
        resultView.setText("");
        updateScore();
        attempt = 0;
        setPause(false);
    }

    private void checkSpeechMatch(String word) {
        attempt++;
        currentWordPerf.addNewAttempt(word);
        if (new CustomPhoneme().checkIfMatch(myContext, word, currentWord, R.raw.final_tab)) {
            succeed += 1d / attempt;
            appendColoredText(resultView, currentWord, Color.GREEN);
            Handler handler = new Handler();
            handler.postDelayed(this::changeWordToSay, 1000);
        } else {
            appendColoredText(resultView, word, Color.RED);
        }
    }

    private void userChangeWord() {
        currentWordPerf.addNewAttempt("***");
        changeWordToSay();
    }

    private void updateUserPerf() {
        stopRecording();
        if (attempt > 0 && userVocalSession != null) {
            currentWordPerf.setAttempt(attempt);
            currentWordPerf.setAttemptWordDate(Calendar.getInstance().getTime());
            currentWordPerf.setOutput(Base64.encodeToString(AudioRecordUtil.getInstance().getBuffer(), Base64.DEFAULT));
            AudioRecordUtil.getInstance().realeasOutput();
            userVocalSession.addWordToUserSession(currentWordPerf);

            if (speechService != null)
                startRecording();
        }
    }

    public static void appendColoredText(TextView tv, String text, int color) {
        int start = tv.getText().length();
        tv.append(text + "\n");
        int end = tv.getText().length();

        Spannable spannableText = (Spannable) tv.getText();
        spannableText.setSpan(new ForegroundColorSpan(color), start, end, 0);
    }

    private void setErrorState(String message) {
        resultView.setText(message);
    }


    private void startRecording() {
        AudioRecordUtil.getInstance().start();
    }

    private void stopRecording() {
        AudioRecordUtil.getInstance().stop();
    }


    private void stopSpeechService() {
        if (speechService != null) {
            speechService.stop();
            speechService = null;
        }
    }

    @Override
    public void onPartialResult(String s) {

    }

    @Override
    public void onResume() {
        super.onResume();
        setPause(false);
    }

    @Override
    public void onPause() {
        super.onPause();
        setPause(true);
    }

    @Override
    public void onResult(String voice) {
        try {
            JSONObject obj = new JSONObject(voice);
            String word = (String) obj.get("text");

            if (!word.equals(""))
                checkSpeechMatch(word);

        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onFinalResult(String s) {

    }

    @Override
    public void onError(Exception e) {
        setErrorState(e.getMessage());
    }

    @Override
    public void onTimeout() {

        AudioRecordUtil.getInstance().stop();
        stopSpeechService();
        setUiState(STATE_DONE);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        AudioRecordUtil.getInstance().stop();
        stopSpeechService();
    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
    }

    @Override
    public void call() {
        ifPressed = 0;
        setStartButtonInvisible();

        LibVosk.setLogLevel(LogLevel.INFO);

        if (myContext != null)
            wordsList = new MyFileManager().readFileForWords(myContext, R.raw.constant_word);

        if (!wordsList.isEmpty()) {
            numbOfWord = wordsList.size();
            launchSession();
        } else
            setErrorState("No words in base");
    }

    @Override
    public void cancel() {
        ifPressed = 0;
    }

    public class VocalSessionForPost {
        Date session_date_heure;
        int statut;
        int note_attribuee;
        Double note_obtenue_pour_la_session;
        int nombre_essais;
        String mot_demande;
        String mot_reconnu;
        Double levo;

        public VocalSessionForPost(Date session_date_heure, int statut, int note_attribuee,
                                   Double note_obtenue_pour_la_session, int nombre_essais,
                                   String mot_demande, String mot_reconnu, Double levo) {
            this.session_date_heure = session_date_heure;
            this.statut = statut;
            this.note_attribuee = note_attribuee;
            this.note_obtenue_pour_la_session = note_obtenue_pour_la_session;
            this.nombre_essais = nombre_essais;
            this.mot_demande = mot_demande;
            this.mot_reconnu = mot_reconnu;
            this.levo = levo;
        }
    }

    private void preparePost() {
        UserVocalSession sessionToPost = userVocalSession;
        for (Word w : sessionToPost.getUserPerf())
            for (String s : w.getUserListAttempt()) {
                VocalSessionForPost vocalSessionForPost = new VocalSessionForPost(sessionToPost.getDate(), sessionToPost.getStatus(),
                        sessionToPost.getState(), sessionToPost.getMark(),
                        w.getAttempt(), w.getWordAsked(), s, mActivity.currentSuivi.concentration());
                JsonObject body = new JsonObject();
                body.addProperty("HashCodeUser", sessionToPost.getHashUser());
                body.addProperty("Modalite", modalite);
                body.addProperty("JSON_resultats", new Gson().toJson(vocalSessionForPost));
                body.addProperty("Fichier_de_sortie", w.getOutput());
                body.addProperty("JSON_levo", new Gson().toJson(new SuiviToPost(mActivity.currentSuivi)));

                new Post(body, METHODE.POSTDATA, tmpCallback).start();
            }
    }

    private void setStartButtonInvisible() {
        startButton.setVisibility(View.GONE);
        audioRecordView.setVisibility(View.VISIBLE);
        wordView.setVisibility(View.VISIBLE);
        resultView.setVisibility(View.VISIBLE);
        percentView.setVisibility(View.VISIBLE);
        openOrCloseSessionButton.setVisibility(View.VISIBLE);
        changeWordButton.setVisibility(View.VISIBLE);
    }

    private void setStartButtonVisible() {
        startButton.setVisibility(View.VISIBLE);
        audioRecordView.setVisibility(View.GONE);
        wordView.setVisibility(View.GONE);
        resultView.setVisibility(View.GONE);
        openOrCloseSessionButton.setVisibility(View.GONE);
        changeWordButton.setVisibility(View.GONE);
    }
}
