package com.tomastc.smartwatchclient;

import static com.tomastc.smartwatchclient.SmartwatchClient.STATUS.ACCEPT_ACK;
import static com.tomastc.smartwatchclient.SmartwatchClient.STATUS.IDLE;
import static com.tomastc.smartwatchclient.SmartwatchProtocol.COMMAND.ACCEPT_NO;
import static com.tomastc.smartwatchclient.SmartwatchProtocol.COMMAND.ACCEPT_YES;
import static com.tomastc.smartwatchclient.SmartwatchProtocol.COMMAND.FINISH_REQUEST;
import static com.tomastc.smartwatchclient.SmartwatchProtocol.COMMAND.JOIN_ACK;
import static com.tomastc.smartwatchclient.SmartwatchProtocol.COMMAND.JOIN_FINISH_REQUEST;
import static com.tomastc.smartwatchclient.SmartwatchProtocol.COMMAND.JOIN_NO;
import static com.tomastc.smartwatchclient.SmartwatchProtocol.COMMAND.JOIN_START_REQUEST;
import static com.tomastc.smartwatchclient.SmartwatchProtocol.COMMAND.JOIN_YES;
import static com.tomastc.smartwatchclient.SmartwatchProtocol.COMMAND.PONG;
import static com.tomastc.smartwatchclient.SmartwatchProtocol.COMMAND.RESET_ACK;
import static com.tomastc.smartwatchclient.SmartwatchProtocol.COMMAND.RESET_REQUEST;
import static com.tomastc.smartwatchclient.SmartwatchProtocol.COMMAND.START_REQUEST;
import static com.tomastc.smartwatchclient.SmartwatchProtocol.COMMAND.TERM_ACK;

import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.speech.RecognitionListener;
import android.speech.RecognizerIntent;
import android.speech.SpeechRecognizer;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.core.content.ContextCompat;

import com.tomastc.smartwatchclient.databinding.ActivityJoinRequestBinding;
import com.tomastc.smartwatchclient.lib.Settings;
import com.tomastc.smartwatchclient.lib.common.MessageListener;
import com.tomastc.smartwatchclient.lib.common.MyAlertDialog;
import com.tomastc.smartwatchclient.lib.common.Utils;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;

import okhttp3.WebSocket;

public class JoinRequestActivity extends Activity implements MessageListener, SensorEventListener {

    private Button btn_ok, btn_join, btn_yes, btn_no, btn_start, btn_finish, btn_finish_yes, btn_finish_no;
    private TextView txt_main_title, txt_title, txt_sub_title, txt_description, txt_device_id, txt_accepted_by;
    public DeviceInfoDisplay infoDisplay;

    private String ext_params;

    private SensorManager sensorManager;
    private Sensor stepSensor, heartRateSensor;
    private SensorEventListener sensorEventListener;

    private JSONObject jext_parm;
    private SpeechRecognizer speechRecognizer;
    private Intent speechRecognizerIntent;

    // Number of seconds displayed
    // on the stopwatch.
    private int seconds = 0;

    // Is the stopwatch running?
    private boolean running;

    private boolean wasRunning;

    private void sendTransactionCommand(SmartwatchProtocol.COMMAND $command, HashMap others) {
        try {
            JSONObject json = new JSONObject(ext_params);
            json.put("user_id", Settings.getPresettingsObject().getString("user_id"));
            json.put("dev_id", Settings.getPresettingsObject().getString("dev_id"));
            json.put("dev_group", Settings.getPresettingsObject().getString("dev_group"));
            if (others != null && others.size() > 0) {
                for ( String key : (Set<String>) others.keySet() ) {
                    json.put(key, others.get(key));
                }
            }

            SmartwatchClient.getInstance().sendMessage(
                    SmartwatchProtocol.encapsulation(
                            $command.toString(),
                            json
                    )
            );
        } catch (Exception e) {
            Log.e(getString(R.string.tag), "Error caught! " + e.getMessage());
            finish();
        }
    }


    private void sendTransactionCommand(SmartwatchProtocol.COMMAND $command) {
        sendTransactionCommand($command, null);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_join_request);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        // update MessageListener class
        SmartwatchClient.getInstance().setOnMessageListener(this);

