defmodule OVSDB.OpenvSwitch do
  use GenServer

  defmodule State do
    defstruct server: nil,
              client_pid: nil,
              monitor_pid: nil,
              ovs_uuid: nil
  end

  @database "Open_vSwitch"

  @open_vswitch "Open_vSwitch"
  @interface "Interface"
  @port "Port"
  @controller "Controller"
  @bridge "Bridge"

  def start_link(server) do
    GenServer.start_link(__MODULE__, [server])
  end

  def find_by_name(pid, table, name) do
    GenServer.call(pid, {:find_by_name, table, name})
  end

  def add_br(pid, bridge, options \\ []) do
    br_uuids = GenServer.call(pid, {:sync_get, @open_vswitch, "bridges"})

    add_br_options = [
      bridge: bridge,
      controller: options[:controller] || "tcp:127.0.0.1:6653",
      protocol: options[:protocol] || "OpenFlow13",
      fail_mode: options[:fail_mode] || "standalone",
      br_uuids: br_uuids
    ]

    GenServer.call(pid, {:add_br, add_br_options})
  end

  def del_br(pid, bridge) do
    case find_by_name(pid, @bridge, bridge) do
      %{"_uuid" => uuid} ->
        new_bridges =
          case GenServer.call(pid, {:sync_get, @open_vswitch, "bridges"}) do
            ["set", bridges] -> %{"bridges" => ["set", bridges -- [uuid]]}
            ^uuid -> %{"bridges" => ["set", []]}
            curr_bridges -> %{"bridges" => curr_bridges}
          end

        GenServer.call(pid, {:del_br, new_bridges})

      :not_found ->
        {:error, :not_found}
    end
  end

  def init([server]) do
    state =
      server
      |> String.to_charlist()
      |> init_client

    {:ok, state}
  end

  def handle_call({:sync_get, table, col_name}, _from, state) do
    [%{"rows" => [%{^col_name => values} | _]}] =
      [col_name]
      |> :eovsdb_op.select(table, [])
      |> xact(state.client_pid)

    {:reply, values, state}
  end

  def handle_call({:add_br, options}, _from, state) do
    %State{client_pid: pid, ovs_uuid: ovs} = state
    br_iface = %{name: options[:bridge], type: :internal}
    br_port = %{name: options[:bridge], interfaces: ["named-uuid", "brinterface"]}
    controller = %{target: options[:controller]}

    new_bridge = %{
      name: options[:bridge],
      ports: ["named-uuid", "brport"],
      controller: ["named-uuid", "brcontroller"],
      fail_mode: options[:fail_mode],
      protocols: options[:protocol]
    }

    named_uuid = ["named-uuid", "bridge"]

    new_bridges =
      case options[:br_uuids] do
        ["set", []] -> %{bridges: named_uuid}
        ["set", bridges] -> %{bridges: ["set", bridges ++ [named_uuid]]}
        ["uuid", _] = bridge -> %{bridges: ["set", [bridge] ++ [named_uuid]]}
      end

    next_config = [{"next_cfg", "+=", 1}]
    eq_ovs_uuid = [{"_uuid", "==", ovs}]

    replies =
      xact(
        [
          :eovsdb_op.insert(@interface, br_iface, "brinterface"),
          :eovsdb_op.insert(@port, br_port, "brport"),
          :eovsdb_op.insert(@controller, controller, "brcontroller"),
          :eovsdb_op.insert(@bridge, new_bridge, "bridge"),
          :eovsdb_op.update(@open_vswitch, eq_ovs_uuid, new_bridges),
          :eovsdb_op.mutate(@open_vswitch, eq_ovs_uuid, next_config)
        ],
        pid
      )

    {:reply, replies, state}
  end

  def handle_call({:del_br, new_bridges}, _from, state) do
    %State{client_pid: pid, ovs_uuid: ovs} = state
    eq_ovs_uuid = [{"_uuid", "==", ovs}]
    next_config = [{"next_cfg", "+=", 1}]

    replies =
      xact(
        [
          :eovsdb_op.update(@open_vswitch, eq_ovs_uuid, new_bridges),
          :eovsdb_op.mutate(@open_vswitch, eq_ovs_uuid, next_config)
        ],
        pid
      )

    {:reply, replies, state}
  end

  def handle_call({:find_by_name, table, name}, _from, state) do
    %State{client_pid: pid} = state
    query = :eovsdb_op.select('*', table, [{"name", "==", name}])

    reply =
      case xact(query, pid) do
        [%{"rows" => []}] -> :not_found
        [%{"rows" => [row]}] -> row
      end

    {:reply, reply, state}
  end

  def handle_cast({:async_get, "_uuid"}, state) do
    [%{"rows" => [%{"_uuid" => values} | _]}] =
      ["_uuid"]
      |> :eovsdb_op.select(@open_vswitch, [])
      |> xact(state.client_pid)

    {:noreply, %{state | ovs_uuid: values}}
  end

  # private functions

  defp init_client(server) do
    {:ok, pid} = :eovsdb_client.connect(server, database: @database)
    :eovsdb_client.regist_schema(pid)
    :ok = GenServer.cast(self(), {:async_get, "_uuid"})
    %State{server: server, client_pid: pid}
  end

  defp xact(query, pid) when is_list(query) do
    {:ok, res} = :eovsdb_client.transaction(pid, query)
    res
  end

  defp xact(query, pid) when is_map(query) do
    xact([query], pid)
  end
end
