package d.d.meshenger;

import android.app.Dialog;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDelegate;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import org.libsodium.jni.Sodium;
import org.libsodium.jni.NaCl;


/*
 * Show splash screen, name setup dialog, database password dialog and
 * start background service before starting the MainActivity.
 */
public class StartActivity extends MeshengerActivity implements ServiceConnection {
    private MainService.MainBinder binder;
    private int startState = 0;
    private static Sodium sodium;
    private static final int IGNORE_BATTERY_OPTIMIZATION_REQUEST = 5223;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_splash);

        // load libsodium for JNI access
        this.sodium = NaCl.sodium();

        Typeface type = Typeface.createFromAsset(getAssets(), "rounds_black.otf");
        ((TextView) findViewById(R.id.splashText)).setTypeface(type);

        // start MainService and call back via onServiceConnected()
        MainService.start(this);

        bindService(new Intent(this, MainService.class), this, Service.BIND_AUTO_CREATE);
    }

    private void continueInit() {
        if (this.binder == null) {
            return;
        }

        this.startState += 1;

        switch (this.startState) {
            case 1:
                log("init 1: load database");
                // open without password
                this.binder.loadDatabase();
                continueInit();
                break;
            case 2:
                log("init 2: check database");
                if (this.binder.getDatabase() == null) {
                    // database is probably encrypted
                    showDatabasePasswordDialog();
                } else {
                    continueInit();
                }
                break;
            case 3:
                log("init 3: check username");
                if (this.binder.getSettings().getUsername().isEmpty()) {
                    // set username
                    showMissingUsernameDialog();
                } else {
                    continueInit();
                }
                break;
            case 4:
                log("init 4: check key pair");
                if (this.binder.getSettings().getPublicKey() == null) {
                    // generate key pair
                    initKeyPair();
                }
                continueInit();
                break;
            case 5:
                log("init 5: check addresses");
                if (this.binder.isFirstStart()) {
                    showMissingAddressDialog();
                } else {
                    continueInit();
                }
                break;
            case 6:
                log("init 6: battery optimizations");
                if (this.binder.isFirstStart() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    try {
                        PowerManager pMgr = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
                        if (!pMgr.isIgnoringBatteryOptimizations(this.getPackageName())) {
                            Intent intent = new Intent(android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
                            intent.setData(Uri.parse("package:" + this.getPackageName()));
                            startActivityForResult(intent, IGNORE_BATTERY_OPTIMIZATION_REQUEST);
                            break;
                        }
                    } catch(Exception e) {
                        // ignore
                    }
                }
                continueInit();
                break;
            case 7:
               log("init 7: start contact list");
                // set night mode
                boolean nightMode = this.binder.getSettings().getNightMode();
                AppCompatDelegate.setDefaultNightMode(
                        nightMode ? AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO
                );

                // all done - show contact list
                startActivity(new Intent(this, MainActivity.class));
                finish();
                break;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == IGNORE_BATTERY_OPTIMIZATION_REQUEST) {
            // resultCode: -1 (Allow), 0 (Deny)
            continueInit();
        }
    }

    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        this.binder = (MainService.MainBinder) iBinder;
        log("onServiceConnected");

        if (this.startState == 0) {
            if (this.binder.isFirstStart()) {
                // show delayed splash page
                (new Handler()).postDelayed(() -> {
                    continueInit();
                }, 1000);
            } else {
                // show contact list as fast as possible
                continueInit();
            }
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        this.binder = null;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(this);
    }

    private void initKeyPair() {
        // create secret/public key pair
        final byte[] publicKey = new byte[Sodium.crypto_sign_publickeybytes()];
        final byte[] secretKey = new byte[Sodium.crypto_sign_secretkeybytes()];

        Sodium.crypto_sign_keypair(publicKey, secretKey);

        Settings settings = this.binder.getSettings();
        settings.setPublicKey(publicKey);
        settings.setSecretKey(secretKey);

        try {
            this.binder.saveDatabase();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String getMacOfDevice(String device) {
        for (AddressEntry ae : Utils.collectAddresses()) {
            // only MAC addresses
            if (ae.device.equals("wlan0") && Utils.isMAC(ae.address)) {
                return ae.address;
            }
        }
        return "";
    }

    private void showMissingAddressDialog() {
        String mac = getMacOfDevice("wlan0");

        if (mac.isEmpty()) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle(R.string.setup_address_title);
            builder.setMessage(R.string.setup_address_message);

            builder.setPositiveButton(R.string.ok, (DialogInterface dialog, int id) -> {
                showMissingAddressDialog();
                dialog.cancel();
            });

            builder.setNegativeButton(R.string.skip, (DialogInterface dialog, int id) -> {
                dialog.cancel();
                // continue with out address configuration
                continueInit();
            });

            builder.show();
        } else {
            this.binder.getSettings().addAddress(mac);

            try {
                this.binder.saveDatabase();
            } catch (Exception e) {
                e.printStackTrace();
            }

            continueInit();
        }
    }

    // initial dialog to set the username
    private void showMissingUsernameDialog() {
        TextView tw = new TextView(this);
        tw.setText(R.string.name_prompt);
        //tw.setTextColor(Color.BLACK);
        tw.setTextSize(20);
        tw.setGravity(Gravity.CENTER_HORIZONTAL);

        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.addView(tw);

        EditText et = new EditText(this);
        et.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        et.setSingleLine(true);

        layout.addView(et);
        layout.setPadding(40, 80, 40, 40);
        //layout.setGravity(Gravity.CENTER_HORIZONTAL);

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.hello);
        builder.setView(layout);
        builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> {
            this.stopService(new Intent(this, MainService.class));
            finish();
        });

        InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);

        builder.setPositiveButton(R.string.next, (dialogInterface, i) -> {
            // we will override this handler
        });

        AlertDialog dialog = builder.create();
        dialog.setOnShowListener((newDialog) -> {
            Button okButton = ((AlertDialog) newDialog).getButton(AlertDialog.BUTTON_POSITIVE);
            et.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                    // nothing to do
                }

                @Override
                public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                    // nothing to do
                }

                @Override
                public void afterTextChanged(Editable editable) {
                    okButton.setClickable(editable.length() > 0);
                    okButton.setAlpha(editable.length() > 0 ? 1.0f : 0.5f);
                }
            });

            okButton.setClickable(false);
            okButton.setAlpha(0.5f);

            if (et.requestFocus()) {
                imm.showSoftInput(et, InputMethodManager.SHOW_IMPLICIT);
                //imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
            }
        });

        dialog.show();

        // override handler (to be able to dismiss the dialog manually)
        dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener((View v) -> {
            imm.toggleSoftInput(0, InputMethodManager.HIDE_IMPLICIT_ONLY);
            String username = et.getText().toString();
            if (Utils.isValidName(username)) {
                this.binder.getSettings().setUsername(username);

                try {
                    this.binder.saveDatabase();
                } catch (Exception e) {
                    e.printStackTrace();
                }

                // close dialog
                dialog.dismiss();
                //dialog.cancel(); // needed?
                continueInit();
            } else {
                Toast.makeText(this, R.string.invalid_name, Toast.LENGTH_SHORT).show();
            }
        });
    }

    // ask for database password
    private void showDatabasePasswordDialog() {
        Dialog dialog = new Dialog(this);
        dialog.setContentView(R.layout.dialog_database_password);

        EditText passwordEditText = dialog.findViewById(R.id.PasswordEditText);
        Button exitButton = dialog.findViewById(R.id.ExitButton);
        Button okButton = dialog.findViewById(R.id.OkButton);

        okButton.setOnClickListener((View v) -> {
            String password = passwordEditText.getText().toString();
            this.binder.setDatabasePassword(password);
            this.binder.loadDatabase();

            if (this.binder.getDatabase() == null) {
                Toast.makeText(this, R.string.wrong_password, Toast.LENGTH_SHORT).show();
            } else {
                // close dialog
                dialog.dismiss();
                continueInit();
            }
        });

        exitButton.setOnClickListener((View v) -> {
            // shutdown app
            dialog.dismiss();
            this.stopService(new Intent(this, MainService.class));
            finish();
        });

        dialog.show();
    }

    private void log(String s) {
        Log.d(this, s);
    }
}