        // update info bar
        infoDisplay = new DeviceInfoDisplay(getApplicationContext(), this);
        try {
            registerReceiver(this.infoDisplay, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
        } catch (Exception e) {
            Log.e(getString(R.string.tag), "Error caught! " + e.getMessage());
        }

        txt_main_title = (TextView)findViewById(R.id.txt_main_title);
        txt_title = (TextView)findViewById(R.id.txt_title);
        txt_sub_title = (TextView)findViewById(R.id.txt_sub_title);
        txt_description = (TextView)findViewById(R.id.txt_description);
        txt_device_id = (TextView)findViewById(R.id.txt_device_id);
        txt_accepted_by = (TextView)findViewById(R.id.txt_accepted_by);
        btn_ok = (Button)findViewById(R.id.btn_ok);
        btn_join = (Button)findViewById(R.id.btn_join);
        btn_yes = (Button)findViewById(R.id.btn_yes);
        btn_no = (Button)findViewById(R.id.btn_no);
        btn_start = (Button)findViewById(R.id.btn_start);
        btn_finish = (Button)findViewById(R.id.btn_finish);
        btn_finish_yes = (Button)findViewById(R.id.btn_finish_yes);
        btn_finish_no = (Button)findViewById(R.id.btn_finish_no);
        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

        try {
            String ext_parm = Settings.getPresettingsObject().getString("ext_parm");
            JSONObject parm = new JSONObject(ext_parm);
            if (parm.has("step_count")) {
                if (parm.getInt("step_count") == 1) {
                    stepSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
                    sensorManager.registerListener(this, stepSensor, SensorManager.SENSOR_DELAY_NORMAL);
                }
                if (parm.getInt("heart_rate") == 1) {
                    heartRateSensor = sensorManager.getDefaultSensor(Sensor.TYPE_HEART_RATE);
                    sensorManager.registerListener(this, heartRateSensor, SensorManager.SENSOR_DELAY_NORMAL);
                }
            }

        } catch (Exception e) {
            Log.e(getString(R.string.tag), "Exception caught: " + e.getMessage());
        }



        ext_params = getIntent().getExtras().getString("ext");
        try {
            JSONObject j_ext = new JSONObject(ext_params);
            txt_title.setText(j_ext.getString("title"));
            txt_sub_title.setText(j_ext.getString("sub_title"));
            txt_description.setText(j_ext.getString("description"));
            txt_accepted_by.setText(
                    String.format("This correspondence by\n\"%s\"", j_ext.getString("accepted_by")) );

            txt_device_id.setText(Settings.getPresettingsObject().getString("dev_id"));
        } catch (JSONException e) {
            e.printStackTrace();
        }

        if (savedInstanceState != null) {

            // Get the previous state of the stopwatch
            // if the activity has been
            // destroyed and recreated.
            seconds
                    = savedInstanceState
                    .getInt("seconds");
            running
                    = savedInstanceState
                    .getBoolean("running");
            wasRunning
                    = savedInstanceState
                    .getBoolean("wasRunning");
        }
        runTimer();

        // Event
        btn_ok.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d(getString(R.string.tag), "perform click => btn_ok");
                sendTransactionCommand(JOIN_NO);

                // set MainActivity to handle message
                SmartwatchClient.getInstance().setOnMessageListener(MainActivity.instance);

                finish();
            }
        });

        btn_join.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d(getString(R.string.tag), "perform click => btn_join");
                SmartwatchClient.getInstance().status = SmartwatchClient.STATUS.JOIN_ACK;

                txt_main_title.setVisibility(View.VISIBLE);
                txt_accepted_by.setVisibility(View.GONE);
                txt_main_title.setText("Join?");

                btn_ok.setVisibility(View.GONE);
                btn_join.setVisibility(View.GONE);
                btn_yes.setVisibility(View.VISIBLE);
                btn_no.setVisibility(View.VISIBLE);

                // Enable voice command
                startSpeechToText();
            }
        });

        btn_yes.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d(getString(R.string.tag), "perform click => btn_yes");
                sendTransactionCommand(JOIN_YES);
                SmartwatchClient.getInstance().status = SmartwatchClient.STATUS.JOINED;

                // Set behavior text and button
                txt_main_title.setText("Start");
                btn_yes.setVisibility(View.INVISIBLE);
                btn_no.setVisibility(View.INVISIBLE);
                btn_start.setVisibility(View.VISIBLE);

                // Enable voice command
                startSpeechToText();
            }
        });


        btn_no.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d(getString(R.string.tag), "perform click => btn_no");
                sendTransactionCommand(JOIN_NO);

                // set MainActivity to handle message
                SmartwatchClient.getInstance().setOnMessageListener(MainActivity.instance);

                finish();
            }
        });

        // Event
        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d(getString(R.string.tag), "perform click => btn_start");
                sendTransactionCommand(JOIN_START_REQUEST);

                // Enable voice command
                startSpeechToText();
            }
        });

        // Event
        btn_finish.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d(getString(R.string.tag), "perform click => btn_finish");
                // Set behavior text and button
                txt_main_title.setText("Finished?");
                btn_finish.setVisibility(View.INVISIBLE);
                btn_finish_yes.setVisibility(View.VISIBLE);
                btn_finish_no.setVisibility(View.VISIBLE);

                // Enable voice command
                startSpeechToText();
            }
        });

        // Event
        btn_finish_yes.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d(getString(R.string.tag), "perform click => btn_finish_yes");
                sendTransactionCommand(JOIN_FINISH_REQUEST);

                // Resume the stop watch timer
                running = false;


                SmartwatchClient.getInstance().status = IDLE;

                SmartwatchClient.getInstance().setOnMessageListener(MainActivity.instance);
                finish();
            }
        });

        // Event
        btn_finish_no.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d(getString(R.string.tag), "perform click => btn_finish_no");
                // Set behavior text and button
                txt_main_title.setText("Working");
                btn_finish_yes.setVisibility(View.INVISIBLE);
                btn_finish_no.setVisibility(View.INVISIBLE);
                btn_finish.setVisibility(View.VISIBLE);

                // Enable voice command
                startSpeechToText();
            }
        });

        /**
         * Version 1.6.0: Add supported for voice recognize command
         */
        checkAudioPermission();
        // Enable voice command
        startSpeechToText();
    }

    @Override
    protected void onDestroy() {

        Log.d(getString(R.string.tag),
                "onDestroy(): SmartwatchClient status: " + SmartwatchClient.getInstance().status);


        if (SmartwatchClient.status == SmartwatchClient.STATUS.JOIN_ACK
                || SmartwatchClient.status == SmartwatchClient.STATUS.JOINED
                || SmartwatchClient.status == SmartwatchClient.STATUS.WORKING) {
            // In case of silent mode, it wouldn't go back to MainActivity.
            SmartwatchClient.getInstance().status = IDLE;
            sendTransactionCommand(RESET_REQUEST);

            // Set Message handle to MainActivity
            SmartwatchClient.getInstance().setOnMessageListener(MainActivity.instance);
        }



        super.onDestroy();
        sensorManager.unregisterListener(this);

        if (this.infoDisplay != null) {
            try {
                this.unregisterReceiver(this.infoDisplay);
            } catch (Exception e) {
                Log.e(getString(R.string.tag), "Error caught! " + e.getMessage());
            }
        }

        if (speechRecognizer != null) {
            speechRecognizer.destroy();
        }
    }

    /**
     * Version 1.6.0: Add supported voice recognize command
     */
    private void checkAudioPermission() {
        try {
            if (jext_parm.getInt("voice_command") != 1) return;

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {  // M = 23
                if (ContextCompat.checkSelfPermission(this, "android.permission.RECORD_AUDIO") != PackageManager.PERMISSION_GRANTED) {
                    Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                            Uri.parse("package:com.programmingtech.offlinespeechtotext"));
                    startActivity(intent);
                    Toast.makeText(this, "Allow Microphone Permission", Toast.LENGTH_SHORT).show();
                }
            }
        } catch (Exception e) {
            Log.e(getString(R.string.tag), e.getMessage());
        }
    }
    private void startSpeechToText() {
        try {
            if (jext_parm.getInt("voice_command") != 1) return;

            if (speechRecognizer == null) {
                speechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
                speechRecognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
                speechRecognizerIntent.putExtra(
                        RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                        RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
                );
                speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());

                speechRecognizer.setRecognitionListener(new RecognitionListener() {
                    public void onReadyForSpeech(Bundle bundle) {
                    }

                    @Override
                    public void onBeginningOfSpeech() {

                    }

                    @Override
                    public void onRmsChanged(float v) {

                    }

                    @Override
                    public void onBufferReceived(byte[] bytes) {

                    }

                    @Override
                    public void onEndOfSpeech() {

                    }

                    @Override
                    public void onError(int error) {
                        if ((error == SpeechRecognizer.ERROR_NO_MATCH)
                                || (error == SpeechRecognizer.ERROR_SPEECH_TIMEOUT)){

                        }
                        else if(error == SpeechRecognizer.ERROR_RECOGNIZER_BUSY){

                        }

                        startSpeechToText();
                    }

                    @Override
                    public void onResults(Bundle bundle) {
                        ArrayList<String> matches = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
                        String tmp = matches.stream().map(Object::toString)
                                .collect(Collectors.joining(", "));
                        Log.d(getString(R.string.tag), tmp);

                        switch (SmartwatchClient.getInstance().status) {
                            case JOIN_ACK:
                                if (tmp.contains("join") || tmp.contains("yes") || tmp.contains("yeah")) {
                                    btn_yes.performClick();
                                } else if (tmp.contains("no") || tmp.contains("ok")) {
                                    btn_no.performClick();
                                } else { // no matched
                                    startSpeechToText();
                                }
                                break;
                            case JOINED:
                                if (tmp.contains("start")) {
                                    btn_start.performClick();
                                } else { // no matched
                                    startSpeechToText();
                                }
                                break;
                            case WORKING:
                                if (btn_finish.getVisibility() == View.VISIBLE) {
                                    if (tmp.contains("fin") || tmp.contains("end")) {
                                        btn_finish.performClick();
                                    } else { // no matched
                                        startSpeechToText();
                                    }
                                } else {
                                    if (tmp.contains("yes") || tmp.contains("yeah")) {
                                        btn_finish_yes.performClick();
                                    } else if (tmp.contains("no")) {
                                        btn_finish_no.performClick();
                                    } else { // no matched
                                        startSpeechToText();
                                    }

                                }
                                break;
                        }

                        speechRecognizer.cancel();
                    }

                    @Override
                    public void onPartialResults(Bundle bundle) {

                    }

                    @Override
                    public void onEvent(int i, Bundle bundle) {

                    }

                });

                // starts listening ...
                speechRecognizer.startListening(speechRecognizerIntent);
            }
        } catch (Exception e) {
            Log.e(getString(R.string.tag), e.getMessage());
        }

    }

    @Override
    public void onMessage(WebSocket webSocket, String text) {
        Log.d(getString(R.string.tag), String.format("%s : received -> \"%s\"", this.getLocalClassName(),
                text));

        runOnUiThread(new Runnable() {

            @Override
            public void run() {

                try {
                    JSONObject jsonObject = new JSONObject(text);

                    switch (SmartwatchProtocol.COMMAND.valueOf(jsonObject.getString("cmd"))) {
                        case HELLO:
                            SmartwatchClient.getInstance().status = SmartwatchClient.STATUS.IDLE;

                            finish();
                            break;
                        case ACCEPT_REQUEST:
                        case JOIN_REQUEST:

                            break;

                        case ACCEPT_ACK:
                        case JOIN_ACK:

                            break;


                        case START_ACK:
                        case JOIN_START_ACK:
                            SmartwatchClient.getInstance().status = SmartwatchClient.STATUS.WORKING;

                            // Set behavior text and button
                            txt_main_title.setText("Working");
                            btn_start.setVisibility(View.INVISIBLE);
                            btn_finish.setVisibility(View.VISIBLE);

                            // start that stop watch timer
                            seconds = 0;
                            running = true;
                            break;

                        case FINISH_ACK:
                        case JOIN_FINISH_ACK:
                            SmartwatchClient.getInstance().status = SmartwatchClient.STATUS.IDLE;

                            SmartwatchClient.getInstance().setOnMessageListener(MainActivity.instance);
                            finish();

                            break;

                        case RESET_REQUEST:
                            SmartwatchClient.getInstance().status = SmartwatchClient.STATUS.IDLE;

                            sendTransactionCommand(RESET_ACK);

                            SmartwatchClient.getInstance().setOnMessageListener(MainActivity.instance);
                            finish();
                            break;

                        case PING:
                            SmartwatchClient.getInstance().sendMessage(
                                    SmartwatchProtocol.encapsulation(
                                            PONG.toString(),
                                            null
                                    )
                            );
                            break;

                        case TERM_REQUEST:
                            sendTransactionCommand(TERM_ACK);
                            SmartwatchClient.getInstance().status = IDLE;
                            Utils.vibrate(JoinRequestActivity.this, Utils.VIBRATE_PATTERN2 );

                            Toast.makeText(JoinRequestActivity.this, "Job has been terminated!", Toast.LENGTH_LONG).show();
                            SmartwatchClient.getInstance().setOnMessageListener(MainActivity.instance);
                            finish();
                            break;

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

            }
        });
    }

    @Override
    public void onSensorChanged(SensorEvent event) {

        if (
                SmartwatchClient.getInstance().status != SmartwatchClient.STATUS.ACCEPTED
                        && SmartwatchClient.getInstance().status != SmartwatchClient.STATUS.WORKING
        )
            return;

        String msg;
        switch (event.sensor.getType()) {
            case Sensor.TYPE_HEART_RATE:
                msg = "Heart Rate : " + (int)event.values[0];
                Log.d(getString(R.string.tag), msg);
                sendTransactionCommand(SmartwatchProtocol.COMMAND.HEART_RATE_TRACKING_REQUEST, new HashMap<String, String>(){{
                    put("value", String.valueOf(event.values[0]));
                    put("status", SmartwatchClient.getInstance().status.toString());
                }});
                break;

            case Sensor.TYPE_STEP_COUNTER:
                msg = "Step Count : " + (int)event.values[0];
                Log.d(getString(R.string.tag), msg);
                sendTransactionCommand(SmartwatchProtocol.COMMAND.STEP_COUNT_TRACKING_REQUEST, new HashMap<String, String>(){{
                    put("value", String.valueOf(event.values[0]));
                    put("status", SmartwatchClient.getInstance().status.toString());
                }});
                break;

            default:
                Log.d(getString(R.string.tag), "Unsupported sensor type");

        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int i) {

    }


    // Save the state of the stopwatch
    // if it's about to be destroyed.
    @Override
    public void onSaveInstanceState(
            Bundle savedInstanceState)
    {
        savedInstanceState
                .putInt("seconds", seconds);
        savedInstanceState
                .putBoolean("running", running);
        savedInstanceState
                .putBoolean("wasRunning", wasRunning);
    }

    // If the activity is paused,
    // stop the stopwatch.
    @Override
    protected void onPause()
    {
        super.onPause();
        wasRunning = running;
        running = false;
    }

    // If the activity is resumed,
    // start the stopwatch
    // again if it was running previously.
    @Override
    protected void onResume()
    {
        super.onResume();
        if (wasRunning) {
            running = true;
        }
    }

    // Sets the NUmber of seconds on the timer.
    // The runTimer() method uses a Handler
    // to increment the seconds and
    // update the text view.
    private void runTimer()
    {

        // Get the text view.
        final TextView timeView
                = (TextView)findViewById(
                R.id.txt_description);

        // Creates a new Handler
        final Handler handler
                = new Handler();

        // Call the post() method,
        // passing in a new Runnable.
        // The post() method processes
        // code without a delay,
        // so the code in the Runnable
        // will run almost immediately.
        handler.post(new Runnable() {
            @Override

            public void run()
            {
                int hours = seconds / 3600;
                int minutes = (seconds % 3600) / 60;
                int secs = seconds % 60;

                // Format the seconds into hours, minutes,
                // and seconds.
                String time
                        = String
                        .format(Locale.getDefault(),
                                "%d:%02d:%02d", hours,
                                minutes, secs);

                // Set the text view text.
                if (txt_main_title.getText().equals("Working")) {
                    timeView.setText(time);
                }

                // If running is true, increment the
                // seconds variable.
                if (running) {
                    seconds++;
                }

                // Post the code again
                // with a delay of 1 second.
                handler.postDelayed(this, 1000);
            }
        });
    }
}