defmodule Perhap.EventHandler do
  use Perhap.Handler

  def handle(:get_event, conn, state) do
    case retrieve_event(state[:event_id]) do
      {:ok, event} ->
        Perhap.Response.send(conn, 200, event)
      {:error, _reason} ->
        raise(RuntimeError, message: :not_found)
    end
  end
  def handle(:get_events, conn, state) do
    args = case Keyword.has_key?(state, :entity_id) do
      true -> [context: state[:context], entity_id: state[:entity_id]]
      _ -> [context: state[:context]]
    end
    case retrieve_events(args) do
      {:ok, events} ->
        Perhap.Response.send(conn, 200, %{"events" => events})
      {:error, _reason} ->
        raise(RuntimeError, message: :not_found)
    end
  end
  def handle(:post_event, conn, state) do
    :ok = conn
          |> read_event
          |> envelope_event
          |> validate_event
          |> save_event
          |> dispatch_event(state)
    Perhap.Response.send(conn, 204)
  end

  @spec read_event(:cowboy_req.req())::
    {:ok, :cowboy_req.req(), String.t} | {:error, :cowboy_req.req(), atom()}
  def read_event(conn) do
    case read_body(conn) do
      {:ok, conn2, body} -> {:ok, conn2, body}
      {:timeout, _conn2, _body} -> raise(RuntimeError, message: :request_timeout)
    end
  end

  def envelope_event({:ok, req, body}) do
    [ "" | [ context | [ event_type | _ ] ] ] = :cowboy_req.path(req) |> String.split("/")
    data = case Poison.decode!(body) do
      data_map when is_map(data_map) -> data_map
      _ -> raise(RuntimeError, message: :validation)
    end
    %Perhap.Event{ event_id: req.bindings.event_id |> to_string,
                   metadata: %Perhap.Event.Metadata{ event_id: :cowboy_req.binding(:event_id, req) |> to_string,
                                                     entity_id: :cowboy_req.binding(:entity_id, req) |> to_string,
                                                     scheme: :cowboy_req.scheme(req),
                                                     host: :cowboy_req.host(req),
                                                     port: :cowboy_req.port(req) |> to_string |> String.to_integer,
                                                     path: :cowboy_req.path(req),
                                                     context: context |> String.to_existing_atom,
                                                     type: event_type |> String.to_existing_atom,
                                                     user_id: "", # Todo: capture user_id
                                                     ip_addr: peer_to_ip(:cowboy_req.peer(req)),
                                                     timestamp: Perhap.Event.timestamp() },
                    data: data }
  end

  def validate_event(event) do
    case Perhap.Event.validate(event) do
      :ok -> event
      {:invalid, _reason} -> raise(RuntimeError, message: :validation)
    end
  end

  def save_event(event) do
    case Perhap.Event.save_event!(event) do
      {:ok, event} -> event
      {:error, _reason} -> raise(RuntimeError, message: :service_unavailable)
    end
  end

  def dispatch_event(event, state) do
    Perhap.Dispatcher.ensure_started({Perhap.Dispatcher, __MODULE__})
    Perhap.Dispatcher.dispatch({Perhap.Dispatcher, __MODULE__}, event, state)
  end

  def retrieve_event(event_id) do
    Perhap.Event.retrieve_event(event_id)
  end

  def retrieve_events([ context: context, entity_id: entity_id ]) do
    Perhap.Event.retrieve_events(context, entity_id: entity_id)
  end
  def retrieve_events([ context: context ]) do
    Perhap.Event.retrieve_events(context)
  end

  defp peer_to_ip({ip, _port}), do: :inet.ntoa(ip) |> to_string

  defp read_body(conn), do: read_body(conn, "")
	defp read_body(conn, acc) do
		read_body_opts = %{ length: @read_length, period: @timeout }
		try do
			case :cowboy_req.read_body(conn, read_body_opts) do
				{:ok, data, conn2} -> {:ok, conn2, acc <> data};
				{:more, data, conn2} -> read_body(conn2, acc <> data)
			end
		catch
			:exit, _ -> {:timeout, conn, acc}
		end
	end

end
