Run the code formatter on ExUnit.CaptureIOTest (#6912)

Code.require_file("../test_helper.exs", __DIR__)

defmodule ExUnit.CaptureIOTest do
  use ExUnit.Case

  defmodule GetUntil do
    def until_new_line(_, :eof, _) do
      {:done, :eof, []}
    end

    def until_new_line(this_far, chars, stop_char) do
      case Enum.split_while(chars, fn c -> c != stop_char end) do
        {l, []} ->
          {:more, this_far ++ l}

        {l, [stop_char | rest]} ->
          {:done, this_far ++ l ++ [stop_char], rest}
      end
    end

    def get_line(device \\ Process.group_leader()) do
      request = {:get_until, :unicode, "", __MODULE__, :until_new_line, [?\n]}
      send(device, {:io_request, self(), device, request})

      receive do
        {:io_reply, _, data} -> data
      end
    end
  end

  import ExUnit.CaptureIO
  doctest ExUnit.CaptureIO, import: true

  test "no leakage on failures" do
    group_leader = Process.group_leader()

    test = self()

    assert_raise ArgumentError, fn ->
      capture_io(fn ->
        send(test, {:string_io, Process.group_leader()})
        raise ArgumentError
      end)
    end

    receive do
      {:string_io, pid} ->
        ref = Process.monitor(pid)
        assert_receive {:DOWN, ^ref, _, _, _}
    end

    assert Process.group_leader() == group_leader
  end

  test "with no output" do
    assert capture_io(fn -> nil end) == ""
  end

  test "with put chars" do
    assert capture_io(fn ->
             :io.put_chars("")
           end) == ""

    assert capture_io(fn ->
             :io.put_chars("a")
             :io.put_chars("b")
           end) == "ab"

    assert capture_io(fn ->
             :io.put_chars("josé")
           end) == "josé"

    assert capture_io(fn ->
             spawn(fn -> :io.put_chars("a") end)
             Process.sleep(10)
           end) == "a"

    assert capture_io(fn ->
             assert :io.put_chars("a") == :ok
           end)
  end

  test "with put chars to stderr" do
    assert capture_io(:stderr, fn ->
             :io.put_chars(:standard_error, "a")
           end) == "a"
  end

  test "with get chars" do
    assert capture_io(fn ->
             :io.get_chars(">", 3)
           end) == ">"

    assert capture_io([capture_prompt: false], fn ->
             :io.get_chars(">", 3)
           end) == ""

    capture_io(fn ->
      assert :io.get_chars(">", 3) == :eof
    end)

    capture_io("", fn ->
      assert :io.get_chars(">", 3) == :eof
    end)

    capture_io("abc\ndef", fn ->
      assert :io.get_chars(">", 3) == "abc"
      assert :io.get_chars(">", 5) == "\ndef"
      assert :io.get_chars(">", 7) == :eof
    end)

    capture_io("あいう", fn ->
      assert :io.get_chars(">", 2) == "あい"
      assert :io.get_chars(">", 1) == "う"
      assert :io.get_chars(">", 1) == :eof
    end)
  end

  test "with get line" do
    assert capture_io(fn ->
             :io.get_line(">")
           end) == ">"

    assert capture_io([capture_prompt: false], fn ->
             :io.get_line(">")
           end) == ""

    capture_io(fn ->
      assert :io.get_line(">") == :eof
    end)

    capture_io("", fn ->
      assert :io.get_line(">") == :eof
    end)

    capture_io("\n", fn ->
      assert :io.get_line(">") == "\n"
      assert :io.get_line(">") == :eof
    end)

    capture_io("a", fn ->
      assert :io.get_line(">") == "a"
      assert :io.get_line(">") == :eof
    end)

    capture_io("a\n", fn ->
      assert :io.get_line(">") == "a\n"
      assert :io.get_line(">") == :eof
    end)

    capture_io("a\nb", fn ->
      assert :io.get_line(">") == "a\n"
      assert :io.get_line(">") == "b"
      assert :io.get_line(">") == :eof
    end)

    capture_io("あい\nう", fn ->
      assert :io.get_line(">") == "あい\n"
      assert :io.get_line(">") == "う"
      assert :io.get_line(">") == :eof
    end)
  end

  test "with get password" do
    capture_io(fn ->
      assert :io.get_password() == :eof
    end)

    capture_io("", fn ->
      assert :io.get_password() == :eof
    end)

    capture_io("abc", fn ->
      assert :io.get_password() == "abc"
      assert :io.get_password() == :eof
    end)

    capture_io("abc\n", fn ->
      assert :io.get_password() == "abc\n"
      assert :io.get_password() == :eof
    end)

    capture_io("\n", fn ->
      assert :io.get_password() == "\n"
      assert :io.get_password() == :eof
    end)

    capture_io("a\nb", fn ->
      assert :io.get_password() == "a\n"
      assert :io.get_password() == "b"
      assert :io.get_password() == :eof
    end)

    capture_io("あい\nう", fn ->
      assert :io.get_password() == "あい\n"
      assert :io.get_password() == "う"
      assert :io.get_password() == :eof
    end)
  end

  test "with get until" do
    assert capture_io(fn ->
             :io.scan_erl_form('>')
           end) == ">"

    assert capture_io("1.\n", fn ->
             :io.scan_erl_form('>')
           end) == ">"

    assert capture_io("1\n.\n", fn ->
             :io.scan_erl_form('>')
           end) == ">>"

    assert capture_io([capture_prompt: false], fn ->
             :io.scan_erl_form('>')
           end) == ""

    capture_io(fn ->
      assert :io.scan_erl_form('>') == {:eof, 1}
    end)

    capture_io("1", fn ->
      assert :io.scan_erl_form('>') == {:ok, [{:integer, 1, 1}], 1}
      assert :io.scan_erl_form('>') == {:eof, 1}
    end)

    capture_io("1\n.", fn ->
      assert :io.scan_erl_form('>') == {:ok, [{:integer, 1, 1}, {:dot, 2}], 2}
      assert :io.scan_erl_form('>') == {:eof, 1}
    end)

    capture_io("1.\n.", fn ->
      assert :io.scan_erl_form('>') == {:ok, [{:integer, 1, 1}, {:dot, 1}], 2}
      assert :io.scan_erl_form('>') == {:ok, [dot: 1], 1}
      assert :io.scan_erl_form('>') == {:eof, 1}
    end)

    capture_io("\"a", fn ->
      assert :io.scan_erl_form('>') == {:error, {1, :erl_scan, {:string, 34, 'a'}}, 1}
      assert :io.scan_erl_form('>') == {:eof, 1}
    end)

    capture_io("\"a\n\"", fn ->
      assert :io.scan_erl_form('>') == {:ok, [{:string, 1, 'a\n'}], 2}
      assert :io.scan_erl_form('>') == {:eof, 1}
    end)

    capture_io(":erl. mof*,,l", fn ->
      assert :io.scan_erl_form('>') == {:ok, [{:":", 1}, {:atom, 1, :erl}, {:dot, 1}], 1}

      expected_tokens = [{:atom, 1, :mof}, {:*, 1}, {:",", 1}, {:",", 1}, {:atom, 1, :l}]
      assert :io.scan_erl_form('>') == {:ok, expected_tokens, 1}

      assert :io.scan_erl_form('>') == {:eof, 1}
    end)

    capture_io("a\nb\nc", fn ->
      assert GetUntil.get_line() == "a\n"
      assert GetUntil.get_line() == "b\n"
      assert GetUntil.get_line() == :eof
    end)
  end

  test "with setopts" do
    assert capture_io(fn ->
             assert :io.setopts({:encoding, :latin1}) == {:error, :enotsup}
           end) == ""
  end

  test "with getopts" do
    assert capture_io(fn ->
             assert :io.getopts() == {:ok, [binary: true, encoding: :unicode]}
           end) == ""
  end

  test "with columns" do
    assert capture_io(fn ->
             :io.columns()
           end) == ""

    capture_io(fn ->
      assert :io.columns() == {:error, :enotsup}
    end)
  end

  test "with rows" do
    assert capture_io(fn ->
             :io.rows()
           end) == ""

    capture_io(fn ->
      assert :io.rows() == {:error, :enotsup}
    end)
  end

  test "with multiple IO requests" do
    requests = [{:put_chars, :unicode, "a"}, {:put_chars, :unicode, "b"}]

    assert capture_io(fn ->
             send_and_receive_io({:requests, requests})
           end) == "ab"

    capture_io(fn ->
      assert send_and_receive_io({:requests, requests}) == :ok
    end)
  end

  test "with unknown IO request" do
    assert capture_io(fn ->
             send_and_receive_io(:unknown)
           end) == ""

    capture_io(fn ->
      assert send_and_receive_io(:unknown) == {:error, :request}
    end)
  end

  test "device re-registering" do
    {_pid, ref} =
      spawn_monitor(fn ->
        capture_io(:stderr, fn ->
          spawn_link(Kernel, :exit, [:shutdown])
          Process.sleep(:infinity)
        end)
      end)

    # Assert the process is down then invoke capture_io
    # to trigger the ExUnit.Server, ensuring the DOWN
    # message from previous capture_io has been processed
    assert_receive {:DOWN, ^ref, _, _, :shutdown}
    _ = capture_io(fn -> "trigger" end)
    assert capture_io(:stderr, fn -> nil end)
  end

  test "with assert inside" do
    try do
      capture_io(fn ->
        assert false
      end)
    rescue
      error in [ExUnit.AssertionError] ->
        assert error.message == "Expected truthy, got false"
    end
  end

  test "capture :stderr by two processes" do
    spawn(fn -> capture_io(:stderr, fn -> Process.sleep(100) end) end)
    Process.sleep(10)

    expected_message = "IO device registered at :standard_error is already captured"

    assert_raise RuntimeError, expected_message, fn ->
      capture_io(:stderr, fn -> nil end)
    end

    Process.sleep(100)
  end

  defp send_and_receive_io(req) do
    pid = self()
    send(:erlang.group_leader(), {:io_request, pid, pid, req})

    receive do
      {:io_reply, ^pid, res} -> res
    end
  end
end

Code.require_file "../test_helper.exs", __DIR__

defmodule ExUnit.CaptureIOTest do
  use ExUnit.Case

  defmodule GetUntil do
    def until_new_line(_, :eof, _) do
      {:done, :eof, []}
    end

    def until_new_line(this_far, chars, stop_char) do
      case Enum.split_while(chars, fn(c) -> c != stop_char end) do
        {l, []} ->
          {:more, this_far ++ l}
        {l, [stop_char | rest]} ->
          {:done, this_far ++ l ++ [stop_char], rest}
      end
    end

    def get_line(device \\ Process.group_leader) do
      send device, {:io_request, self(), device, {:get_until, :unicode, "", __MODULE__, :until_new_line, [?\n]}}
      receive do
        {:io_reply, _, data} -> data
      end
    end
  end

  import ExUnit.CaptureIO
  doctest ExUnit.CaptureIO, import: true

  test "no leakage on failures" do
    group_leader = Process.group_leader()

    test = self()
    assert_raise ArgumentError, fn ->
      capture_io(fn ->
        send(test, {:string_io, Process.group_leader()})
        raise ArgumentError
      end)
    end

    receive do
      {:string_io, pid} ->
        ref = Process.monitor(pid)
        assert_receive {:DOWN, ^ref, _, _, _}
    end
    assert Process.group_leader() == group_leader
  end

  test "with no output" do
    assert capture_io(fn -> nil end) == ""
  end

  test "with put chars" do
    assert capture_io(fn ->
      :io.put_chars("")
    end) == ""

    assert capture_io(fn ->
      :io.put_chars("a")
      :io.put_chars("b")
    end) == "ab"

    assert capture_io(fn ->
      :io.put_chars("josé")
    end) == "josé"

    assert capture_io(fn ->
      spawn(fn -> :io.put_chars("a") end)
      Process.sleep(10)
    end) == "a"

    assert capture_io(fn ->
      assert :io.put_chars("a") == :ok
    end)
  end

  test "with put chars to stderr" do
    assert capture_io(:stderr, fn ->
      :io.put_chars(:standard_error, "a")
    end) == "a"
  end

  test "with get chars" do
    assert capture_io(fn ->
      :io.get_chars(">", 3)
    end) == ">"

    assert capture_io([capture_prompt: false], fn ->
      :io.get_chars(">", 3)
    end) == ""

    capture_io(fn ->
      assert :io.get_chars(">", 3) == :eof
    end)

    capture_io("", fn ->
      assert :io.get_chars(">", 3) == :eof
    end)

    capture_io("abc\ndef", fn ->
      assert :io.get_chars(">", 3) == "abc"
      assert :io.get_chars(">", 5) == "\ndef"
      assert :io.get_chars(">", 7) == :eof
    end)

    capture_io("あいう", fn ->
      assert :io.get_chars(">", 2) == "あい"
      assert :io.get_chars(">", 1) == "う"
      assert :io.get_chars(">", 1) == :eof
    end)
  end

  test "with get line" do
    assert capture_io(fn ->
      :io.get_line ">"
    end) == ">"

    assert capture_io([capture_prompt: false], fn ->
      :io.get_line ">"
    end) == ""

    capture_io(fn ->
      assert :io.get_line(">") == :eof
    end)

    capture_io("", fn ->
      assert :io.get_line(">") == :eof
    end)

    capture_io("\n", fn ->
      assert :io.get_line(">") == "\n"
      assert :io.get_line(">") == :eof
    end)

    capture_io("a", fn ->
      assert :io.get_line(">") == "a"
      assert :io.get_line(">") == :eof
    end)

    capture_io("a\n", fn ->
      assert :io.get_line(">") == "a\n"
      assert :io.get_line(">") == :eof
    end)

    capture_io("a\nb", fn ->
      assert :io.get_line(">") == "a\n"
      assert :io.get_line(">") == "b"
      assert :io.get_line(">") == :eof
    end)

    capture_io("あい\nう", fn ->
      assert :io.get_line(">") == "あい\n"
      assert :io.get_line(">") == "う"
      assert :io.get_line(">") == :eof
    end)
  end

  test "with get password" do
    capture_io(fn ->
      assert :io.get_password() == :eof
    end)

    capture_io("", fn ->
      assert :io.get_password() == :eof
    end)

    capture_io("abc", fn ->
      assert :io.get_password() == "abc"
      assert :io.get_password() == :eof
    end)

    capture_io("abc\n", fn ->
      assert :io.get_password() == "abc\n"
      assert :io.get_password() == :eof
    end)

    capture_io("\n", fn ->
      assert :io.get_password() == "\n"
      assert :io.get_password() == :eof
    end)

    capture_io("a\nb", fn ->
      assert :io.get_password() == "a\n"
      assert :io.get_password() == "b"
      assert :io.get_password() == :eof
    end)

    capture_io("あい\nう", fn ->
      assert :io.get_password() == "あい\n"
      assert :io.get_password() == "う"
      assert :io.get_password() == :eof
    end)
  end

  test "with get until" do
    assert capture_io(fn ->
      :io.scan_erl_form('>')
    end) == ">"

    assert capture_io("1.\n", fn ->
      :io.scan_erl_form('>')
    end) == ">"

    assert capture_io("1\n.\n", fn ->
       :io.scan_erl_form('>')
    end) == ">>"

    assert capture_io([capture_prompt: false], fn ->
      :io.scan_erl_form('>')
    end) == ""

    capture_io(fn ->
      assert :io.scan_erl_form('>') == {:eof, 1}
    end)

   capture_io("1", fn ->
     assert :io.scan_erl_form('>') == {:ok, [{:integer, 1, 1}], 1}
     assert :io.scan_erl_form('>') == {:eof, 1}
   end)

    capture_io("1\n.", fn ->
      assert :io.scan_erl_form('>') == {:ok, [{:integer, 1, 1}, {:dot, 2}], 2}
      assert :io.scan_erl_form('>') == {:eof, 1}
    end)

    capture_io("1.\n.", fn ->
      assert :io.scan_erl_form('>') == {:ok, [{:integer, 1, 1}, {:dot, 1}], 2}
      assert :io.scan_erl_form('>') == {:ok, [dot: 1], 1}
      assert :io.scan_erl_form('>') == {:eof, 1}
    end)

    capture_io("\"a", fn ->
      assert :io.scan_erl_form('>') == {:error, {1, :erl_scan, {:string, 34, 'a'}}, 1}
      assert :io.scan_erl_form('>') == {:eof, 1}
    end)

    capture_io("\"a\n\"", fn ->
      assert :io.scan_erl_form('>') == {:ok, [{:string, 1, 'a\n'}], 2}
      assert :io.scan_erl_form('>') == {:eof, 1}
    end)

    capture_io(":erl. mof*,,l", fn ->
      assert :io.scan_erl_form('>') == {:ok, [{:":", 1}, {:atom, 1, :erl}, {:dot, 1}], 1}
      assert :io.scan_erl_form('>') == {:ok, [{:atom, 1, :mof}, {:*, 1}, {:",", 1}, {:",", 1}, {:atom, 1, :l}], 1}
      assert :io.scan_erl_form('>') == {:eof, 1}
    end)

    capture_io("a\nb\nc", fn ->
      assert GetUntil.get_line == "a\n"
      assert GetUntil.get_line == "b\n"
      assert GetUntil.get_line == :eof
    end)
  end

  test "with setopts" do
    assert capture_io(fn ->
      assert :io.setopts({:encoding, :latin1}) == {:error, :enotsup}
    end) == ""
  end

  test "with getopts" do
    assert capture_io(fn ->
      assert :io.getopts == {:ok, [binary: true, encoding: :unicode]}
    end) == ""
  end

  test "with columns" do
    assert capture_io(fn ->
      :io.columns
    end) == ""

    capture_io(fn ->
      assert :io.columns == {:error, :enotsup}
    end)
  end

  test "with rows" do
    assert capture_io(fn ->
      :io.rows
    end) == ""

    capture_io(fn ->
      assert :io.rows == {:error, :enotsup}
    end)
  end

  test "with multiple IO requests" do
    assert capture_io(fn ->
      send_and_receive_io({:requests, [{:put_chars, :unicode, "a"},
                                        {:put_chars, :unicode, "b"}]})
    end) == "ab"

    capture_io(fn ->
      assert send_and_receive_io({:requests, [{:put_chars, :unicode, "a"},
                                               {:put_chars, :unicode, "b"}]}) == :ok
    end)
  end

  test "with unknown IO request" do
    assert capture_io(fn ->
      send_and_receive_io(:unknown)
    end) == ""

    capture_io(fn ->
      assert send_and_receive_io(:unknown) == {:error, :request}
    end)
  end

  test "device re-registering" do
    {_pid, ref} = spawn_monitor(fn ->
      capture_io(:stderr, fn ->
        spawn_link(Kernel, :exit, [:shutdown])
        Process.sleep(:infinity)
      end)
    end)

    # Assert the process is down then invoke capture_io
    # to trigger the ExUnit.Server, ensuring the DOWN
    # message from previous capture_io has been processed
    assert_receive {:DOWN, ^ref, _, _, :shutdown}
    _ = capture_io(fn -> "trigger" end)
    assert capture_io(:stderr, fn -> nil end)
  end

  test "with assert inside" do
    try do
      capture_io(fn ->
        assert false
      end)
    rescue
      error in [ExUnit.AssertionError] ->
        assert error.message == "Expected truthy, got false"
    end
  end

  test "capture :stderr by two processes" do
    spawn(fn -> capture_io(:stderr, fn -> Process.sleep(100) end) end)
    Process.sleep(10)
    assert_raise RuntimeError, "IO device registered at :standard_error is already captured", fn ->
      capture_io(:stderr, fn -> nil end)
    end
    Process.sleep(100)
  end

  defp send_and_receive_io(req) do
    pid = self()
    send :erlang.group_leader, {:io_request, pid, pid, req}
    receive do
      {:io_reply, ^pid, res} -> res
    end
  end
end


warnings as errors

warnings as errors

defmodule OpenAperture.Manager.Controllers.ProductDeployments do
  require Logger

  use OpenAperture.Manager.Web, :controller

  import OpenAperture.Manager.Controllers.FormatHelper
  alias OpenAperture.Manager.Controllers.ResponseBodyFormatter
  import Ecto.Query
  import OpenAperture.Manager.Router.Helpers

  alias OpenAperture.Manager.Endpoint
  alias OpenAperture.Manager.Repo
  alias OpenAperture.Manager.DB.Models.Product
  alias OpenAperture.Manager.DB.Models.ProductDeployment
  #alias OpenAperture.Manager.DB.Queries.ProductDeployment, as: DeploymentQuery
  alias OpenAperture.Manager.DB.Models.ProductDeploymentPlan
  alias OpenAperture.Manager.DB.Models.ProductDeploymentStep

  alias OpenAperture.ProductDeploymentOrchestratorApi.Request, as: OrchestratorRequest
  alias OpenAperture.ProductDeploymentOrchestratorApi.ProductDeploymentOrchestrator.Publisher, as: OrchestratorPublisher

  @deployment_sendable_fields [:id, :product_id, :product_deployment_plan_id, :product_environment_id, :execution_options, :completed, :duration, :output, :inserted_at, :updated_at]
  @deployment_steps_sendable_fields [:id, :product_deployment_plan_step_id, :product_deployment_plan_step_type, :duration, :successful, :execution_options, :output, :sequence, :inserted_at, :updated_at]

  plug :action

  # GET /products/:product_name/deployments
  def index(conn, %{"product_name" => product_name} = params) do
    product_name
    |> URI.decode
    |> get_product_by_name
    |> case do
      nil ->
        conn
        |> put_status(:not_found)
        |> json ResponseBodyFormatter.error_body(:not_found, "ProductDeployment")
      product ->
        if params["page"] == nil do
          page_number = 1
        else
          page_number = Integer.parse(params["page"])
        end

       product_id = product.id
       page = ProductDeployment
         |> where([p], p.product_id == ^product_id)
         |> order_by([p], desc: p.inserted_at)
         |> Repo.paginate(page: page_number)

       deployments = page.entries
         |> Enum.map(&to_sendable(&1, @deployment_sendable_fields))

       json conn, %{deployments: deployments, total_pages: page.total_pages, total_deployments: page.total_entries}
    end
  end

  # GET /products/:product_name/deployments/:deployment_id
  def show(conn, %{"product_name" => product_name, "deployment_id" => deployment_id}) do
    product_name = URI.decode(product_name)

    case get_product_deployment(product_name, deployment_id) do
      nil ->
        conn
        |> put_status(:not_found)
        |> json ResponseBodyFormatter.error_body(:not_found, "ProductDeployment")
      pd ->
        conn
        |> json to_sendable(pd, @deployment_sendable_fields)
    end
  end

  # POST /products/:product_name/deployments
  def create(conn, %{"product_name" => product_name, "plan_name" => plan_name} = params) do
    product_name = URI.decode(product_name)

    case get_deployment_plan_by_name(product_name, plan_name) do
      nil ->
        conn
        |> put_status(:not_found)
        |> json ResponseBodyFormatter.error_body(:not_found, "ProductDeployment")
      {product, plan} ->
        execution_options_string = params["execution_options"] || ""
                                   |> Poison.encode!

        new_map = %{
          "product_id" => product.id,
          "product_deployment_plan_id" => plan.id,
          "execution_options" => execution_options_string,
          "completed" => false
        }

        params = Map.merge(params, new_map)

        changeset = ProductDeployment.new(params)

        if changeset.valid? do
          deployment = Repo.insert!(changeset)

          path = product_deployments_path(Endpoint, :show, product_name, deployment.id)
          conn
          |> put_resp_header("location", path)
          |> resp :created, ""
        else
          conn
          |> put_status(:bad_request)
          |> json ResponseBodyFormatter.error_body(changeset.errors, "ProductDeployment")
        end

    end

  end

  # This clause will only be hit if the request was missing a "plan_name" field
  # POST /products/:product_name/deployments
  def create(conn, _params) do
    conn
    |> put_status(:bad_request)
    |> json ResponseBodyFormatter.error_body(:bad_request, "ProductDeployment")
  end

  def update(conn, %{"product_name" => product_name, "deployment_id" => deployment_id} = params) do
    product_name = URI.decode(product_name)

    case get_product_deployment(product_name, deployment_id) do
      nil ->
        conn
        |> put_status(:not_found)
        |> json ResponseBodyFormatter.error_body(:not_found, "ProductDeployment")
      deployment ->
        changeset = ProductDeployment.update(deployment, params)
        if changeset.valid? do
          deployment = Repo.update!(changeset)
          path = product_deployments_path(Endpoint, :show, product_name, deployment.id)

          conn
          |> put_resp_header("location", path)
          |> resp :no_content, ""
        else
          conn
          |> put_status(:bad_request)
          |> json ResponseBodyFormatter.error_body(changeset.errors, "ProductEnvironment")
        end
    end
  end

  # DELETE /products/:product_name/deployments/:deploymen_id
  def destroy(conn, %{"product_name" => product_name, "deployment_id" => deployment_id}) do
    product_name = URI.decode(product_name)

    case get_product_deployment(product_name, deployment_id) do
      nil ->
        conn
        |> put_status(:not_found)
        |> json ResponseBodyFormatter.error_body(:not_found, "ProductDeployment")
      pd ->
        result = ProductDeployment.destroy(pd)

        case result do
          :ok ->
            conn
            |> resp :no_content, ""
          {:error, _reason} ->
            conn
            |> put_status(:internal_server_error)
            |> json ResponseBodyFormatter.error_body(:internal_server_error, "ProductDeployment")
        end
    end
  end

  # GET /products/:product_name/deployments/:deployment_id/steps
  def index_steps(conn, %{"product_name" => product_name, "deployment_id" => deployment_id}) do
    product_name = URI.decode(product_name)

    case get_product_deployment(product_name, deployment_id) do
      nil ->
        conn
        |> put_status(:not_found)
        |> json ResponseBodyFormatter.error_body(:not_found, "ProductDeployment")
      pd ->
        steps = ProductDeploymentStep
                |> where([pdps], pdps.product_deployment_id == ^pd.id)
                |> Repo.all
                |> Enum.map(&(to_sendable(&1, @deployment_steps_sendable_fields)))
        conn
        |> json steps
    end
  end

  def execute(conn, %{"product_name" => product_name, "id" => id} = _params) do
    deployment = get_product_deployment(product_name, id)

    cond do
      deployment == nil -> resp(conn, :not_found, "")
      deployment.completed == true -> resp(conn, :conflict, "Workflow has already completed")
      deployment.output != "[]" -> resp(conn, :conflict, "Workflow has already been started")
      true ->
        request = %OrchestratorRequest{}
        request = %{request | deployment: deployment}
        request = %{request | completed: nil}
        request = %{request | product_deployment_orchestration_exchange_id: Configuration.get_current_exchange_id}
        request = %{request | product_deployment_orchestration_broker_id: Configuration.get_current_broker_id}
        request = %{request | product_deployment_orchestration_queue: "product_deployment_orchestrator"}

        case OrchestratorPublisher.execute_orchestration(request) do
          :ok ->
            path = OpenAperture.Manager.Router.Helpers.workflows_path(Endpoint, :show, id)

            # Set location header
            conn
            |> put_resp_header("location", path)
            |> resp(:accepted, "")
          {:error, reason} ->
            Logger.error("Error executing Workflow #{id}: #{inspect reason}")
            resp(conn, :internal_server_error, "")
        end
    end
  end

  defp get_deployment_plan_by_name(product_name, deployment_plan_name) do
    ProductDeploymentPlan
    |> join(:inner, [pdp], p in Product, pdp.product_id == p.id and fragment("lower(?) = lower(?)", p.name, ^product_name))
    |> where([pdp, p], fragment("lower(?) = lower(?)", pdp.name, ^deployment_plan_name))
    |> select([pdp, p], {p, pdp})
    |> Repo.one
  end

  defp get_product_deployment(product_name, deployment_id) do
    ProductDeployment
    |> join(:inner, [pd], p in Product, pd.product_id == p.id and fragment("lower(?) = lower(?)", p.name, ^product_name))
    |> where([pd, p], pd.id == ^deployment_id)
    |> Repo.one
  end

  defp get_product_by_name(product_name) do
    Product
    |> where([p], fragment("lower(?) = lower(?)", p.name, ^product_name))
    |> Repo.one
  end

end

defmodule OpenAperture.Manager.Controllers.ProductDeployments do
  require Logger

  use OpenAperture.Manager.Web, :controller

  import OpenAperture.Manager.Controllers.FormatHelper
  alias OpenAperture.Manager.Controllers.ResponseBodyFormatter
  import Ecto.Query
  import OpenAperture.Manager.Router.Helpers

  alias OpenAperture.Manager.Endpoint
  alias OpenAperture.Manager.Repo
  alias OpenAperture.Manager.DB.Models.Product
  alias OpenAperture.Manager.DB.Models.ProductDeployment
  #alias OpenAperture.Manager.DB.Queries.ProductDeployment, as: DeploymentQuery
  alias OpenAperture.Manager.DB.Models.ProductDeploymentPlan
  alias OpenAperture.Manager.DB.Models.ProductDeploymentStep

  alias OpenAperture.ProductDeploymentOrchestratorApi.Request, as: OrchestratorRequest
  alias OpenAperture.ProductDeploymentOrchestratorApi.ProductDeploymentOrchestrator.Publisher, as: OrchestratorPublisher

  @deployment_sendable_fields [:id, :product_id, :product_deployment_plan_id, :product_environment_id, :execution_options, :completed, :duration, :output, :inserted_at, :updated_at]
  @deployment_steps_sendable_fields [:id, :product_deployment_plan_step_id, :product_deployment_plan_step_type, :duration, :successful, :execution_options, :output, :sequence, :inserted_at, :updated_at]

  plug :action

  # GET /products/:product_name/deployments
  def index(conn, %{"product_name" => product_name} = params) do
    product_name
    |> URI.decode
    |> get_product_by_name
    |> case do
      nil ->
        conn
        |> put_status(:not_found)
        |> json ResponseBodyFormatter.error_body(:not_found, "ProductDeployment")
      product ->
        if params["page"] == nil do
         page_number = 0
        else
          page_number = Integer.parse(params["page"])
        end

       product_id = product.id
       page = ProductDeployment
         |> where([p], p.product_id == ^product_id)
         |> order_by([p], desc: p.inserted_at)
         |> Repo.paginate(page: params["page"])

       deployments = page.entries
         |> Enum.map(&to_sendable(&1, @deployment_sendable_fields))

       json conn, %{deployments: deployments, total_pages: page.total_pages, total_deployments: page.total_entries}
    end
  end

  # GET /products/:product_name/deployments/:deployment_id
  def show(conn, %{"product_name" => product_name, "deployment_id" => deployment_id}) do
    product_name = URI.decode(product_name)

    case get_product_deployment(product_name, deployment_id) do
      nil ->
        conn
        |> put_status(:not_found)
        |> json ResponseBodyFormatter.error_body(:not_found, "ProductDeployment")
      pd ->
        conn
        |> json to_sendable(pd, @deployment_sendable_fields)
    end
  end

  # POST /products/:product_name/deployments
  def create(conn, %{"product_name" => product_name, "plan_name" => plan_name} = params) do
    product_name = URI.decode(product_name)

    case get_deployment_plan_by_name(product_name, plan_name) do
      nil ->
        conn
        |> put_status(:not_found)
        |> json ResponseBodyFormatter.error_body(:not_found, "ProductDeployment")
      {product, plan} ->
        execution_options_string = params["execution_options"] || ""
                                   |> Poison.encode!

        new_map = %{
          "product_id" => product.id,
          "product_deployment_plan_id" => plan.id,
          "execution_options" => execution_options_string,
          "completed" => false
        }

        params = Map.merge(params, new_map)

        changeset = ProductDeployment.new(params)

        if changeset.valid? do
          deployment = Repo.insert!(changeset)

          path = product_deployments_path(Endpoint, :show, product_name, deployment.id)
          conn
          |> put_resp_header("location", path)
          |> resp :created, ""
        else
          conn
          |> put_status(:bad_request)
          |> json ResponseBodyFormatter.error_body(changeset.errors, "ProductDeployment")
        end

    end

  end

  # This clause will only be hit if the request was missing a "plan_name" field
  # POST /products/:product_name/deployments
  def create(conn, _params) do
    conn
    |> put_status(:bad_request)
    |> json ResponseBodyFormatter.error_body(:bad_request, "ProductDeployment")
  end

  def update(conn, %{"product_name" => product_name, "deployment_id" => deployment_id} = params) do
    product_name = URI.decode(product_name)

    case get_product_deployment(product_name, deployment_id) do
      nil ->
        conn
        |> put_status(:not_found)
        |> json ResponseBodyFormatter.error_body(:not_found, "ProductDeployment")
      deployment ->
        changeset = ProductDeployment.update(deployment, params)
        if changeset.valid? do
          deployment = Repo.update!(changeset)
          path = product_deployments_path(Endpoint, :show, product_name, deployment.id)

          conn
          |> put_resp_header("location", path)
          |> resp :no_content, ""
        else
          conn
          |> put_status(:bad_request)
          |> json ResponseBodyFormatter.error_body(changeset.errors, "ProductEnvironment")
        end
    end
  end

  # DELETE /products/:product_name/deployments/:deploymen_id
  def destroy(conn, %{"product_name" => product_name, "deployment_id" => deployment_id}) do
    product_name = URI.decode(product_name)

    case get_product_deployment(product_name, deployment_id) do
      nil ->
        conn
        |> put_status(:not_found)
        |> json ResponseBodyFormatter.error_body(:not_found, "ProductDeployment")
      pd ->
        result = ProductDeployment.destroy(pd)

        case result do
          :ok ->
            conn
            |> resp :no_content, ""
          {:error, _reason} ->
            conn
            |> put_status(:internal_server_error)
            |> json ResponseBodyFormatter.error_body(:internal_server_error, "ProductDeployment")
        end
    end
  end

  # GET /products/:product_name/deployments/:deployment_id/steps
  def index_steps(conn, %{"product_name" => product_name, "deployment_id" => deployment_id}) do
    product_name = URI.decode(product_name)

    case get_product_deployment(product_name, deployment_id) do
      nil ->
        conn
        |> put_status(:not_found)
        |> json ResponseBodyFormatter.error_body(:not_found, "ProductDeployment")
      pd ->
        steps = ProductDeploymentStep
                |> where([pdps], pdps.product_deployment_id == ^pd.id)
                |> Repo.all
                |> Enum.map(&(to_sendable(&1, @deployment_steps_sendable_fields)))
        conn
        |> json steps
    end
  end

  def execute(conn, %{"product_name" => product_name, "id" => id} = params) do
    deployment = get_product_deployment(product_name, id)

    cond do
      deployment == nil -> resp(conn, :not_found, "")
      deployment.completed == true -> resp(conn, :conflict, "Workflow has already completed")
      deployment.output != "[]" -> resp(conn, :conflict, "Workflow has already been started")
      true ->
        payload = deployment

        request = %OrchestratorRequest{}
        request = %{request | deployment: deployment}
        request = %{request | completed: nil}
        request = %{request | product_deployment_orchestration_exchange_id: Configuration.get_current_exchange_id}
        request = %{request | product_deployment_orchestration_broker_id: Configuration.get_current_broker_id}
        request = %{request | product_deployment_orchestration_queue: "product_deployment_orchestrator"}

        case OrchestratorPublisher.execute_orchestration(request) do
          :ok ->
            path = OpenAperture.Manager.Router.Helpers.workflows_path(Endpoint, :show, id)

            # Set location header
            conn
            |> put_resp_header("location", path)
            |> resp(:accepted, "")
          {:error, reason} ->
            Logger.error("Error executing Workflow #{id}: #{inspect reason}")
            resp(conn, :internal_server_error, "")
        end
    end
  end

  defp get_deployment_plan_by_name(product_name, deployment_plan_name) do
    ProductDeploymentPlan
    |> join(:inner, [pdp], p in Product, pdp.product_id == p.id and fragment("lower(?) = lower(?)", p.name, ^product_name))
    |> where([pdp, p], fragment("lower(?) = lower(?)", pdp.name, ^deployment_plan_name))
    |> select([pdp, p], {p, pdp})
    |> Repo.one
  end

  defp get_product_deployment(product_name, deployment_id) do
    ProductDeployment
    |> join(:inner, [pd], p in Product, pd.product_id == p.id and fragment("lower(?) = lower(?)", p.name, ^product_name))
    |> where([pd, p], pd.id == ^deployment_id)
    |> Repo.one
  end

  defp get_product_by_name(product_name) do
    Product
    |> where([p], fragment("lower(?) = lower(?)", p.name, ^product_name))
    |> Repo.one
  end

end


delete logger

delete logger

require Logger, as: L
alias Keyword, as: K

defmodule Bottler.Helpers do

  @doc """
    Parses given args using OptionParser with given opts.
    Raises ArgumentError if any unknown argument found.
  """
  def parse_args!(args, opts \\ []) do
    {switches, remaining_args, unknown} = OptionParser.parse(args, opts)

    case unknown do
      [] -> {switches, remaining_args}
      x -> raise ArgumentError, message: "Unknown arguments: #{inspect x}"
    end
  end

  @doc """
    Convenience to get environment bits. Avoid all that repetitive
    `Application.get_env( :myapp, :blah, :blah)` noise.
  """
  def env(key, default \\ nil), do: env(:bottler, key, default)
  def env(app, key, default), do: Application.get_env(app, key, default)

  @doc """
    Get current app's name
  """
  def app, do: Mix.Project.get!.project[:app]

  @doc """
    Chop final end of line chars to given string
  """
  def chop(s), do: String.replace(s, ~r/[\n\r\\"]/, "")

  @doc """
  Spit to output any passed variable, with location information.

  If `sample` option is given, it should be a float between 0.0 and 1.0.
  Output will be produced randomly with that probability.

  Given `opts` will be fed straight into `inspect`. Any option accepted by it should work.
  """
  defmacro spit(obj \\ "", opts \\ []) do
    quote do
      opts = unquote(opts)
      obj = unquote(obj)
      opts = Keyword.put(opts, :env, __ENV__)

      Bottler.Helpers.maybe_spit(obj, opts, opts[:sample])
      obj  # chainable
    end
  end

  @doc false
  def maybe_spit(obj, opts, nil), do: do_spit(obj, opts)
  def maybe_spit(obj, opts, prob) when is_float(prob) do
    if :rand.uniform <= prob, do: do_spit(obj, opts)
  end

  defp do_spit(obj, opts) do
    %{file: file, line: line} = opts[:env]
    name = Process.info(self())[:registered_name]
    chain = [ :bright, :red, "\n\n#{file}:#{line}", :normal, "\n     #{inspect self()}", :green," #{name}"]

    msg = inspect(obj, opts)
    chain = chain ++ [:red, "\n\n#{msg}"]

    (chain ++ ["\n\n", :reset]) |> IO.ANSI.format(true) |> IO.puts
  end

  @doc """
  Print to stdout a _TODO_ message, with location information.
  """
  defmacro todo(msg \\ "") do
    quote do
      %{file: file, line: line} = __ENV__
      [ :yellow, "\nTODO: #{file}:#{line} #{unquote(msg)}\n", :reset]
      |> IO.ANSI.format(true)
      |> IO.puts
      :todo
    end
  end

  @doc """
    Pipable log. Calls Logger and then returns first argument.
    Second argument is a template, or a function returning a template.
    To render the template `EEx` will be used, and the first argument will be passed.
  """
  def pipe_log(obj, template, opts \\ [])
  def pipe_log(obj, fun, opts) when is_function(fun) do
    pipe_log(obj, fun.(obj), opts)
  end
  def pipe_log(obj, template, opts) do
    opts = opts |> defaults(level: :info)

    msg = template |> EEx.eval_string(data: obj)
    :ok = L.log opts[:level], msg
    obj
  end

  @doc """
    Run given function in different Tasks. One `Task` for each entry on given
    list. Each entry on list will be given as args for the function.

    Explodes if `timeout` is reached waiting for any particular task to end.

    Once run, each return value from each task is compared with `expected`.
    It returns `{:ok, results}` if _every_ task returned as expected.

    If any task did not return as expected, then it returns `{:error, results}`.

    If `to_s` is `true` then results are fed to `to_string` before return.
    This is useful when returned value is a char list and is to be printed to
    stdout.
  """
  def in_tasks(list, fun, opts \\ []) do
    expected = opts |> K.get(:expected, :ok)
    timeout = opts |> K.get(:timeout, 60_000)
    to_s = opts |> K.get(:to_s, false)

    # run and get results
    tasks = for args <- list, into: [], do: Task.async(fn -> fun.(args) end)
    results = for t <- tasks, into: [], do: Task.await(t, timeout)

    # figure out return value
    sign = if Enum.all?(results, &(&1 == expected)), do: :ok, else: :error
    if to_s, do: {sign, to_string(results)},
        else: {sign, results}
  end

  @doc """
    Set up `prod` environment variables. Be careful, it only applies to newly
    loaded modules.

    If `MIX_ENV` was already set, then it's not overwritten.
  """
  def set_prod_environment do
    use Mix.Config

    if System.get_env("MIX_ENV") do
      L.info "MIX_ENV was already set, not forcing..."
    else
      L.info "Setting up 'prod' environment..."
      System.put_env "MIX_ENV","prod"
      Mix.env :prod
    end

    res = "config/config.exs" |> Path.absname
          |> Mix.Config.read! |> Mix.Config.persist

    # different responses for Elixir 1.0 and 1.1, we want both
    if not is_ok_response_for_10_and_11(res),
      do: raise "Could not persist the requested config: #{inspect(res)}"

    # destroy other environments' traces, helpful for environment debugging
    # {:ok, _} = File.rm_rf("_build")

    # support dynamic config, force project's compilation
    [] = :os.cmd 'touch config/config.exs'

    :ok
  end

  # hack to allow different responses for Elixir 1.0 and 1.1
  # 1.0 -> :ok
  # 1.1 -> is a list and includes at least bottler config keys
  #
  defp is_ok_response_for_10_and_11(res) do
    case res do
      :ok -> true
      x when is_list(x) -> Enum.all?([:logger,:bottler], fn(i)-> i in res end)
      _ -> false
    end
  end

  @doc """
    Returns `:bottler` config keywords. It also validates they are all set.
    Raises an error if anything looks wrong.
  """
  def read_and_validate_config do
    c = [ scripts_folder: ".bottler/scripts",
          into_path_folder: "~/.local/bin",
          remote_port: 22,
          additional_folders: [],
          ship: [],
          green_flag: [],
          goto: [terminal: "terminator -T '<%= title %>' -e '<%= command %>' &"] ]
        |> K.merge(Application.get_env(:bottler, :params))

    if not is_valid_servers_list?(c[:servers]),
      do: raise ":bottler :servers should look like \n" <>
                "    [srvname: [ip: '' | rest ] | rest ]\n" <>
                "or [gce_project: \"project-id\"]\n" <>
                "but it was\n    #{inspect c[:servers]}"

    c
  end

  def validate_branch(config) do
    case check_active_branch(config[:forced_branch]) do
      true -> config
      false -> L.error "You are not in branch '#{config[:forced_branch]}'."
        raise "WrongBranchError"
    end
  end

  defp check_active_branch(nil), do: true
  defp check_active_branch(branch) do
    "git branch 2> /dev/null | sed -e '/^[^*]/d' -e \"s/* \\(.*\\)/\\1/\""
    |> to_charlist
    |> :os.cmd
    |> to_string
    |> String.replace("\n","")
    |> Kernel.==(branch)
  end

  defp is_valid_servers_list?(s) do
    K.keyword?(s) and ( is_gce_servers?(s) or is_default_servers?(s) )
  end

  defp is_default_servers?(s),
    do: Enum.all?(s, fn({_,v})-> :ip in K.keys(v) end)

  defp is_gce_servers?(s),
    do: match?(%{gce_project: _} , Enum.into(s,%{}))

  defp get_servers_type(s) do
    case is_gce_servers?(s) do
      true -> :gce
      false -> case is_default_servers?(s) do
        true -> :default
        false -> :none
      end
    end
  end

  @doc """
    Return the server list, whatever its type is.
    Raises an error if it's not recognised.
  """
  def guess_server_list(config) do
    case get_servers_type(config[:servers]) do
      :default -> config[:servers]  # explicit, non GCE
      :gce -> get_gce_server_list(config)
      :none -> raise "Server list specification not recognised: '#{inspect config[:servers]}'"
    end
  end

  @doc """
    Returns a copy of given config with the servers list well formed,
    and filtered using given parsed switches.
  """
  def inline_resolve_servers(config), do: inline_resolve_servers(config, [])
  def inline_resolve_servers(config, switches) do
    servers_list = guess_server_list(config)
      |> inline_filter_servers(switches[:servers])

    config |> Keyword.put(:servers, servers_list)
  end

  defp inline_filter_servers(servers, nil), do: servers
  defp inline_filter_servers(servers, switch) when is_binary(switch) do
    names = switch |> String.split(",") |> Enum.map(&Regex.compile!(&1))
    servers
    |> Enum.filter(fn({k,_})->
      k = to_string(k)
      names |> Enum.any?(&Regex.match?(&1, k))
    end)
  end

  defp get_gce_server_list(config) do
    L.info "Getting server list from GCE..."

    config
    |> Bottler.Helpers.GCE.instances
    |> Enum.map(fn(i)->
      name = i["name"] |> String.to_atom
      ip = i |> get_nested(["networkInterfaces", 0, "accessConfigs", 0, "natIP"])
      {name, [ip: ip]}
    end)
    |> pipe_log("<%= inspect data %>")
  end

  @doc """
    Adds the `name` and a new `id` element to the given servers `Keyword`.
    It returns a plain list, with a `Keyword` for each server.
  """
  def prepare_servers(servers) do
    servers |> Enum.map(fn({name, values}) ->
      values ++ [ name: name, id: "#{name}(#{values[:ip]})" ]
    end)
  end

  @doc """
    Writes an Elixir/Erlang term to the provided path
  """
  def write_term(path, term),
    do: :file.write_file('#{path}', :io_lib.fwrite('~p.\n', [term]))

  @doc """
    Reads a file as Erlang terms
  """
  def read_terms(path), do: :file.consult('#{path}')

  @doc """
    Apply given defaults to given Keyword. Returns merged Keyword.

    The inverse of `Keyword.merge`, best suited to apply some defaults in a
    chainable way.

    Ex:
      kw = gather_data
        |> transform_data
        |> H.defaults(k1: 1234, k2: 5768)
        |> here_i_need_defaults

    Instead of:
      kw1 = gather_data
        |> transform_data
      kw = [k1: 1234, k2: 5768]
        |> Keyword.merge(kw1)
        |> here_i_need_defaults

  """
  def defaults(args, defs) do
    defs |> Keyword.merge(args)
  end

  @doc """
    Run given command through `Mix.Shell`
  """
  def cmd(command) do
    case Mix.Shell.cmd(command, &(IO.write(&1)) ) do
      0 -> :ok
      _ -> {:error, "Release step failed. Please fix any errors and try again."}
    end
  end

  @doc """
    `ls` with full paths
    Returns the list of full paths. An empty list if anything fails
  """
  def full_ls(path) do
    expanded = Path.expand(path)
    case path |> File.ls do
      {:ok, list} -> Enum.map(list,&( "#{expanded}/#{&1}" ))
      _ -> []
    end
  end

  @doc """
    Delete, then recreate given folders
  """
  def empty_dirs(paths) when is_list(paths) do
    for p <- paths, do: empty_dir(p)
  end

  @doc """
    Delete, then recreate given folder
  """
  def empty_dir(path) do
    File.rm_rf! path
    File.mkdir_p! path
  end

  @doc """
    Log local and remote versions of erts
  """
  def check_erts_versions(config) do
    :ssh.start # just in case

    local_release = :erlang.system_info(:version) |> to_string

    {_, remote_releases} = config[:servers] |> K.values
      |> in_tasks( fn(args)->
        user = config[:remote_user] |> to_charlist
        ip = args[:ip] |> to_charlist
        {:ok, conn} = SSHEx.connect(ip: ip, user: user)
        cmd = "source ~/.bash_profile && erl -eval 'erlang:display(erlang:system_info(version)), halt().'  -noshell" |> to_charlist
        SSHEx.cmd!(conn, cmd)
        |> String.replace(~r/[\n\r\\"]/, "")
        |> Kernel.<>(" on #{ip}")
      end, to_s: false)

    level = if Enum.all?(remote_releases, &( local_release == &1 |> String.split(" ") |> List.first )), do: :info, else: :error

    L.log level, "Compiling against Erlang/OTP release #{local_release}. Remote releases are #{Enum.map_join(remote_releases, ", ", &(&1))}."

    if level == :error, do: raise "Aborted release"
  end

  @doc """
  Get the value at given coordinates inside the given nested structure.
  The structure must be composed of `Map` and `List`.

  If coordinates do not exist `nil` is returned.
  """
  def get_nested(data, []), do: data
  def get_nested(data, [key | rest]) when is_map(data) do
    data |> Map.get(key) |> get_nested(rest)
  end
  def get_nested(data, [key | rest]) when is_list(data) do
    data |> Enum.at(key) |> get_nested(rest)
  end
  def get_nested(_, _), do: nil
  def get_nested(data, keys, default), do: get_nested(data, keys) || default

  @doc """
  Put given value on given coordinates inside the given structure.
  Returns updated structure.

  If coordinates do not exist, needed structures are created.
  """
  def put_nested(nil, [key], value) when is_integer(key),
    do: put_nested([], [key], value)
  def put_nested(nil, [key | _] = keys, value) when is_integer(key),
    do: put_nested([], keys, value)
  def put_nested(nil, [key], value),
    do: put_nested(%{}, [key], value)
  def put_nested(nil, keys, value),
    do: put_nested(%{}, keys, value)
  def put_nested(data, [key], value) when is_map(data) do
    {_, v} = Map.get_and_update(data, key, &({&1, value}))
    v
  end
  def put_nested(data, [key | rest], value) when is_map(data) do
    {_, v} = Map.get_and_update(data, key, &({&1, put_nested(&1, rest, value)}))
    v
  end
  def put_nested(data, [key], value) when is_list(data) and is_integer(key) do
    case List.update_at(data, key, fn(_)-> value end) do
      ^data -> data |> grow_list(key + 1) |> put_nested([key], value)
      x -> x
    end
  end
  def put_nested(data, [key | rest] = keys, value) when is_list(data) and is_integer(key) do
    case List.update_at(data, key, &put_nested(&1, rest, value)) do
      ^data -> data |> grow_list(key + 1) |> put_nested(keys, value)
      x -> x
    end
  end

  @doc """
  Fills given list with nils until it is of the given length
  """
  def grow_list(list, length) do
    count = length - Enum.count(list)
    list ++ List.duplicate(nil, count)
  end

  @doc """
  Method applies a function against element if condition is true.
  """
  def run_if(elem, condition, fun) do
    cond do
      condition -> fun.(elem)
      true -> elem
    end
  end
end

require Logger, as: L
alias Keyword, as: K

defmodule Bottler.Helpers do

  @doc """
    Parses given args using OptionParser with given opts.
    Raises ArgumentError if any unknown argument found.
  """
  def parse_args!(args, opts \\ []) do
    {switches, remaining_args, unknown} = OptionParser.parse(args, opts)

    case unknown do
      [] -> {switches, remaining_args}
      x -> raise ArgumentError, message: "Unknown arguments: #{inspect x}"
    end
  end

  @doc """
    Convenience to get environment bits. Avoid all that repetitive
    `Application.get_env( :myapp, :blah, :blah)` noise.
  """
  def env(key, default \\ nil), do: env(:bottler, key, default)
  def env(app, key, default), do: Application.get_env(app, key, default)

  @doc """
    Get current app's name
  """
  def app, do: Mix.Project.get!.project[:app]

  @doc """
    Chop final end of line chars to given string
  """
  def chop(s), do: String.replace(s, ~r/[\n\r\\"]/, "")

  @doc """
  Spit to output any passed variable, with location information.

  If `sample` option is given, it should be a float between 0.0 and 1.0.
  Output will be produced randomly with that probability.

  Given `opts` will be fed straight into `inspect`. Any option accepted by it should work.
  """
  defmacro spit(obj \\ "", opts \\ []) do
    quote do
      opts = unquote(opts)
      obj = unquote(obj)
      opts = Keyword.put(opts, :env, __ENV__)

      Bottler.Helpers.maybe_spit(obj, opts, opts[:sample])
      obj  # chainable
    end
  end

  @doc false
  def maybe_spit(obj, opts, nil), do: do_spit(obj, opts)
  def maybe_spit(obj, opts, prob) when is_float(prob) do
    if :rand.uniform <= prob, do: do_spit(obj, opts)
  end

  defp do_spit(obj, opts) do
    %{file: file, line: line} = opts[:env]
    name = Process.info(self())[:registered_name]
    chain = [ :bright, :red, "\n\n#{file}:#{line}", :normal, "\n     #{inspect self()}", :green," #{name}"]

    msg = inspect(obj, opts)
    chain = chain ++ [:red, "\n\n#{msg}"]

    (chain ++ ["\n\n", :reset]) |> IO.ANSI.format(true) |> IO.puts
  end

  @doc """
  Print to stdout a _TODO_ message, with location information.
  """
  defmacro todo(msg \\ "") do
    quote do
      %{file: file, line: line} = __ENV__
      [ :yellow, "\nTODO: #{file}:#{line} #{unquote(msg)}\n", :reset]
      |> IO.ANSI.format(true)
      |> IO.puts
      :todo
    end
  end

  @doc """
    Pipable log. Calls Logger and then returns first argument.
    Second argument is a template, or a function returning a template.
    To render the template `EEx` will be used, and the first argument will be passed.
  """
  def pipe_log(obj, template, opts \\ [])
  def pipe_log(obj, fun, opts) when is_function(fun) do
    pipe_log(obj, fun.(obj), opts)
  end
  def pipe_log(obj, template, opts) do
    opts = opts |> defaults(level: :info)

    msg = template |> EEx.eval_string(data: obj)
    :ok = L.log opts[:level], msg
    obj
  end

  @doc """
    Run given function in different Tasks. One `Task` for each entry on given
    list. Each entry on list will be given as args for the function.

    Explodes if `timeout` is reached waiting for any particular task to end.

    Once run, each return value from each task is compared with `expected`.
    It returns `{:ok, results}` if _every_ task returned as expected.

    If any task did not return as expected, then it returns `{:error, results}`.

    If `to_s` is `true` then results are fed to `to_string` before return.
    This is useful when returned value is a char list and is to be printed to
    stdout.
  """
  def in_tasks(list, fun, opts \\ []) do
    expected = opts |> K.get(:expected, :ok)
    timeout = opts |> K.get(:timeout, 60_000)
    to_s = opts |> K.get(:to_s, false)

    # run and get results
    tasks = for args <- list, into: [], do: Task.async(fn -> fun.(args) end)
    results = for t <- tasks, into: [], do: Task.await(t, timeout)

    # figure out return value
    sign = if Enum.all?(results, &(&1 == expected)), do: :ok, else: :error
    if to_s, do: {sign, to_string(results)},
        else: {sign, results}
  end

  @doc """
    Set up `prod` environment variables. Be careful, it only applies to newly
    loaded modules.

    If `MIX_ENV` was already set, then it's not overwritten.
  """
  def set_prod_environment do
    use Mix.Config

    if System.get_env("MIX_ENV") do
      L.info "MIX_ENV was already set, not forcing..."
    else
      L.info "Setting up 'prod' environment..."
      System.put_env "MIX_ENV","prod"
      Mix.env :prod
    end

    res = "config/config.exs" |> Path.absname
          |> Mix.Config.read! |> Mix.Config.persist

    # different responses for Elixir 1.0 and 1.1, we want both
    if not is_ok_response_for_10_and_11(res),
      do: raise "Could not persist the requested config: #{inspect(res)}"

    # destroy other environments' traces, helpful for environment debugging
    # {:ok, _} = File.rm_rf("_build")

    # support dynamic config, force project's compilation
    [] = :os.cmd 'touch config/config.exs'

    :ok
  end

  # hack to allow different responses for Elixir 1.0 and 1.1
  # 1.0 -> :ok
  # 1.1 -> is a list and includes at least bottler config keys
  #
  defp is_ok_response_for_10_and_11(res) do
    case res do
      :ok -> true
      x when is_list(x) -> Enum.all?([:logger,:bottler], fn(i)-> i in res end)
      _ -> false
    end
  end

  @doc """
    Returns `:bottler` config keywords. It also validates they are all set.
    Raises an error if anything looks wrong.
  """
  def read_and_validate_config do
    c = [ scripts_folder: ".bottler/scripts",
          into_path_folder: "~/.local/bin",
          remote_port: 22,
          additional_folders: [],
          ship: [],
          green_flag: [],
          goto: [terminal: "terminator -T '<%= title %>' -e '<%= command %>' &"] ]
        |> K.merge(Application.get_env(:bottler, :params))

    if not is_valid_servers_list?(c[:servers]),
      do: raise ":bottler :servers should look like \n" <>
                "    [srvname: [ip: '' | rest ] | rest ]\n" <>
                "or [gce_project: \"project-id\"]\n" <>
                "but it was\n    #{inspect c[:servers]}"

    c
  end

  def validate_branch(config) do
    L.error "#{inspect config}"
    case check_active_branch(config[:forced_branch]) do
      true -> config
      false -> L.error "You are not in branch '#{config[:forced_branch]}'."
        raise "WrongBranchError"
    end
  end

  defp check_active_branch(nil), do: true
  defp check_active_branch(branch) do
    "git branch 2> /dev/null | sed -e '/^[^*]/d' -e \"s/* \\(.*\\)/\\1/\""
    |> to_charlist
    |> :os.cmd
    |> to_string
    |> String.replace("\n","")
    |> Kernel.==(branch)
  end

  defp is_valid_servers_list?(s) do
    K.keyword?(s) and ( is_gce_servers?(s) or is_default_servers?(s) )
  end

  defp is_default_servers?(s),
    do: Enum.all?(s, fn({_,v})-> :ip in K.keys(v) end)

  defp is_gce_servers?(s),
    do: match?(%{gce_project: _} , Enum.into(s,%{}))

  defp get_servers_type(s) do
    case is_gce_servers?(s) do
      true -> :gce
      false -> case is_default_servers?(s) do
        true -> :default
        false -> :none
      end
    end
  end

  @doc """
    Return the server list, whatever its type is.
    Raises an error if it's not recognised.
  """
  def guess_server_list(config) do
    case get_servers_type(config[:servers]) do
      :default -> config[:servers]  # explicit, non GCE
      :gce -> get_gce_server_list(config)
      :none -> raise "Server list specification not recognised: '#{inspect config[:servers]}'"
    end
  end

  @doc """
    Returns a copy of given config with the servers list well formed,
    and filtered using given parsed switches.
  """
  def inline_resolve_servers(config), do: inline_resolve_servers(config, [])
  def inline_resolve_servers(config, switches) do
    servers_list = guess_server_list(config)
      |> inline_filter_servers(switches[:servers])

    config |> Keyword.put(:servers, servers_list)
  end

  defp inline_filter_servers(servers, nil), do: servers
  defp inline_filter_servers(servers, switch) when is_binary(switch) do
    names = switch |> String.split(",") |> Enum.map(&Regex.compile!(&1))
    servers
    |> Enum.filter(fn({k,_})->
      k = to_string(k)
      names |> Enum.any?(&Regex.match?(&1, k))
    end)
  end

  defp get_gce_server_list(config) do
    L.info "Getting server list from GCE..."

    config
    |> Bottler.Helpers.GCE.instances
    |> Enum.map(fn(i)->
      name = i["name"] |> String.to_atom
      ip = i |> get_nested(["networkInterfaces", 0, "accessConfigs", 0, "natIP"])
      {name, [ip: ip]}
    end)
    |> pipe_log("<%= inspect data %>")
  end

  @doc """
    Adds the `name` and a new `id` element to the given servers `Keyword`.
    It returns a plain list, with a `Keyword` for each server.
  """
  def prepare_servers(servers) do
    servers |> Enum.map(fn({name, values}) ->
      values ++ [ name: name, id: "#{name}(#{values[:ip]})" ]
    end)
  end

  @doc """
    Writes an Elixir/Erlang term to the provided path
  """
  def write_term(path, term),
    do: :file.write_file('#{path}', :io_lib.fwrite('~p.\n', [term]))

  @doc """
    Reads a file as Erlang terms
  """
  def read_terms(path), do: :file.consult('#{path}')

  @doc """
    Apply given defaults to given Keyword. Returns merged Keyword.

    The inverse of `Keyword.merge`, best suited to apply some defaults in a
    chainable way.

    Ex:
      kw = gather_data
        |> transform_data
        |> H.defaults(k1: 1234, k2: 5768)
        |> here_i_need_defaults

    Instead of:
      kw1 = gather_data
        |> transform_data
      kw = [k1: 1234, k2: 5768]
        |> Keyword.merge(kw1)
        |> here_i_need_defaults

  """
  def defaults(args, defs) do
    defs |> Keyword.merge(args)
  end

  @doc """
    Run given command through `Mix.Shell`
  """
  def cmd(command) do
    case Mix.Shell.cmd(command, &(IO.write(&1)) ) do
      0 -> :ok
      _ -> {:error, "Release step failed. Please fix any errors and try again."}
    end
  end

  @doc """
    `ls` with full paths
    Returns the list of full paths. An empty list if anything fails
  """
  def full_ls(path) do
    expanded = Path.expand(path)
    case path |> File.ls do
      {:ok, list} -> Enum.map(list,&( "#{expanded}/#{&1}" ))
      _ -> []
    end
  end

  @doc """
    Delete, then recreate given folders
  """
  def empty_dirs(paths) when is_list(paths) do
    for p <- paths, do: empty_dir(p)
  end

  @doc """
    Delete, then recreate given folder
  """
  def empty_dir(path) do
    File.rm_rf! path
    File.mkdir_p! path
  end

  @doc """
    Log local and remote versions of erts
  """
  def check_erts_versions(config) do
    :ssh.start # just in case

    local_release = :erlang.system_info(:version) |> to_string

    {_, remote_releases} = config[:servers] |> K.values
      |> in_tasks( fn(args)->
        user = config[:remote_user] |> to_charlist
        ip = args[:ip] |> to_charlist
        {:ok, conn} = SSHEx.connect(ip: ip, user: user)
        cmd = "source ~/.bash_profile && erl -eval 'erlang:display(erlang:system_info(version)), halt().'  -noshell" |> to_charlist
        SSHEx.cmd!(conn, cmd)
        |> String.replace(~r/[\n\r\\"]/, "")
        |> Kernel.<>(" on #{ip}")
      end, to_s: false)

    level = if Enum.all?(remote_releases, &( local_release == &1 |> String.split(" ") |> List.first )), do: :info, else: :error

    L.log level, "Compiling against Erlang/OTP release #{local_release}. Remote releases are #{Enum.map_join(remote_releases, ", ", &(&1))}."

    if level == :error, do: raise "Aborted release"
  end

  @doc """
  Get the value at given coordinates inside the given nested structure.
  The structure must be composed of `Map` and `List`.

  If coordinates do not exist `nil` is returned.
  """
  def get_nested(data, []), do: data
  def get_nested(data, [key | rest]) when is_map(data) do
    data |> Map.get(key) |> get_nested(rest)
  end
  def get_nested(data, [key | rest]) when is_list(data) do
    data |> Enum.at(key) |> get_nested(rest)
  end
  def get_nested(_, _), do: nil
  def get_nested(data, keys, default), do: get_nested(data, keys) || default

  @doc """
  Put given value on given coordinates inside the given structure.
  Returns updated structure.

  If coordinates do not exist, needed structures are created.
  """
  def put_nested(nil, [key], value) when is_integer(key),
    do: put_nested([], [key], value)
  def put_nested(nil, [key | _] = keys, value) when is_integer(key),
    do: put_nested([], keys, value)
  def put_nested(nil, [key], value),
    do: put_nested(%{}, [key], value)
  def put_nested(nil, keys, value),
    do: put_nested(%{}, keys, value)
  def put_nested(data, [key], value) when is_map(data) do
    {_, v} = Map.get_and_update(data, key, &({&1, value}))
    v
  end
  def put_nested(data, [key | rest], value) when is_map(data) do
    {_, v} = Map.get_and_update(data, key, &({&1, put_nested(&1, rest, value)}))
    v
  end
  def put_nested(data, [key], value) when is_list(data) and is_integer(key) do
    case List.update_at(data, key, fn(_)-> value end) do
      ^data -> data |> grow_list(key + 1) |> put_nested([key], value)
      x -> x
    end
  end
  def put_nested(data, [key | rest] = keys, value) when is_list(data) and is_integer(key) do
    case List.update_at(data, key, &put_nested(&1, rest, value)) do
      ^data -> data |> grow_list(key + 1) |> put_nested(keys, value)
      x -> x
    end
  end

  @doc """
  Fills given list with nils until it is of the given length
  """
  def grow_list(list, length) do
    count = length - Enum.count(list)
    list ++ List.duplicate(nil, count)
  end

  @doc """
  Method applies a function against element if condition is true.
  """
  def run_if(elem, condition, fun) do
    cond do
      condition -> fun.(elem)
      true -> elem
    end
  end
end