defmodule Modbux.Tcp.Server.Handler do
  @moduledoc """
  A worker for each Modbus Client, handles Client requests.
  """
  alias Modbux.Tcp.Server.Handler
  alias Modbux.Model.Shared
  alias Modbux.Tcp
  use GenServer, restart: :temporary
  require Logger

  defstruct model_pid: nil,
            parent_pid: nil,
            socket: nil

  @spec start_link([...]) :: :ignore | {:error, any} | {:ok, pid}
  def start_link([socket, model_pid, parent_pid]) do
    GenServer.start_link(__MODULE__, [socket, model_pid, parent_pid])
  end

  def init([socket, model_pid, parent_pid]) do
    {:ok, %Handler{model_pid: model_pid, socket: socket, parent_pid: parent_pid}}
  end

  def handle_info({:tcp, socket, data}, state) do
    Logger.debug("(#{__MODULE__}) Received: #{inspect(data, base: :hex)} ")
    {cmd, transid} = Tcp.parse_req(data)
    Logger.debug("(#{__MODULE__}) Received Modbux request: #{inspect({cmd, transid})}")

    case Shared.apply(state.model_pid, cmd) do
      {:ok, values} ->
        Logger.debug("(#{__MODULE__}) msg send")
        resp = Tcp.pack_res(cmd, values, transid)
        if !is_nil(state.parent_pid), do: notify(state.parent_pid, cmd)
        :ok = :gen_tcp.send(socket, resp)

      {:error, reason} ->
        Logger.debug("(#{__MODULE__}) An error has occur: #{reason}")

      nil ->
        Logger.debug("(#{__MODULE__}) Message for another slave")
    end

    {:noreply, state}
  end

  def handle_info({:tcp_closed, _socket}, state) do
    Logger.debug("(#{__MODULE__}) Socket closed")
    {:stop, :normal, state}
  end

  def handle_info({:tcp_error, socket, reason}, state) do
    Logger.error("(#{__MODULE__}) TCP error: #{reason}")
    :gen_tcp.close(socket)
    {:stop, :normal, state}
  end

  def handle_info(:timeout, state) do
    Logger.debug("(#{__MODULE__}) timeout")
    :gen_tcp.close(state.socket)
    {:stop, :normal, state}
  end

  def terminate(:normal, _state), do: nil

  def terminate(reason, state) do
    Logger.error("(#{__MODULE__}) Error: #{inspect(reason)}")
    :gen_tcp.close(state.socket)
  end

  defp notify(pid, cmd) do
    send(pid, {:modbus_tcp, {:server_request, cmd}})
  end
end
