/*
 * Copyright (c) 2016 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.app.Activity;
import android.app.AlertDialog;
import android.app.FragmentManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
import android.os.PowerManager;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.security.GeneralSecurityException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Locale;

import oracle.iot.client.DeviceModel;
import oracle.iot.client.device.GatewayDevice;
import oracle.iot.client.device.VirtualDevice;

public class MainActivity extends Activity {
    private final static String IBEACON_MODEL_URN = "urn:com:oracle:iot:device:location:ibeacon";
    private final static String EDDYSTONE_MODEL_URN = "urn:com:oracle:iot:device:location:eddystone-tlm-uid";
    private BluetoothAdapter mBluetoothAdapter;
    private int REQUEST_ENABLE_BT = 1;
    private final int REQUEST_LOCATION = 2;
    private Handler mHandler; // UI Thread handler
    private static final long SCAN_PERIOD = 5000;
    private BluetoothLeScanner mLEScanner;
    private ScanSettings settings;
    private List<ScanFilter> filters;
    TextView mScanResultsOutput;
    private ArrayList<Beacon> beaconsFound = new ArrayList<Beacon>();
    static boolean firstCreate = true;
    BeaconListAdapter dataAdapter = null;
    Context context;
    ArrayList<BeaconListItem> beaconList;
    static Beacon beaconToMonitor = null;

    /**
     * Only information from beacons currently within this maximum distance in meters
     * will be reported.
     */
    private static final double MAX_DISTANCE = 100.0;

    /**
     * Set simulated to true to use simulated beacons or false for real beacons.
     */
    private final static boolean simulated = false;

    /**
     * Number of simulated beacons to create when doing beacon simulation.
     */
    private static final byte DEFAULT_NUM_BEACONS = 2;

    private GatewayDevice gatewayDevice;
    private DeviceModel ibeaconDeviceModel;
    private DeviceModel eddystoneDeviceModel;
    private boolean beaconListUpdated = false;

    private RetainedFragment dataFragment;
    PowerManager.WakeLock wakeLock;
    private static WeakReference<Activity> mActivityRef;
    public static void updateActivity(Activity activity) {
        mActivityRef = new WeakReference<Activity>(activity);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = this;
        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
            if (firstCreate) {
                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);
                firstCreate = false;
            }

            //Must request location privilege to use bluetooth scanning for marshmallow
            if (!simulated) {
                if (ContextCompat.checkSelfPermission(this,
                        Manifest.permission.ACCESS_COARSE_LOCATION)
                        != PackageManager.PERMISSION_GRANTED) {

                   ActivityCompat.requestPermissions(this,
                                new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
                                REQUEST_LOCATION);
                }
            }
        }
        wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                "IoTWakeLock");
        // power management: Keep the CPU on so we can continue to send messages back to the server
        wakeLock.acquire();

        // Configure text view
        mHandler = new Handler();

        if (!simulated) {
            if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
                Toast.makeText(this, "BLE Not Supported",
                        Toast.LENGTH_SHORT).show();
                finish();
            }
            final BluetoothManager bluetoothManager =
                    (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
            mBluetoothAdapter = bluetoothManager.getAdapter();
        }

        FragmentManager fm = getFragmentManager();
        dataFragment = (RetainedFragment)fm.findFragmentByTag("beaconData");
        if (dataFragment == null) {
            dataFragment = new RetainedFragment();
            fm.beginTransaction().add(dataFragment, "beaconData").commit();
        }
        else {
            beaconsFound = dataFragment.getBeaconsFound();
        }

        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 provisioning 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();
            }
        });

        new GatewayManager().execute(this);
    }

    void resetApp() {
        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));
        prefsEditor.apply();
        deleteFile(getString(R.string.last_known));
        Intent anIntent = new Intent(MainActivity.this, SelectProvisioningActivity.class);
        anIntent.putExtra("RESET", true);
        startActivity(anIntent);
        finish();
    }

    void addBeaconToList(Beacon newBeacon)throws Exception{
        boolean existingBeaconFound = false;
        beaconListUpdated = true;
        uiHandler.sendEmptyMessage(1);
        for(int i = 0 ; i < beaconsFound.size(); i++){
            Beacon abeacon = beaconsFound.get(i);
            if (newBeacon.getIdentifier().equals(abeacon.getIdentifier())) {
                abeacon.setRssi(newBeacon.getRssi());
                Log.i("Beacon Smoothing Data", abeacon.getIdentifier()+" Raw: "+newBeacon.getRssi() + " Avg: "+abeacon.getRssi());
                existingBeaconFound = true;
                break;
            }
        }
        if(!existingBeaconFound){
            beaconsFound.add(newBeacon);
        }
        new RegistrationManager().execute(this);
    }

    void updateTLMData(byte[] packet, String address)throws Exception{
        beaconListUpdated = true;
        uiHandler.sendEmptyMessage(1);
        for(int i = 0 ; i < beaconsFound.size(); i++){
            Beacon abeacon = beaconsFound.get(i);
            if (abeacon.getType() == Beacon.Type.EDDYSTONE && abeacon.getAddress().equals(address)) {
                ((EddystoneBeacon)abeacon).setTLM(packet);
                Log.i("Beacon TLM Data", abeacon.getIdentifier()
                        + "Temp: " + ((EddystoneBeacon)abeacon).getTemperature()
                        + "Voltage: " + ((EddystoneBeacon)abeacon).getVoltage());
                break;
            }
        }
        new RegistrationManager().execute(this);
    }

    public void registerBeacon(Beacon beacon)throws Exception{
        final String beaconEndpointId;
        VirtualDevice virtualizedBeacon;
        if(beacon.isRegistered)return;
        Map<String, String> metaData = new HashMap<String, String>();
        metaData.put(
                GatewayDevice.MANUFACTURER,
                "Estimote");
        metaData.put(
                GatewayDevice.DEVICE_CLASS,
                "LE");
        metaData.put(
                GatewayDevice.PROTOCOL,
                "Bluetooth-LE");
        metaData.put(
                "UUID",
                beacon.getUUID());
        if (beacon.getType() == Beacon.Type.IBEACON) {
            metaData.put(
                    "major",
                    ((IBeacon)beacon).getMajorVersion());
            metaData.put(
                    "minor",
                    ((IBeacon)beacon).getMinorVersion());
        }
        metaData.put(
                GatewayDevice.PROTOCOL_DEVICE_ID,
                beacon.getAddress());
        metaData.put(
                GatewayDevice.PROTOCOL_DEVICE_CLASS,
                beacon.getType().alias());
        metaData.put(
                GatewayDevice.MODEL_NUMBER,
                beacon.getModelNumber());
        metaData.put(
                GatewayDevice.SERIAL_NUMBER,
                beacon.getSerialNumber());

        try{
            if (beacon.getType() == Beacon.Type.IBEACON) {
                beaconEndpointId =
                        gatewayDevice.registerDevice(
                                false, // Allow other Gateways to send message on behalf of this device
                                beacon.getIdentifier(),
                                metaData,
                                IBEACON_MODEL_URN);
                virtualizedBeacon =
                        gatewayDevice.createVirtualDevice(
                                beaconEndpointId,
                                ibeaconDeviceModel);
            }
            else if(beacon.getType() == Beacon.Type.EDDYSTONE) {
                beaconEndpointId =
                        gatewayDevice.registerDevice(
                                false, // Allow other Gateways to send message on behalf of this device
                                beacon.getIdentifier(),
                                metaData,
                                EDDYSTONE_MODEL_URN);
                virtualizedBeacon =
                        gatewayDevice.createVirtualDevice(
                                beaconEndpointId,
                                eddystoneDeviceModel);
            }
            else {
                Log.w("Beacon type unknown", beacon.getType().alias());
                return;
            }
        }catch(Exception e){
            e.printStackTrace();
            return;
        }

        beacon.setVirtualizedBeacon(virtualizedBeacon);
        beacon.isRegistered = true;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // power management: let the CPU go to sleep
        if (wakeLock != null) wakeLock.release();
        dataFragment.setBeaconsFound(beaconsFound);
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_ENABLE_BT) {
            if (resultCode == Activity.RESULT_CANCELED) {
                //Bluetooth not enabled.
                finish();
                return;
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        switch (requestCode) {
            case REQUEST_LOCATION: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                } else {
                    // permission denied
                    finish();
                    return;
                }
                return;
            }
        }
    }

    private void scanLeDevice(final boolean enable) {
        if (enable) {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mLEScanner.stopScan(mScanCallback);
                    scanLeDevice(true);
                }
            }, SCAN_PERIOD);
            mLEScanner.startScan(filters, settings, mScanCallback);
        } else {
            mLEScanner.stopScan(mScanCallback);
        }
    }

    private ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            BluetoothDevice btDevice = result.getDevice();
            ScanRecord scanRecord = result.getScanRecord();
            byte[] rawBytes = scanRecord.getBytes();
            Beacon.Type type = Beacon.getPacketType(rawBytes);
            Beacon aBeacon;
            try{
                if (type == Beacon.Type.IBEACON) {
                    aBeacon = IBeacon.getInstance(rawBytes, btDevice.getAddress(), btDevice.getName());
                } else if (type == Beacon.Type.EDDYSTONE) {
                    aBeacon = EddystoneBeacon.getInstance(rawBytes, btDevice.getAddress(), btDevice.getName());
                } else if (type == Beacon.Type.EDDYSTONE_TLM) {
                    updateTLMData(rawBytes, btDevice.getAddress());
                    return;
                } else {
                    return;
                }
                aBeacon.setRssi(result.getRssi());
                addBeaconToList(aBeacon);
            }catch(Exception e) {
                // todo: Handle exception here
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            mScanResultsOutput.setText("Scan Failed: Error Code: " + errorCode);
        }
    };

    class GatewayManager extends AsyncTask<Context, String, String> {

        private Exception exception;

        protected String doInBackground(Context... params) {
            try {
                SharedPreferences mSharedPref = getApplicationContext().getSharedPreferences(
                        getString(R.string.preference_file_key), Context.MODE_PRIVATE);
                if(mSharedPref.getBoolean(getString(R.string.use_provided_bks), false)) {
                    Log.d("BGS_DEBUG", "Using existing BKS");
                    gatewayDevice = new GatewayDevice(params[0]);
                }else{
                    Log.d("BGS_DEBUG", "Using BKS" + mSharedPref.getString(getString(R.string.ta_file_path),"") + "---" + mSharedPref.getString(getString(R.string.ta_password),"") + " --------");
                    SharedPreferences pref = getApplicationContext().getSharedPreferences(
                        getString(R.string.preference_file_key), Context.MODE_PRIVATE);
                    try (FileOutputStream fos = openFileOutput(getString(R.string.last_known), Context.MODE_PRIVATE)) {
                        fos.write(pref.getString(getString(R.string.ta_file_path), "").getBytes());
                    } catch (IOException ex) {
                        Log.d("IOT_ERROR", "Can not safe to internal storage");
                    }
                    gatewayDevice = new GatewayDevice(mSharedPref.getString(getString(R.string.ta_file_path),""), mSharedPref.getString(getString(R.string.ta_password),""));
                }

                if (!gatewayDevice.isActivated()) {
                    // If the device has not been activated, connect to the server
                    // using client-credentials and activate the client to
                    // obtain the private key. The private key is then persisted.
                    gatewayDevice.activate();
                }
                // Get the device model instance.
                ibeaconDeviceModel =
                        gatewayDevice.getDeviceModel(IBEACON_MODEL_URN);
                eddystoneDeviceModel =
                        gatewayDevice.getDeviceModel(EDDYSTONE_MODEL_URN);
                if (!simulated) {
                    if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
                        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
                    } else {
                        if (Build.VERSION.SDK_INT >= 21) {
                            mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
                            settings = new ScanSettings.Builder()
                                    .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                                    .build();
                            filters = new ArrayList<ScanFilter>();
                        }
                        scanLeDevice(true);
                    }
                } else {
                    if (beaconsFound.size() == 0) {
                        String beaconUUID = gatewayDevice.getEndpointId() + "_Sample";
                        for (byte i = 0; i < DEFAULT_NUM_BEACONS; i++) {
                            Beacon aBeacon;
                            if (i % 2 == 0) {
                                aBeacon = IBeacon.getSimulatedInstance(i, beaconUUID);
                            } else {
                                aBeacon = EddystoneBeacon.getSimulatedInstance(i, beaconUUID);
                            }
                            addBeaconToList(aBeacon);
                        }
                    }
                    simulationTask.run();
                }

            } catch (GeneralSecurityException dse) {
                resetApp();
                return "Problem initializing gateway.";
            }catch (IOException dse) {
                System.exit(1);
            }catch (Exception dse) {
                System.exit(1);
            }
            return null;
        }

        protected void onPostExecute(String result) {
            if (result != null) {
                Toast.makeText(getApplicationContext(), result, Toast.LENGTH_SHORT).show();
            }
        }
    }


    Runnable simulationTask = new Runnable() {
        @Override
        public void run() {
            try{
                new RegistrationManager().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
                beaconListUpdated = true;
                uiHandler.sendEmptyMessage(1);
            }
            catch (Exception e) {
                Log.e("error", "could not execute registration manager.");
            }
            finally{
                //also call the same runnable to call it at regular interval
                uiHandler.postDelayed(this, 2000);
            }
        }
    };

    class RegistrationManager extends AsyncTask<Context, String, Void> {

        private Exception exception;

        protected Void doInBackground(Context... params) {
            try {
                for(int i = 0 ; i < beaconsFound.size(); i++){
                    Beacon abeacon = beaconsFound.get(i);
                    if(!abeacon.isRegistered){
                        registerBeacon(abeacon);
                        abeacon.getVirtualizedBeacon().update().set("ora_txPower", abeacon.getMssi()).finish();
                        Log.i("Beacon Server Msg Sent", abeacon.getIdentifier()+" ora_tx_Power: "+ abeacon.getMssi());
                    }else {
                        //Only send data for beacons within the defined range
                        if (abeacon.getDistance() <= MAX_DISTANCE) {
                            //Check if enough RSSI data has been received to send smoothed data
                            if (abeacon.isReadyToSend()) {
                                if (abeacon.getType() == Beacon.Type.IBEACON) {
                                    //Send the smoothed RSSI data
                                    int rssi = abeacon.getRssi();
                                    String currDate =  new SimpleDateFormat("HH:mm:ss", Locale.ROOT).format(new Date()).toString();
                                    abeacon.getVirtualizedBeacon().update().set("ora_rssi", rssi).finish();
                                    Log.i("Beacon Server Msg Sent", abeacon.getIdentifier() + " ora_rssi: " + rssi);
                                    if (BeaconDetailActivity.active && beaconToMonitor != null && abeacon.getIdentifier().equals(beaconToMonitor.getIdentifier())) {
                                        Message message = ((BeaconDetailActivity)mActivityRef.get()).mHandler.obtainMessage();
                                        Bundle bundle = new Bundle();
                                        bundle.putString("beaconInfo", Beacon.parseSensorInfo(abeacon));
                                        bundle.putString("output", currDate + " : Set : \"ora_rssi\"=" + rssi + "\n");
                                        message.setData(bundle);
                                        ((BeaconDetailActivity)mActivityRef.get()).mHandler.sendMessage(message);
                                    }
                                } else if (abeacon.getType() == Beacon.Type.EDDYSTONE) {
                                    //Send the smoothed RSSI data and most recently received telemetry data
                                    int rssi = abeacon.getRssi();
                                    double temp = ((EddystoneBeacon)abeacon).getTemperature();
                                    double volt = ((EddystoneBeacon)abeacon).getVoltage();
                                    String currDate =  new SimpleDateFormat("HH:mm:ss", Locale.ROOT).format(new Date()).toString();
                                    abeacon.getVirtualizedBeacon().update()
                                            .set("ora_rssi", rssi)
                                            .set("temperature", temp)
                                            .set("batteryVoltage", volt)
                                            .finish();
                                    Log.i("Beacon Server Msg Sent", abeacon.getIdentifier()
                                            + " ora_rssi: " + rssi
                                            + " temperature: " + temp
                                            + " batteryVoltage: " + volt);
                                    if (BeaconDetailActivity.active && beaconToMonitor != null && abeacon.getIdentifier().equals(beaconToMonitor.getIdentifier())) {
                                        Message message = ((BeaconDetailActivity)mActivityRef.get()).mHandler.obtainMessage();
                                        Bundle bundle = new Bundle();
                                        bundle.putString("beaconInfo", Beacon.parseSensorInfo(abeacon));
                                        bundle.putString("output", currDate + " : Set : \"ora_rssi\"=" + rssi
                                                + " \"temperature\"=" + temp
                                                + " \"batteryVoltage\"= " + volt + "\n");
                                        message.setData(bundle);
                                        ((BeaconDetailActivity)mActivityRef.get()).mHandler.sendMessage(message);
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (GeneralSecurityException dse) {
                System.exit(1);
            }catch (IOException dse) {
                System.exit(1);
            }catch (Exception dse) {
                System.exit(1);
            }
            return null;
        }

        protected void onPostExecute() {
            // TODO: check this.exception
            // TODO: do something with the feed
        }
    }

    private Handler uiHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if(msg.what != 1)return;
            if (beaconListUpdated) {
                beaconList = new ArrayList<BeaconListItem>();

                Collections.sort(beaconsFound, new BeaconComparator());
                for (int i = 0; i < beaconsFound.size(); i++) {
                    Beacon abeacon = beaconsFound.get(i);
                    if (abeacon.getDistance() > MAX_DISTANCE)
                        break;
                    BeaconListItem bli = new BeaconListItem();
                    bli.id = abeacon.getIdentifier();
                    bli.currentDistance = abeacon.getDistance();
                    bli.beaconInfo = abeacon.toString();
                    beaconList.add(bli);
                }
                beaconListUpdated = false;

                dataAdapter = new BeaconListAdapter(context,
                        R.layout.beacon_list_line_item, beaconList);
                ListView listView = (ListView) findViewById(R.id.beaconList);
                Parcelable state = listView.onSaveInstanceState();
                // Assign adapter to ListView
                listView.setAdapter(dataAdapter);
                //Restore scroll position
                listView.onRestoreInstanceState(state);
                listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                    public void onItemClick(AdapterView<?> parent, View view,
                                            int position, long id) {
                        beaconToMonitor = beaconsFound.get(position);

                        // start monitoring activity
                        Intent intent = new Intent(context, BeaconDetailActivity.class);
                        startActivity(intent);
                    }
                });
            }
        }

    };

    private class BeaconListAdapter extends ArrayAdapter<BeaconListItem> {

        private ArrayList<BeaconListItem> beaconList;

        public BeaconListAdapter(Context context, int textViewResourceId, ArrayList<BeaconListItem> bList) {
            super(context, textViewResourceId, bList);
            beaconList = new ArrayList<BeaconListItem>();
            beaconList.addAll(bList);
        }

        private class ViewHolder {
            TextView name;

        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            BeaconListAdapter.ViewHolder holder = null;

            if (convertView == null) {
                LayoutInflater vi = (LayoutInflater)getSystemService(
                        Context.LAYOUT_INFLATER_SERVICE);
                convertView = vi.inflate(R.layout.beacon_list_line_item, null);

                holder = new BeaconListAdapter.ViewHolder();
                holder.name = (TextView) convertView.findViewById(R.id.beaconName);
                convertView.setTag(holder);

                holder.name.setOnClickListener( new View.OnClickListener() {
                    public void onClick(View v) {
                        TextView tv = (TextView) v ;
                        String id = ((BeaconListItem)tv.getTag()).id;

                        for(int i = 0 ; i < beaconsFound.size(); i++){
                            Beacon abeacon = beaconsFound.get(i);
                            if (id.equals(abeacon.getIdentifier())) {
                                beaconToMonitor = abeacon;
                                break;
                            }
                        }
                        // start monitoring activity
                        Intent intent = new Intent(context, BeaconDetailActivity.class);
                        startActivity(intent);
                    }
                });
            }
            else {
                holder = (BeaconListAdapter.ViewHolder) convertView.getTag();
            }

            BeaconListItem beaconListItem = beaconList.get(position);
            String currentValue = String.format(Locale.ROOT, "\n   %7.3f meters away", beaconListItem.currentDistance);
            String beaconID = beaconListItem.id;
            holder.name.setText( beaconID+ " " + currentValue + "   >");
            holder.name.setTag(beaconListItem);
            return convertView;
        }
    }

    class BeaconComparator implements Comparator<Beacon> {
        @Override
        public int compare(Beacon b1, Beacon b2) {
            return new Double(b1.getDistance()).compareTo(new Double(b2.getDistance()));
        }
    }
}
