/*
 * Copyright (c) 2017 Oracle and/or its affiliates.  All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL).  See the LICENSE file in the root
 * directory for license terms.  You may choose either license, or both.
 */

package com.oracle.iot.sample;

import android.Manifest;
import android.app.AlertDialog;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
import android.net.Uri;
import android.os.*;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.ScrollingMovementMethod;
import android.text.style.ForegroundColorSpan;
import android.view.*;
import android.widget.*;
import com.oracle.iot.sample.obd.BluetoothCommManager;
import com.oracle.iot.sample.obd.ObdPID;
import com.oracle.iot.sample.obd.ObdSensor;
import com.oracle.iot.sample.obd.ObdDeviceService;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * Main activity for the app. Provides a tableview of current OBD attributes and a status line view.
 * It forks the OBD device service that communicates to OBD device.
 */
public class MainActivity extends AppCompatActivity {
    /**
     * The primary interface we will be calling on the OBD sample service.
     */
    IObdDeviceService obdService = null;
    TextView mStatus;
    private boolean mIsBound = false;
    Intent obdServiceIntent;
    BluetoothCommManager bluetoothCommManager;
    SharedPreferences sharedPref;
    TextView deviceText;
    TableLayout tableLayout;
    boolean scanInProgress = false;
    boolean isServiceConnected = false;

    private BluetoothCommManager.IObdScanResult scanResult = new BluetoothCommManager.IObdScanResult() {
        @Override
        public void deviceFound(ObdSensor.ObdDeviceType type, BluetoothDevice device) {
            if (type == ObdSensor.ObdDeviceType.FREEMATICS) {
                bluetoothCommManager.stopScanning();
                displayInfo("INFO: OBD Device Found: " + device.getName());
                startObdService(type, device);
            }
        }

        @Override
        public void scanFinished() {
            scanInProgress = false;
        }
    };

    public static final int LOC_GRANTED =1;

