package com.algobase.share.bluetooth;

import java.util.List;
import java.util.ArrayList;

import android.util.SparseArray;

import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelUuid;

import android.content.Context;

import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;

import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;
import android.bluetooth.le.ScanRecord;


import com.algobase.share.system.*;


public class BluetoothScanner {

  static String HEART_RATE_SERVICE = AllGattServices.HEART_RATE;
  static String CYCLING_POWER_SERVICE = AllGattServices.CYCLING_POWER;
  static String CYCLING_SPEED_AND_CADENCE = 
                                     AllGattServices.CYCLING_SPEED_AND_CADENCE;
  static String ENVIRONMENTAL_SENSING = AllGattServices.ENVIRONMENTAL_SENSING;
  static String FITNESS_MACHINE = AllGattServices.FITNESS_MACHINE;


  public void writeLog(String txt) {}
  public void showToast(String msg) {}

  public void handleDevice(String name, String addr, int rssi, double dist)  {}
  public void handleStop(int dev_num) {}
  public void handleTimeout(int t) {}
  public void handleError(String msg) {}


  Context context;

  Handler handler;

  BluetoothManager btManager;
  BluetoothAdapter btAdapter;
  BluetoothLeScanner btLeScanner;
  ScanCallback scanCallback;

  ArrayList<BluetoothDevice> btDeviceList;

  String filterType = null;
  String filterAddress = null;

  int timeout = 0;
  boolean scanning = false;


  String byteString(byte[] bytes)
  { String bstr = "0x";
    for(int i=0; i<bytes.length; i++) bstr += String.format("%02X",bytes[i]);
    return bstr;
  }

  public BluetoothScanner(Context ctxt) 
  {
    context = ctxt;

    filterType = null;
    filterAddress = null;
    timeout = 0;
    scanning = false;

    handler = new Handler(context.getMainLooper());

    btManager = 
        (BluetoothManager)context.getSystemService(Context.BLUETOOTH_SERVICE);
    btAdapter = btManager.getAdapter();
    btLeScanner = btAdapter.getBluetoothLeScanner();
    btDeviceList = new ArrayList<BluetoothDevice>();
   }


  public void setFilter(String type)
  { filterType = type;
    filterAddress = null;
   }

  public void setFilter(String type, String address)
  { filterType = type;
    filterAddress = address;
   }

  public void setTimeout(int msec) { timeout = msec; }

  ArrayList<BluetoothDevice> getDeviceList() { return btDeviceList; }


  String parseAdvertisingData(byte[] bytes)
  {
    // return 128-bit service uuid 

    String uuid = "0000xxxx-0000-0000-0000-000000000000";

    int pos = 0;

    while (pos < bytes.length && bytes[pos] > 0)
    { int length = bytes[pos++];
      // read next AD Structure starting at pos (lengh bytes)

      //byte type = bytes[pos];
      int type = bytes[pos] & 0xff;

      String bstr = "";
      for(int i=1; i < length; i++) {
        bstr += String.format("%02X ", bytes[pos+i]);
      }

      String what = "unknown";
      if (type == 0x01) what = "Flags";

      if (type == 0x02 || type == 0x03) what = "16-bit Service UUID";
      if (type == 0x04 || type == 0x05) what = "32-bit Service UUID";
      if (type == 0x06 || type == 0x07) what = "128-bit Service UUID";

      if (type == 0x08) what = "Shortened Name";
      if (type == 0x09) what = "Device Name";
      if (type == 0x0a) what = "Tx Power Level";
      if (type == 0x16) what = "Service Data";
      if (type == 0x21) what = "Service Data UUID";
      if (type == 0x19) what = "Apperance Data";
      if (type == 0xff) what = "Manufacturer Data";

      writeLog("");
      writeLog(String.format("type: %02x %s",type,what));

      if (type >= 0x02 && type <= 0x07) {
        // service uuid

        writeLog(String.format("%02x%02x",bytes[pos+length-3],
                                          bytes[pos+length-4]));

        writeLog(String.format("%02x%02x",bytes[pos+length-1],
                                          bytes[pos+length-2]));

        uuid = String.format("0000%02x%02x-0000-0000-0000-000000000000",
                             bytes[pos+length-3], bytes[pos+length-4]);
      }
      else
      if (type == 0x08 || type == 0x09) {
        String name = new String(bytes,pos+1,length-1);
        writeLog(name);
      }
      else
        writeLog(bstr);

      pos += length;
    }

    return uuid;
  }


