defmodule RustlerBtleplug.Genserver do
  @moduledoc false
  @name :ble_genserver

  use GenServer
  require Logger

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, %{}, name: @name)
  end

  def child_spec(init_arg) do
    Supervisor.child_spec(
      %{
        id: init_arg,
        start: {__MODULE__, :start_link, [init_arg]}
      },
      []
    )
  end

  def init(opts) do
    Process.flag(:trap_exit, true)
    IO.puts("#{__MODULE__} init #{inspect(opts)}")

    {:ok,
     %{
       central: nil,
       peripheral: nil,
       messages: []
     }}
  end

  def handle_info({:btleplug_peripheral_discovered, uuid}, state) do
    Logger.debug("NIF Peripheral Discovered: #{uuid}")
    new_state = update_state_with_message(state, {:btleplug_peripheral_discovered, uuid})

    {:noreply, new_state}
  end

  def handle_info({:btleplug_peripheral_connected, uuid}, state) do
    Logger.debug("NIF Peripheral Connected: #{uuid}")
    new_state = update_state_with_message(state, {:btleplug_peripheral_connected, uuid})
    {:noreply, new_state}
  end

  def handle_info({:btleplug_peripheral_disconnected, uuid}, state) do
    Logger.debug("NIF Peripheral Disconnected: #{uuid}")
    new_state = update_state_with_message(state, {:btleplug_peripheral_disconnected, uuid})
    {:noreply, new_state}
  end

  def handle_info({:btleplug_characteristic_value_changed, uuid, value}, state) do
    Logger.debug("NIF Characteristic Value Changed for #{uuid}: #{value}")

    new_state =
      update_state_with_message(state, {:btleplug_characteristic_value_changed, uuid, value})

    {:noreply, new_state}
  end

  def handle_info(msg, state) do
    Logger.debug("NIF msg received : #{inspect(msg)}")
    new_state = update_state_with_message(state, msg)
    {:noreply, new_state}
  end

  def create_central() do
    GenServer.call(@name, {:create_central})
  end

  def start_scan() do
    Logger.debug("client :start_scan")
    GenServer.cast(@name, {:start_scan})
  end

  def stop_scan() do
    Logger.debug("client :stop_scan")
    GenServer.cast(@name, {:stop_scan})
  end

  def find_peripheral_by_name(device_name) do
    Logger.debug("client :find_peripheral_by_name #{device_name}")
    GenServer.call(@name, {:find_peripheral_by_name, device_name})
  end

  def connect() do
    Logger.debug("client :connect")
    GenServer.call(@name, {:connect})
  end

  def subscribe(uuid) do
    Logger.debug("client :subscribe characteristic uuid: #{uuid}")
    GenServer.call(@name, {:subscribe, uuid})
  end

  def get_messages() do
    GenServer.call(@name, {:get_messages})
  end

  def handle_cast({:set_central, central_ref}, state) do
    Logger.debug("handle_cast :set_central #{inspect(central_ref)}")

    new_state = %{state | central: central_ref}

    Logger.debug("handle_cast :set_central new_state: #{inspect(new_state)}")
    {:noreply, new_state}
  end

  def handle_cast({:start_scan}, state) do
    Logger.debug("handle_cast :start_scan #{inspect(state)}")

    case state.central do
      nil ->
        Logger.debug("No central reference to start scan.")
        {:noreply, state}

      central_ref ->
        # Call NIF to stop the scan using the central reference
        case RustlerBtleplug.Native.start_scan(central_ref) do
          {:error, reason} ->
            Logger.debug("Failed to start scan: #{reason}")
            {:noreply, state}

          _central_ref ->
            Logger.debug("Scan Started.")
            Process.sleep(1000)
            {:noreply, state}
        end
    end
  end

  def handle_cast({:stop_scan}, state) do
    Logger.debug("handle_cast :stop_scan #{inspect(state)}")

    case state.central do
      nil ->
        Logger.debug("No central reference to stop scan.")
        {:noreply, state}

      central_ref ->
        # Call NIF to stop the scan using the central reference
        case RustlerBtleplug.Native.stop_scan(central_ref) do
          {:error, reason} ->
            Logger.debug("Failed to stop scan: #{reason}")
            {:noreply, state}

          _central_ref ->
            Logger.debug("Scan Stopped.")
            {:noreply, state}
        end
    end
  end

  def handle_call({:create_central}, _from, state) do
    case RustlerBtleplug.Native.create_central() do
      {:error, reason} ->
        {:error, reason}

      central_ref ->
        GenServer.cast(@name, {:set_central, central_ref})
        Logger.debug("Central Created and Reference Stored!")
        {:reply, {:ok, central_ref}, state}
    end
  end

  def handle_call({:find_peripheral_by_name, device_name}, _from, state) do
    case state.central do
      nil ->
        Logger.debug("No central reference to find_peripheral_by_name.")
        {:noreply, state}

      central_ref ->
        case RustlerBtleplug.Native.find_peripheral_by_name(central_ref, device_name) do
          {:error, reason} ->
            Logger.debug("Failed to find #{device_name}: #{reason}")
            {:noreply, state}

          peripheral_ref ->
            Logger.debug("Peripheral #{device_name} found #{inspect(peripheral_ref)}")
            {:reply, {:ok, peripheral_ref}, %{state | peripheral: peripheral_ref}}
        end
    end
  end

  def handle_call({:connect, _peripheral_ref}, _from, state) do
    case state.peripheral do
      nil ->
        Logger.debug("No peripheral reference to connect.")
        {:noreply, state}

      peripheral_ref ->
        case RustlerBtleplug.Native.connect(peripheral_ref) do
          {:error, reason} ->
            Logger.debug("Failed to connect to #{inspect(peripheral_ref)}: #{reason}")
            {:noreply, state}

          peripheral_ref ->
            Logger.debug("Connecting to #{inspect(peripheral_ref)}")
            {:reply, {:ok, peripheral_ref}, %{state | peripheral: peripheral_ref}}
        end
    end
  end

  def handle_call({:subscribe, uuid}, _from, state) do
    case state.peripheral do
      nil ->
        Logger.debug("No peripheral reference to subscribe to.")
        {:noreply, state}

      peripheral_ref ->
        case RustlerBtleplug.Native.subscribe(peripheral_ref, uuid) do
          {:error, reason} ->
            Logger.debug("Failed to subscribe to #{uuid}: #{reason}")
            {:noreply, state}

          peripheral_ref ->
            Logger.debug("Subscribing to #{uuid} #{inspect(peripheral_ref)}")
            {:reply, {:ok, peripheral_ref}, %{state | peripheral: peripheral_ref}}
        end
    end
  end

  def handle_call({:get_messages}, _from, state) do
    Logger.debug("handle_call :get_messages")
    {:reply, state.messages, state}
  end

  defp update_state_with_message(state, message) do
    %{state | messages: [message | state.messages]}
  end
end