    void checkPermissions() {
        int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION);
        if (permissionCheck != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
                    LOC_GRANTED);
        }
        permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
        if (permissionCheck != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                    LOC_GRANTED);
        }
    }

    void startScan() {
        if (!scanInProgress) {
            bluetoothCommManager.scanForFreematicsDevice(scanResult);
            scanInProgress = true;
        }
    }

    void handleExiting() {
        if (scanInProgress) {
            bluetoothCommManager.stopScanning();
        }
        if (mIsBound && isServiceConnected) {
            if (obdServiceConnection != null) {
                unbindService(obdServiceConnection);
            }
            if (obdServiceIntent != null) {
                stopService(obdServiceIntent);
            }
        }
    }

    void exitApp() {
        handleExiting();
        Intent anIntent = new Intent(MainActivity.this, SelectProvisioningActivity.class);
        anIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        anIntent.putExtra("EXIT", true);
        startActivity(anIntent);
    }

    void resetApp() {
        handleExiting();
        SharedPreferences sharedPref = getApplicationContext().getSharedPreferences(
                getString(R.string.preference_file_key), Context.MODE_PRIVATE);
        SharedPreferences.Editor prefsEditor = sharedPref.edit();
        prefsEditor.remove(getString(R.string.ta_file_path));
        deleteFile(getString(R.string.last_known));
        prefsEditor.apply();
        Intent anIntent = new Intent(MainActivity.this, SelectProvisioningActivity.class);
        anIntent.putExtra("RESET", true);
        startActivity(anIntent);
        finish();
    }

    HashMap<String,ArrayList<TextView>> mapData = new HashMap<String, ArrayList<TextView>>();

    private void createTableView() {
        tableLayout = (TableLayout) findViewById(R.id.tableLayout);
        GradientDrawable gd=new GradientDrawable();
        gd.setStroke(2, Color.BLUE);

        TableRow headRow = new TableRow(this);
        //headRow.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT, TableRow.LayoutParams.WRAP_CONTENT));
        headRow.setBackground(gd);
        headRow.setBackgroundColor(Color.rgb(0,191,255));
        //headRow.setShowDividers(TableRow.SHOW_DIVIDER_MIDDLE);
        headRow.setGravity(Gravity.CENTER);

        TableRow.LayoutParams llp = new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT);
        llp.setMargins(10, 0, 2, 0);//2px right-margin

        TextView headerAttr = new TextView(this);
        headerAttr.setTextColor(Color.WHITE);
        headerAttr.setGravity(Gravity.CENTER);
        headerAttr.setText("Attribute");
        headRow.addView(headerAttr);// add the column to the table row here

        TextView headerData = new TextView(this);
        headerData.setTextColor(Color.WHITE);
        headerData.setGravity(Gravity.CENTER);
        headerData.setText("Value");
        headRow.addView(headerData);// add the column to the table row here

        TextView headerUnit = new TextView(this);
        headerUnit.setTextColor(Color.WHITE);
        headerUnit.setGravity(Gravity.CENTER);
        headerUnit.setText("Units");
        headRow.addView(headerUnit);// add the column to the table row here

        tableLayout.addView(headRow, llp);

        GradientDrawable txtgd = new GradientDrawable();
        txtgd.setColor(0xFFD3D3D3); // Changes this drawbale to use a single color instead of a gradient
        txtgd.setCornerRadius(5);
        txtgd.setStroke(1, 0xFF000000);

        GradientDrawable valuegd = new GradientDrawable();
        valuegd.setColor(0xFFadd8e6); // Changes this drawbale to use a single color instead of a gradient
        valuegd.setCornerRadius(5);
        valuegd.setStroke(1, 0xFF000000);

        for (ObdPID pid: ObdPID.values()) {
            if (!pid.isAttribute()) continue;
            TableRow tableRow = new TableRow(this);
            //tableRow.setBackgroundColor(Color.rgb(0xBC,0xDF,0xF2));

            TextView lbl = new TextView(this);
            //lbl.setLayoutParams(llp);
            //lbl.setPadding(10,0,10,0);
            String desc = pid.getDescription();
            //lbl.setBackground(txtgd);
            lbl.setTextColor(Color.BLACK);
            lbl.setGravity(Gravity.LEFT);
            lbl.setText(desc);
            tableRow.addView(lbl);// add the column to the table row here

            TextView data = new TextView(this);
            data.setPadding(10,0,10,0);
            data.setBackground(valuegd);
            data.setGravity(Gravity.RIGHT);
            data.setTextColor(Color.BLACK);
            data.setText("");
            tableRow.addView(data);// add the column to the table row here

            TextView unitSystem = new TextView(this);
            unitSystem.setPadding(10,0,10,0);
            unitSystem.setBackground(txtgd);
            unitSystem.setGravity(Gravity.LEFT);
            unitSystem.setTextColor(Color.BLACK);
            unitSystem.setText("");
            tableRow.addView(unitSystem);// add the column to the table row here

            tableRow.setBackground(gd);

            ArrayList<TextView> views = new ArrayList<TextView>();
            views.add(lbl);
            views.add(data);
            views.add(unitSystem);
            mapData.put(pid.getDescription(), views);

            tableLayout.addView(tableRow, llp);
        }
    }

    private boolean isTableCreated = false;

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

        setContentView(R.layout.activity_main);

        sharedPref = getApplicationContext().getSharedPreferences(
                getString(R.string.preference_file_key), Context.MODE_PRIVATE);
        if (sharedPref.getBoolean(getString(R.string.use_provided_bks), false)) {
            //resetButton.setVisibility(View.GONE);
        }

        bluetoothCommManager = BluetoothCommManager.getInstance();
        bluetoothCommManager.setActivity(this);
        boolean isBluetoothAvailable = bluetoothCommManager.isBluetoothAvailable();
        if (!isBluetoothAvailable) {
            AlertDialog.Builder alert = new AlertDialog.Builder(this);
            String msg = "Bluetooth functionality is not Available in this phone\n";
            msg += "OBD Application needs Bluetooth to receive OBD data";
            alert.setMessage(msg);
            alert.show();
            return;
        }

        boolean isBluetoothInitialized = bluetoothCommManager.initialize();
        if (!isBluetoothInitialized) {
            AlertDialog.Builder alert = new AlertDialog.Builder(this);
            String msg = "Bluetooth could not be enabled in this phone\n";
            msg += "OBD Application needs Bluetooth to receive OBD data";
            alert.setMessage(msg);
            alert.show();
            return;
        }

        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        int currentapiVersion = Build.VERSION.SDK_INT;
        if (currentapiVersion >= 23) { // using 23 since definition for marshmallow is not available in current sdk
            // only applies to marshmallow and above versions
            Intent intent = new Intent();
            String packageName = this.getPackageName();
            if (pm.isIgnoringBatteryOptimizations(getPackageName())) {
                intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
            } else {
                intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
                intent.setData(Uri.parse("package:" + packageName));
            }
            startActivity(intent);
        }

        // Configure text views
        mStatus = (TextView) findViewById(R.id.textStatus);
        mStatus.setMovementMethod(new ScrollingMovementMethod());
        mStatus.setVerticalScrollBarEnabled(true);
        mStatus.setScrollbarFadingEnabled(false);
        Button resetButton = (Button) findViewById(R.id.resetButton);
        Button exitButton = (Button)findViewById(R.id.exitButton);
        final SharedPreferences sharedPref = getApplicationContext().getSharedPreferences(
                getString(R.string.preference_file_key), Context.MODE_PRIVATE);
        if(sharedPref.getBoolean(getString(R.string.use_provided_bks), false)){
            resetButton.setVisibility(View.GONE);
        }
        resetButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
                    resetApp();
                }else{
                    AlertDialog dialog;
                    AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                    builder  = builder.setMessage("Application provisioining information will be lost and cannot be recovered. Continue?");
                    builder = builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            // User clicked Try again button
                            dialog.dismiss();
                            resetApp();
                        }
                    });
                    builder = builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            // User cancelled the dialog
                            dialog.dismiss();
                        }
                    });
                    dialog = builder.create();
                    dialog.show();
                }
            }
        });

        exitButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handleExiting();
                Intent anIntent = new Intent(MainActivity.this, SelectProvisioningActivity.class);
                anIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                anIntent.putExtra("EXIT", true);
                startActivity(anIntent);
                finish();
            }
        });

        if (!isTableCreated) {
            createTableView();
            isTableCreated = true;
        }
        displayInfo("INFO: Checking permissions\n");
        checkPermissions();
        displayInfo("INFO: Scanning for OBD Devices.. \n");
        startScan();
    }

    void displayInfo(final String msg) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                String newMsg = msg.endsWith("\n") ? msg: msg + "\n";
                Spannable word = new SpannableString(newMsg);
                int color = msg.startsWith("ERROR:")? Color.RED : Color.BLUE ;
                word.setSpan(new ForegroundColorSpan(color), 0,
                        newMsg.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE );
                mStatus.append(word);
            }
        });
    }

    void startObdService(ObdSensor.ObdDeviceType type, BluetoothDevice device) {
        obdServiceIntent = new Intent(MainActivity.this, ObdDeviceService.class);
        obdServiceIntent.putExtra("ObdDevice", device);
        obdServiceIntent.putExtra("ObdDeviceType", type.name());
        startService(obdServiceIntent);

        obdServiceIntent.setAction(IObdDeviceService.class.getName());
        isServiceConnected = bindService(obdServiceIntent, obdServiceConnection, Context.BIND_AUTO_CREATE);

        mIsBound = true;
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    /**
     * Class for interacting with the main interface of the DCD Sample service.
     */
    private ServiceConnection obdServiceConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                                       IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            obdService = IObdDeviceService.Stub.asInterface(service);
            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                obdService.registerCallback(obdCallback);
            } catch (RemoteException e) {
                // In this case the service has crashed before we could even
                // do anything with it; we can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(MainActivity.this, R.string.obd_service_connected,
                    Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            obdService = null;
            // As part of the sample, tell the user what happened.
            Toast.makeText(MainActivity.this, R.string.obd_service_disconnected,
                    Toast.LENGTH_SHORT).show();
        }
    };

    /**
     * This implementation is used to receive callbacks from the OBD sample
     * service.
     */
    private IObdDeviceServiceCallback obdCallback =
            new IObdDeviceServiceCallback.Stub() {
                /**
                 * This is called by the remote service regularly to tell us about
                 * new values.
                 */
                public void statusChanged(String value) {
                    mHandler.sendMessage(mHandler.obtainMessage(STATUS_MSG, value));
                }
                /**
                 * This is called by the remote service regularly to tell us about
                 * new values.
                 */
                public void valueChanged(String value) {
                    mHandler.sendMessage(mHandler.obtainMessage(DATA_MSG, value));
                }

                /**
                 * This is called by the remote service to display error messages.
                 */
                public void errorMessage(String value) {
                    mHandler.sendMessage(mHandler.obtainMessage(ERROR_MSG, value));
                }

            };


    private static final int DATA_MSG = 1;
    private static final int STATUS_MSG = 2;
    private static final int ERROR_MSG = 4;

    void clearHighlighting() {
        for (ObdPID pid: ObdPID.values()) {
            String attr = pid.getDescription();
            ArrayList<TextView> list= mapData.get(attr);
            if (list != null) {
                for (TextView tv: list) {
                    tv.setTypeface(tv.getTypeface(),Typeface.NORMAL);
                }
                tableLayout.invalidate();
            }
        }
    }

    void processDataMsg(final String dataMsg) {
        clearHighlighting();
        if (dataMsg.contains("\n")) {
            String messages[] = dataMsg.split("\n");
            if (messages != null && messages.length > 0) {
                for (String msg: messages) {
                    String data[] = msg.split(",");
                    if (data.length == 3) {
                        ArrayList<TextView> views = mapData.get(data[0]);
                        if (views != null && views.size() == 3) {
                            for (int i=0; i< views.size(); i++) {
                                TextView tv = views.get(i);
                                tv.setTypeface(tv.getTypeface(), Typeface.BOLD);
                                tv.setText(data[i]);
                            }
                        }
                    }
                }
                tableLayout.invalidate();
            }
        }
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case STATUS_MSG: {
                    clearHighlighting();
                    String newMsg = msg.obj.toString();
                    if (newMsg != null) {
                        displayInfo(newMsg);
                    }
                    break;
                }
                case DATA_MSG: {
                    String newMsg = msg.obj.toString();
                    if (newMsg != null) {
                        processDataMsg(newMsg);
                    }
                    break;
                }
                case ERROR_MSG: {
                    // As part of the sample, tell the user what happened.
                    String errorMsg = msg.obj.toString();
                    mStatus.setTextColor(Color.RED);
                    if (errorMsg != null) {
                        displayInfo("ERROR: " + errorMsg);
                    } else {
                        displayInfo("Unkown ERROR: ");
                    }
                    break;
                }
                default:
                    super.handleMessage(msg);
            }
        }

    };

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

    }

    @Override
    public void onStop() {
        super.onStop();
    }

}
