package com.hesiii.i_hear.Game;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.speech.tts.TextToSpeech;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.hesiii.i_hear.API.ApiServices;
import com.hesiii.i_hear.API.RetroServer;
import com.hesiii.i_hear.Activities.HomeActivity;
import com.hesiii.i_hear.Activities.SignInActivity;
import com.hesiii.i_hear.Models.Data;
import com.hesiii.i_hear.R;
import com.hesiii.i_hear.ResponseModels.PronunciationAccuracyResponse;
import com.hesiii.i_hear.databinding.ActivityGameBinding;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Random;

import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class GameActivity extends AppCompatActivity {

    private static final int PERMISSION_CODE = 0;
    private ActivityGameBinding binding;
    private List<Data>dataList;

    private int level = 0;
    private String text;
    private boolean isRecording = false;
    private static final int RECORDER_BPP = 16;
    private static final String AUDIO_RECORDER_FILE_EXT_WAV = ".wav";
    private static final String AUDIO_RECORDER_FOLDER = "AudioRecorder";
    private static final String AUDIO_RECORDER_TEMP_FILE = "record_temp.raw";
    private static final int RECORDER_SAMPLERATE = 44100;
    private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_STEREO;
    private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
    short[] audioData;

    private AudioRecord recorder = null;
    private int bufferSize = 0;
    private Thread recordingThread = null;
    private File audioFile;

    private TextToSpeech textToSpeech;
    private String status;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityGameBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        if (getSupportActionBar() != null) {

            getSupportActionBar().hide();

        }

        dataList = (ArrayList<Data>) getIntent().getSerializableExtra("data");

        Data data = dataList.get(level);
        text = data.getName();
        binding.imageIcon.setImageResource(data.getImage());

        status = level+1+" / 0"+dataList.size();
        binding.statusText.setText(status);

        //text = "Mango";

        bufferSize = AudioRecord.getMinBufferSize
                (RECORDER_SAMPLERATE, RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING) * 3;

        audioData = new short[bufferSize]; //short array that pcm data is put into.

        binding.recordButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                checkPermissions();

            }
        });

        textToSpeech = new TextToSpeech(this, new TextToSpeech.OnInitListener() {
            @Override
            public void onInit(int i) {

                if (i != TextToSpeech.ERROR) {

                    textToSpeech.setLanguage(Locale.ENGLISH);

                }

            }
        });

    }

    private String getRandomWord() {

        // Define the character pool
        String characters = "abcdefghijklmnopqrstuvwxyz";

        // Create a random object
        Random random = new Random();

        // Initialize an empty string to store the random characters
        StringBuilder randomWord = new StringBuilder();

        // Generate 4 random characters and add them to the StringBuilder
        for (int i = 0; i < 6; i++) {
            int randomIndex = random.nextInt(characters.length());
            char randomChar = characters.charAt(randomIndex);
            randomWord.append(randomChar);
        }

        // Convert the StringBuilder to a string
        String meaninglessWord = randomWord.toString();

        // Print the meaningless word
        System.out.println("Meaningless Word: " + meaninglessWord);
        return "Audio_"+meaninglessWord;

    }

    private String getFilename() {

        String filepath = Environment.getExternalStorageDirectory().getPath();
        File file = new File(filepath, AUDIO_RECORDER_FOLDER);

        if (!file.exists()) {
            file.mkdirs();
        }

        String name = file.getAbsolutePath() + "/Audio_" + System.currentTimeMillis() +
                AUDIO_RECORDER_FILE_EXT_WAV;

        audioFile = new File(name);

        return name;
    }

    private String getTempFilename() {
        String filepath = Environment.getExternalStorageDirectory().getPath();
        File file = new File(filepath, AUDIO_RECORDER_FOLDER);

        if (!file.exists()) {
            file.mkdirs();
        }

        File tempFile = new File(filepath, AUDIO_RECORDER_TEMP_FILE);

        if (tempFile.exists())
            tempFile.delete();

        return (file.getAbsolutePath() + "/" + AUDIO_RECORDER_TEMP_FILE);
    }

    private void checkPermissions() {

        if ((ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_AUDIO)
                != PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
                != PackageManager.PERMISSION_GRANTED)) {

            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO}, PERMISSION_CODE);

        } else {

            if (isRecording) {

                binding.recordButton.setImageResource(R.drawable.record);
                isRecording = false;
                stopRecording();

            } else {

                binding.recordButton.setImageResource(R.drawable.stop_button);
                isRecording = true;
                startRecording();

            }

        }

    }

    private void startRecording() {

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return;
        }
        recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
                RECORDER_SAMPLERATE,
                RECORDER_CHANNELS,
                RECORDER_AUDIO_ENCODING,
                bufferSize);
        int i = recorder.getState();
        if (i==1)
            recorder.startRecording();

        isRecording = true;

        recordingThread = new Thread(new Runnable() {
            @Override
            public void run() {
                writeAudioDataToFile();
            }
        }, "AudioRecorder Thread");

        recordingThread.start();
    }

    private void writeAudioDataToFile() {
        byte data[] = new byte[bufferSize];
        String filename = getTempFilename();
        FileOutputStream os = null;

        try {
            os = new FileOutputStream(filename);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        int read = 0;
        if (null != os) {
            while(isRecording) {
                read = recorder.read(data, 0, bufferSize);
                if (read > 0){
                }

                if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                    try {
                        os.write(data);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

            try {
                os.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void stopRecording() {

        if (null != recorder){
            isRecording = false;

            int i = recorder.getState();
            if (i==1)
                recorder.stop();
            recorder.release();

            recorder = null;
            recordingThread = null;
        }

        String fileName = getFilename();
        //textView.setText(fileName);
        System.out.println(fileName);

        copyWaveFile(getTempFilename(),fileName);
        deleteTempFile();
    }

    private void deleteTempFile() {

        File file = new File(getTempFilename());
        file.delete();

        //binding.dataContainer.setVisibility(View.VISIBLE);
        // visible things here
        if (audioFile.exists()) {
            System.out.println("Audio File Name : "+audioFile.getName());
            System.out.println("Audio File Path : "+audioFile.getAbsolutePath());
            uploadToServer(audioFile);
        }

    }

    private void uploadToServer(File audioFile) {

        binding.progressBar.setVisibility(View.VISIBLE);

        // Create an instance of the ApiService using the Retrofit instance
        ApiServices apiServices = RetroServer.getRetrofitInstance().create(ApiServices.class);

        // Create a request body from the file to be sent in the API request
        RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), audioFile);
        // Create a MultipartBody.Part for the image file with the key "image_path"
        MultipartBody.Part audioPart = MultipartBody.Part.createFormData("audio", audioFile.getName(), requestFile);

        // Create the API request to get the sign response
        Call<PronunciationAccuracyResponse> getSignResponse = apiServices.getPronunciationAccuracy(text, audioPart);

        getSignResponse.enqueue(new Callback<PronunciationAccuracyResponse>() {
            @Override
            public void onResponse(@NonNull Call<PronunciationAccuracyResponse> call, @NonNull Response<PronunciationAccuracyResponse> response) {

                if (response.isSuccessful()) {

                    if (response.body() != null) {

                        System.out.println("hit : "+response.body().getHit());

                        binding.firstContainer.setVisibility(View.GONE);
                        binding.secondContainer.setVisibility(View.VISIBLE);
                        String formattedPercentage = String.format(Locale.US, "%.0f%%", response.body().getSimilarity());
                        System.out.println("similarity : "+formattedPercentage);

                        binding.accuracyText.setText(formattedPercentage);

                        buttonsProcess();

                    } else {

                        Toast.makeText(GameActivity.this, "Something Went Wrong!", Toast.LENGTH_SHORT).show();

                    }

                } else {

                    Toast.makeText(GameActivity.this, "Error - "+response.code(), Toast.LENGTH_SHORT).show();

                }

                binding.progressBar.setVisibility(View.GONE);

            }

            @Override
            public void onFailure(@NonNull Call<PronunciationAccuracyResponse> call, @NonNull Throwable t) {

                binding.progressBar.setVisibility(View.GONE);
                Toast.makeText(GameActivity.this, t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
                System.out.println(t.getLocalizedMessage());

            }
        });

    }

    private void buttonsProcess() {

        binding.textButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                binding.correctText.setText(text);
                binding.correctText.setVisibility(View.VISIBLE);

            }
        });

        binding.speechButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

                    textToSpeech.speak(text,TextToSpeech.QUEUE_FLUSH,null,null);

                } else {

                    textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null);

                }

            }
        });

        binding.nextButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                nextButtonProcess();

            }
        });

    }

    private void nextButtonProcess() {

        binding.correctText.setText("");
        binding.correctText.setVisibility(View.GONE);
        binding.secondContainer.setVisibility(View.GONE);
        binding.firstContainer.setVisibility(View.VISIBLE);

        level++;
        System.out.println("A Level : "+level);

        if (level != 5) {

            status = level+1+" / 0"+dataList.size();
            binding.statusText.setText(status);

            Data data = dataList.get(level);
            text = data.getName();
            binding.imageIcon.setImageResource(data.getImage());
            System.out.println("B Level : "+level);

        } else {

            Intent intent = new Intent(this, CategoryActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
            startActivity(intent);

        }

    }

    private void copyWaveFile(String inFilename,String outFilename){

        FileInputStream in = null;
        FileOutputStream out = null;
        long totalAudioLen = 0;
        long totalDataLen = totalAudioLen + 36;
        long longSampleRate = RECORDER_SAMPLERATE;
        int channels = 2;
        long byteRate = RECORDER_BPP * RECORDER_SAMPLERATE * channels/8;

        byte[] data = new byte[bufferSize];

        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;

            System.out.println("File size: " + totalDataLen);

            WriteWaveFileHeader(out, totalAudioLen, totalDataLen,
                    longSampleRate, channels, byteRate);

            while(in.read(data) != -1) {
                out.write(data);
            }

            in.close();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void WriteWaveFileHeader(
            FileOutputStream out, long totalAudioLen,
            long totalDataLen, long longSampleRate, int channels,
            long byteRate) throws IOException
    {
        byte[] header = new byte[44];

        header[0] = 'R';  // RIFF/WAVE header
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        header[12] = 'f';  // 'fmt ' chunk
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        header[16] = 16;  // 4 bytes: size of 'fmt ' chunk
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        header[20] = 1;  // format = 1
        header[21] = 0;
        header[22] = (byte) channels; //Set  channel
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        header[32] = (byte) (2 * 16 / 8);  // block align
        header[33] = 0;
        header[34] = RECORDER_BPP;  // bits per sample
        header[35] = 0;
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);

        out.write(header, 0, 44);
    }

}