/*
 * Professional Android, 4th Edition
 * Reto Meier and Ian Lake
 * Copyright 2018 John Wiley Wiley & Sons, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.professionalandroid.apps.myapplication;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class BluetoothActivity extends AppCompatActivity {

  private static final String TAG = "CH18_BLUETOOTH";
  private static final int REQUEST_ACCESS_COARSE_LOCATION = 2;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_bluetooth);
  }

  /*
   * Listing 18-1: Accessing the default Bluetooth
   * Listing 18-2: Enabling Bluetooth
   * Listing 18-4: Monitoring discoverability request approval
   */
  private BluetoothAdapter mBluetooth = BluetoothAdapter.getDefaultAdapter();

  private static final int ENABLE_BLUETOOTH = 1;

  private void initBluetooth() {
    if (!mBluetooth.isEnabled()) {
      // Bluetooth isn't enabled, prompt the user to turn it on.
      Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
      startActivityForResult(intent, ENABLE_BLUETOOTH);
    } else {
      // Bluetooth is enabled, initialize the UI.
      initBluetoothUI();
    }
  }

  protected void onActivityResult(int requestCode,
                                  int resultCode, Intent data) {
    if (requestCode == ENABLE_BLUETOOTH)
      if (resultCode == RESULT_OK) {
        // Bluetooth has been enabled, initialize the UI.
        initBluetoothUI();
      }

    // Listing 18-4: Monitoring discoverability request approval
    if (requestCode == DISCOVERY_REQUEST) {
      if (resultCode == RESULT_CANCELED) {
        Log.d(TAG, "Discovery canceled by user.");
      }
    }
  }

  private void initBluetoothUI() {
    // TODO Update the UI when Bluetooth has been enabled.
  }

  /*
   * Listing 18-3: Enabling discoverability
   */
  private static final int DISCOVERY_REQUEST = 2;

  private void enable_discovery() {
    startActivityForResult(
      new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE),
      DISCOVERY_REQUEST);
  }

  /*
   * Listing 18-5: Discovering remote Bluetooth Devices
   */
  //private BluetoothAdapter mBluetooth;

  private List<BluetoothDevice> deviceList = new ArrayList<>();

  private void startDiscovery() {
    BroadcastReceiver discoveryResult = new BroadcastReceiver() {
      @Override
      public void onReceive(Context context, Intent intent) {
        String remoteDeviceName =
          intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
        BluetoothDevice remoteDevice =
          intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        deviceList.add(remoteDevice);
        Log.d(TAG, "Discovered " + remoteDeviceName);
      }
    };

    if (ContextCompat.checkSelfPermission(this,
      Manifest.permission.ACCESS_COARSE_LOCATION)
          == PackageManager.PERMISSION_GRANTED) {
      mBluetooth = BluetoothAdapter.getDefaultAdapter();

      registerReceiver(discoveryResult,
        new IntentFilter(BluetoothDevice.ACTION_FOUND));

      if (mBluetooth.isEnabled() && !mBluetooth.isDiscovering()) {
        deviceList.clear();
        mBluetooth.startDiscovery();
      } else
        ActivityCompat.requestPermissions(this,
          new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
          REQUEST_ACCESS_COARSE_LOCATION);
    }
  }

  /*
   * Listing 18-6: Listening for Bluetooth Socket connection requests
   */
  //private BluetoothAdapter mBluetooth;
  private BluetoothSocket mBluetoothSocket;

  private UUID startServerSocket() {
    UUID uuid = UUID.randomUUID(); // Listener socket must know this value.
    String name = "bluetoothserver";

    mBluetooth = BluetoothAdapter.getDefaultAdapter();
    try {
      final BluetoothServerSocket btserver = mBluetooth.listenUsingRfcommWithServiceRecord(name, uuid);

      Thread acceptThread = new Thread(new Runnable() {
        public void run() {
          try {
            // Block until client connection established.
            mBluetoothSocket = btserver.accept();

            // Start listening for messages.
            listenForMessages();
          } catch (IOException e) {
            Log.e(TAG, "Server connection IO Exception", e);
          }
        }
      });
      acceptThread.start();
    } catch (IOException e) {
      Log.e(TAG, "Socket listener IO Exception", e);
    }
    return uuid;
  }

  private void listenForMessages() {
    // TODO Listen for messages between sockets.
  }

  /*
   * Listing 18-7: Creating a client Bluetooth Socket
   */
  //private BluetoothSocket mBluetoothSocket;

  private void connectToServerSocket(BluetoothDevice device, UUID uuid) {
    try{
      BluetoothSocket clientSocket = device.createRfcommSocketToServiceRecord(uuid);

      // Block until server connection accepted.
      clientSocket.connect();

      // Add a reference to the socket used to send messages.
      mBluetoothSocket = clientSocket;

      // Start listening for messages.
      listenForMessages();
    } catch (IOException e) {
      Log.e(TAG, "Bluetooth client I/O Exception.", e);
    }
  }

  /*
   *  Listing 18-8: Sending and receiving strings using Bluetooth Sockets
   */
  private void sendMessage(BluetoothSocket socket, String message) {
    OutputStream outputStream;
    try {
      outputStream = socket.getOutputStream();

      // Add a stop character.
      byte[] byteArray = (message + " ").getBytes();
      byteArray[byteArray.length-1] = 0;
      outputStream.write(byteArray);
    } catch (IOException e) {
      Log.e(TAG, "Failed to send message: " + message, e);
    }
  }

  private boolean mListening = false;

  private String listenForMessages(BluetoothSocket socket,
                                   StringBuilder incoming) {
    String result = "";
    mListening = true;

    int bufferSize = 1024;
    byte[] buffer = new byte[bufferSize];

    try {
      InputStream instream = socket.getInputStream();
      int bytesRead = -1;
      while (mListening) {
        bytesRead = instream.read(buffer);
        if (bytesRead != -1) {
          while ((bytesRead == bufferSize) && (buffer[bufferSize-1] != 0)) {
            result = result + new String(buffer, 0, bytesRead - 1);
            bytesRead = instream.read(buffer);
          }

          result = result + new String(buffer, 0, bytesRead - 1);
          incoming.append(result);
        }
      }
    } catch (IOException e) {
      Log.e(TAG, "Message receive failed.", e);
    }
    return result;
  }
}