   public void startScan()
   { 
     //showToast("startScan: type = " + filterType);

     scanning = true;

     btDeviceList.clear();

     scanCallback = new ScanCallback() {

       @Override
       public void onBatchScanResults(List<ScanResult> results) {
         writeLog("onBatchScanResults: " + results.size() );
        }
 
       @Override
       public void onScanResult(int callbackType, final ScanResult result)
       { 
         // called for every device found

         BluetoothDevice device = result.getDevice(); 
 
         final String addr = device.getAddress();
         final String name = device.getName();
 
         if (name == null) return;

         // add device only once
         if (btDeviceList.contains(device)) return;

         btDeviceList.add(device);
 
         writeLog("");
         writeLog("Scan Result");
         writeLog(name);
         writeLog(addr);
 
         ScanRecord record = result.getScanRecord();
 
         //showToast(record.getDeviceName());
 
         byte[] bytes = record.getBytes();
         String bstr = "";
         if (bytes != null) {
           for(byte b : bytes) bstr += String.format("%02X ", b);
         }
         writeLog(bstr);
 
         final int rssi = result.getRssi();

/*
         ParcelUuid[] parcel_uuids = device.getUuids();
         String uuid = parcel_uuids[0];
         writeLog("uuid = " + uuid);
*/
         String uuid = parseAdvertisingData(bytes);

         //showToast(uuid);
 
         writeLog("UUID = " + uuid);
         writeLog("RSSI = " + rssi + " dBm");
 
         int txPower = -69;
 
         if (Build.VERSION.SDK_INT >= 26)
         { int txp = result.getTxPower();
           if (txp != ScanResult.TX_POWER_NOT_PRESENT) txPower = txp;
         }
         writeLog("txPower = " + txPower);
 
         int N = 2;
         final double dist = Math.pow(10,(txPower-rssi)/(10.0*N));
         writeLog("DIST = " + dist + " m");
 
         writeLog("Manufacturer Data");
 
         SparseArray<byte[]> manuData = record.getManufacturerSpecificData();
 
         for(int i=0; i<manuData.size(); i++)
         { int manuId = manuData.keyAt(i);
           writeLog("manuId = " + manuId);
 
           byte[] manu_bytes = record.getManufacturerSpecificData(manuId);
 
           if (manu_bytes == null) continue;
 
           String manu_str = "";
           for(byte b : manu_bytes) manu_str += String.format("%02X ", b);
           writeLog(manu_str);
 
           if (name.equals("ThermoBeacon") && manu_bytes.length == 18) 
           { float bat = (255*manu_bytes[9] + manu_bytes[10])/34.0f;
             float tmp = (255*manu_bytes[11] + manu_bytes[12])/16.0f;
             float hum = (255*manu_bytes[13] + manu_bytes[14])/16.0f;
             String txt = 
               String.format("tmp: %.1f  hum: %.1f  bat: %.0f %%",tmp,hum,bat);

             //showToast(txt);

             writeLog(txt);
             writeLog("");
           }
         }

         handler.post(new Runnable() {
             public void run() { handleDevice(name,addr,rssi,dist); }
          });
      }


      @Override
      public void onScanFailed(int errorCode) { 
        final String msg = "BT-Scan failed: err = " + errorCode;
        writeLog(msg);

        handler.post(new Runnable() {
             public void run() { handleError(msg); }
          });
      }

     };


     List<ScanFilter> filters =  new ArrayList<ScanFilter>();

     ScanFilter.Builder builder = new ScanFilter.Builder();

     ParcelUuid filterUuid = null;

     if (filterType != null)
     {  if (filterType.equals("hrt"))
          filterUuid = ParcelUuid.fromString(HEART_RATE_SERVICE);
        else if (filterType.equals("pwr"))
          filterUuid = ParcelUuid.fromString(CYCLING_POWER_SERVICE);
        else if (filterType.equals("cad"))
          filterUuid = ParcelUuid.fromString(CYCLING_SPEED_AND_CADENCE);
        else if (filterType.equals("tmp"))
          filterUuid = ParcelUuid.fromString(ENVIRONMENTAL_SENSING);
        else if (filterType.equals("fit"))
          filterUuid = ParcelUuid.fromString(FITNESS_MACHINE);
      }

     if (filterUuid != null) {
       //builder.setServiceUuid(filterUuid);
         ParcelUuid mask = 
            ParcelUuid.fromString("0000ffff-0000-0000-0000-000000000000");
          builder.setServiceUuid(filterUuid,mask);
     }

     if (filterAddress != null)  {
         builder.setDeviceAddress(filterAddress);
       }

     if (filterUuid != null || filterAddress != null) {
       filters.add(builder.build());
     }

     ScanSettings.Builder settings_builder = new ScanSettings.Builder();
     //settings_builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
     //settings_builder.setScanMode(ScanSettings.SCAN_MODE_BALANCED);
     settings_builder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
     final ScanSettings settings = settings_builder.build();

   //showToast("startScan: type = " + filterType + "addr = " + filterAddress); 


     btLeScanner.startScan(filters,settings,scanCallback);

     if (timeout > 1000)
     { // stop scan after timeout msec
       new MyThread() { 
           public void run() { sleep(timeout); stopScan(); }
       }.start();
      }
   }


   void stopScan()
   { if (!scanning) return;

     btLeScanner.stopScan(scanCallback); 
     handler.post(new Runnable() {
         public void run() { 
            if (btDeviceList.isEmpty()) 
              handleError("No devices found.");
            else
              handleStop(btDeviceList.size());
         }
     });
     scanning = false;
    
   }

  
}
