/*
 * 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.Activity;
import android.app.AlertDialog;
import android.app.PendingIntent;
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.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * This the main activity of the application which shows NFC tag id and location values.
 */
public class MainActivity extends Activity {
    /**
     * The primary interface we will be calling on the GD sample service.
     */
    IGatewayDeviceSampleService gdService = null;
    /**
     * Another interface we use on the GD sample service.
     */
    IGatewayDeviceSampleServiceSecondary gdSecondaryService = null;
    TextView sensorInfoText1;
    TextView statusText;
    TextView mCallbackText;

    private NfcAdapter nfcAdapter;
    private boolean isInitialized = false;

    Intent serviceIntent;
    private String startTime;
    private final NFCSensor sensor = NFCSensor.getInstance();
    Location currentLocation;
    LocationManager locationManager;
    boolean isGatewayServiceCreated = false;
    LocationListener locationListener;
    public static final int REQUEST_GPS=1;
    public static final int REQUEST_NFC=2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        int currentapiVersion = android.os.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);
        }

        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) {
                Intent anIntent = new Intent(MainActivity.this, SelectProvisioningActivity.class);
                anIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                anIntent.putExtra("EXIT", true);
                startActivity(anIntent);
                finish();
            }
        });

        // Configure text view
        mCallbackText = (TextView)findViewById(R.id.Output);
        sensorInfoText1= (TextView) findViewById(R.id.sensorinfo1);
        statusText = (TextView) findViewById(R.id.sensorinfo2);
        SimpleDateFormat fmt = new SimpleDateFormat("MM/dd/yy, hh:mm:ss a", Locale.ROOT);
        startTime = fmt.format(new Date());

        isInitialized = initializeNFCAndGPS();
        if (isInitialized) {
            startNFCGatewayService();
            handleIntent(getIntent());
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (isInitialized && isNFCEnabled() && isGPSEnabled()) {
            setupForegroundDispatch(this, nfcAdapter);
        }
    }

    @Override
    protected void onPause() {
        if (isInitialized && isNFCEnabled() && isGPSEnabled()) {
            stopForegroundDispatch(this, nfcAdapter);
        }
        super.onPause();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        handleIntent(intent);
    }

    private boolean initializeNFCAndGPS() {
        boolean isNFCCapable = false;
        boolean isGPSCapable = false;
        // Check if NFC is supported by the phone
        nfcAdapter = NfcAdapter.getDefaultAdapter(this);
        if (nfcAdapter == null) {
            String msg = "This device doesn't support NFC. This application requires NFC capable phone.";
            Toast.makeText(this,msg , Toast.LENGTH_LONG).show();
            displayError(msg);
        } else {
            isNFCCapable = true;
            // Check if NFC permissions are granted.
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.NFC)
                    != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.NFC}, REQUEST_NFC);
            }
        }
        // Check if GPS is supported by the phone
        locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
        if (locationManager == null) {
             String msg = "Location service not available in this phone.";
             Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
             displayError(msg);
        } else {
            isGPSCapable = true;
            boolean isGPSEnabled = isGPSEnabled();
            if (isGPSEnabled) {
                if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                        == PackageManager.PERMISSION_GRANTED) {
                    locationListener = new LocationListener() {
                        @Override
                        public void onLocationChanged(Location location) {
                            currentLocation.set(location);
                        }
                        public void onStatusChanged(String provider, int status, Bundle extras) {}
                        public void onProviderEnabled(String provider) {}
                        public void onProviderDisabled(String provider) {}
                    };
                    locationManager.requestLocationUpdates(
                            LocationManager.GPS_PROVIDER, 0, 0, locationListener);
                    findCurrentLocation();
                } else {
                    ActivityCompat.requestPermissions(this,
                            new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_GPS);
                }
            }
        }
        return isNFCCapable && isGPSCapable;
    }

    private boolean isNFCEnabled() {
        boolean nfsEnabled = false;
        if (nfcAdapter!= null && !nfcAdapter.isEnabled()) {
            String msg = "NFC is disabled. Please enable NFC in Settings";
            Toast.makeText(this,msg , Toast.LENGTH_LONG).show();
        } else {
            nfsEnabled = true;
        }
        return nfsEnabled;
    }

    private boolean isGPSEnabled() {
        boolean isGPSEnabled = false;
        if (locationManager != null) {
            isGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
            if (!isGPSEnabled) {
                String msg = "GPS Location service not enabled. Please enable GPS location in settings";
                Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
                displayError(msg);
            }
        }
        return isGPSEnabled;
    }

    private void findCurrentLocation() {
        // Get the current/last known location.
        currentLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
        if (currentLocation == null) {
            currentLocation = locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER);
            if (currentLocation == null) {
                currentLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
                if (currentLocation == null) {
                    String msg = "Current device location is unknown. Please check if GPS is enabled";
                    Toast.makeText(this,msg , Toast.LENGTH_LONG).show();
                    displayError(msg);
                }
            }
        }
    }

    private void handleIntent(Intent intent) {
        if (sensor.isProximityIntent(intent)) {
            handleProximityIntent(intent);
        }
    }

    private void startNFCGatewayService() {
        serviceIntent = new Intent(MainActivity.this, NFCGatewayService.class);
        startService(serviceIntent);

        // Establish connections with the service, binding
        // by interface names.  This allows other applications to be
        // installed that replace the remote service by implementing
        // the same interface.
        Intent intent = new Intent(MainActivity.this, NFCGatewayService.class);
        intent.setAction(IGatewayDeviceSampleService.class.getName());
        bindService(intent, gdServiceConnection, Context.BIND_AUTO_CREATE);
        intent.setAction(IGatewayDeviceSampleServiceSecondary.class.getName());
        bindService(intent, gdServiceSecondaryConnection, Context.BIND_AUTO_CREATE);
        isGatewayServiceCreated = true;
    }

    private void handleProximityIntent(Intent intent) {
        Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
        if (tag != null) {
            if (currentLocation != null) {
                sensor.processTagFound(tag, currentLocation);
            } else {
                displayError("Current GPS location is unknown.");
            }
        }
    }


    void resetApp(){
        unbindService(gdServiceConnection);
        unbindService(gdServiceSecondaryConnection);
        stopService(serviceIntent);
        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();
    }

    public static void setupForegroundDispatch(final Activity activity, NfcAdapter adapter) {
        final Intent intent = new Intent(activity.getApplicationContext(), activity.getClass());
        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);

        final PendingIntent pendingIntent = PendingIntent.getActivity(activity.getApplicationContext(), 0, intent, 0);
        adapter.enableForegroundDispatch(activity, pendingIntent, null, null);
    }

    public static void stopForegroundDispatch(final Activity activity, NfcAdapter adapter) {
        adapter.disableForegroundDispatch(activity);
    }


   /**
     * Class for interacting with the main interface of the GD Sample service.
     */
    private ServiceConnection gdServiceConnection = 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.
            gdService = IGatewayDeviceSampleService.Stub.asInterface(service);
            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                gdService.registerCallback(gdCallback);
            } 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.gd_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.
            gdService = null;
            // As part of the sample, tell the user what happened.
            Toast.makeText(MainActivity.this, R.string.gd_service_disconnected,
                    Toast.LENGTH_SHORT).show();
        }
    };
    /**
     * Class for interacting with the secondary interface of the GD Sample service.
     */
    private ServiceConnection gdServiceSecondaryConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                                       IBinder service) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            gdSecondaryService = IGatewayDeviceSampleServiceSecondary.Stub.asInterface(service);
        }
        public void onServiceDisconnected(ComponentName className) {
            gdSecondaryService = null;
        }
    };

    /**
     * This implementation is used to receive callbacks from the GD Sample
     * service.
     */
    private IGatewayDeviceSampleServiceCallback gdCallback =
            new IGatewayDeviceSampleServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.
         */
        public void valueChanged(String value) {
            mHandler.sendMessage(mHandler.obtainMessage(BUMP_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 BUMP_MSG = 1;
    private static final int ERROR_MSG = 6;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BUMP_MSG:
                    String message = (String) msg.obj;
                    if (message.startsWith(":") & message.length() > 1) {
                        displayStatus(message.substring(1));
                    } else {
                        mCallbackText.append(msg.obj + "\n");
                    }
                    break;
                case ERROR_MSG:
                    // As part of the sample, tell the user what happened.
                    String errorMsg = msg.obj.toString();
                    displayError(errorMsg);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }

    };
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == REQUEST_GPS) {
            if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Log.d("IOT", "GPS permission was not granted");
                displayError("ERROR: GPS Permission is not granted");
                isInitialized = false;
            }
        }
        if (requestCode == REQUEST_NFC) {
            if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Log.d("IOT", "NFC permission was not granted");
                displayError("ERROR: NFC Permission is not granted");
                isInitialized = false;
            }
        }
    }

    void displayError(String errorMsg) {
        String text = "ERROR:" + errorMsg + "\n";
        statusText.append(text);
    }
    void displayStatus(String msg) {
        String statusMsg = msg + "\n";
        statusText.append(statusMsg);
    }

}
