need to average not sum bin audits

need to average not sum bin audits

require Logger

defmodule Service.Stats do
  @behaviour Reducer

  alias DB.Event
  alias Reducer.State

  @domains [:stats]
  @orderable false
  @types [:bin_audit, :actuals, :challenge]
  def domains, do: @domains
  def types, do: @types
  def orderable, do: @orderable

  @spec call(list(Event.t), State.t) :: State.t
  def call(events, %State{} = state) when is_list(events) do
    new_model = events
    |> validate()
    |> stats_reducer_recursive(state.model)
    %State{state | model: new_model, new_events: []}
  end

  def stats_reducer_recursive([], model), do: model
  def stats_reducer_recursive([event | remaining_events], model) do
    stats_reducer_recursive(remaining_events, play(event, model))
  end

  def play(event, model) do
    data = event.meta["data"]
    store_num = event.meta["store"]
    type = String.to_existing_atom(event.type)
    apply(__MODULE__, type, [model, data])
    |> accuracy()
    |> calc_sub_bin_audit(store_num)
    |> total()
  end

  def correct_type?(event) do
    Enum.member?([
      "actuals",
      "bin_audit",
      "challenge",
      ], event.type)
  end

  def validate(event_list) do
    Enum.filter(event_list, fn(event) -> correct_type?(event) end)
  end

  def merge_models(model, new_model)do
    Map.merge(model, new_model, fn _K, v1, v2 ->
      Map.merge(v1, v2, fn _K, v1, v2 ->
        Map.merge(v1, v2)
      end)
    end)
  end

  def actuals(model, meta) do
    new_model = Map.new(meta
    |> Enum.map(fn {period, list_of_metrics} ->
        {period, Enum.group_by(list_of_metrics, fn(metric) -> metric["Metrics"]end)}
      end)
    |> Enum.map(fn {period, data} -> {period, calculate_actuals(data)}end))
    merge_models(model, new_model)
  end

  def calculate_actuals(data)do
    Map.new(
    Enum.map(data, fn{metric, lines} ->
      case metric do
        "Actual Receipts" -> {"pre",
          %{"actual_units" => Enum.reduce(lines, 0, fn (line, acc) ->
            case Float.parse(line["Count"]) do
              {count, _ } -> count + acc
              :error -> acc
            end
          end)}}
        "Actual Units Sold" -> {"refill",
          %{"actual_units" => Enum.reduce(lines, 0, fn (line, acc) ->
            case Float.parse(line["Count"]) do
              {count, _ } ->
                case reduce_refill_actuals?(line["Store"]) do
                  true -> count * 0.52 + acc
                  _ -> count + acc
                end
              :error -> acc
            end
          end)}}
      end
    end))
  end

    #Nike asked to reduce the actuals for NSO concepts to be reduced by 48%, because they dont refill footwear.
    def reduce_refill_actuals?(store_number) do
      nike_stores = [
        "297", "81", "350", "100053", "60", "120022", "360", "301", "307", "382", "322", "93", "201", "240", "367", "269", "381", "19", "28", "352", "84", "323", "51", "368", "371", "86", "379", "364", "82", "325", "303", "305", "246", "237", "351", "359", "383"]
      Enum.member?(nike_stores, store_number)
    end

  def challenge(model, meta) do
    new_model = Map.new(
    meta
    |> Enum.map(fn {period, challenges} ->
        {period, Enum.map(challenges, fn(challenge) ->
          type = case challenge["challenge_type"] do
            "apparel" -> "pre"
            "equipment" -> "pre"
            "footwear" -> "pre"
            "product refill" -> "refill"
            _ -> :reject
          end
          Map.take(challenge, ["Goal", "UPH", "complete_units"])
          |> Map.put("challenge_type", type)
        end)
        |> Enum.group_by(fn(challenge) -> challenge["challenge_type"]end)
      }
    end)
    |> Enum.map(fn {period, data} -> {period, calc_challenge(data)}end))
    merge_models(model, new_model)
  end

  def calc_challenge(data)do
    Map.new(Enum.map(data, fn{type, lines} ->
      score = Enum.reduce(lines, %{"units" => 0, "percentage_sum" => 0, "count" => 0}, fn (line, acc)
        -> case {Float.parse(line["Goal"]), Float.parse(line["UPH"]), Float.parse(line["complete_units"])} do
          {:error, _, _} -> acc
          {_, :error, _} -> acc
          {_, _, :error} -> acc
          {{goal_uph, _}, {uph, _}, {units, _}} ->
            percent = uph/goal_uph
            %{"units" => acc["units"] + units,
              "percentage_sum" => acc["percentage_sum"] + percent,
              "count" => acc["count"] + 1
            }
        end
      end)
      {type,
      case {score["count"], type} do
        {0, _} -> %{}
        {_, :reject} -> %{}
        {_, "pre"} ->
           Map.put(score, "pre_percentage", score["percentage_sum"] / score["count"])
        |> Map.put("pre_score", percentage_score(score["percentage_sum"] / score["count"]))
        |> Map.drop(["percentage_sum", "count"])
        {_, "refill"} ->
           Map.put(score, "refill_percentage", score["percentage_sum"] / score["count"])
        |> Map.put("refill_score", percentage_score(score["percentage_sum"] / score["count"]))
        |> Map.drop(["percentage_sum", "count"])
      end}

    end))
    |> Map.drop([:reject])
  end

    def accuracy(model)do
      Map.new(model
      |> Enum.map(fn {period, data} ->
          {period,  Map.new(Enum.map(data, fn {type, model}-> {type, calc_accuracy(model)} end))} end))
    end

    def calc_accuracy(%{"actual_units" => actual_units, "units" => app_units} = model)do
      case actual_units do
        0 -> model
        0.0 -> model
        _ -> Map.put(model, "accuracy_percentage", app_units / actual_units)
            |> Map.put("accuracy_score", accuracy_score(app_units / actual_units))
      end
    end
    def calc_accuracy(model), do: model

    def bin_audit(model, meta) do
      new_model = Map.new(meta
      |> Enum.map(fn {period, data} -> {period, %{"bin_audit" => calc_bin_audit(data)}}end))
      merge_models(model, new_model)
    end

    def calc_bin_audit(lines)do
      model = Enum.reduce(lines, %{"bin_percentage" => 0, "bin_score" => 0, "count" => 0}, fn (line, acc) ->
       case Float.parse(line["BIN_PERCENTAGE"]) do
         {bin_percentage, _} ->
           acc
           |> Map.put("count", acc["count"] + 1)
           |> Map.put("bin_percentage", (bin_percentage + acc["bin_percentage"])/ (acc["count"] + 1))
           |> Map.put("bin_score", bin_audit_score((bin_percentage + acc["bin_percentage"])/ (acc["count"] + 1)))
         _ -> acc
       end
     end)
     Map.drop(model, ["count"])
    end

  # Calculates bin_audit_score for stores that do not do bin_audits, Clearance stores and Canada stores
    def calc_sub_bin_audit(model, store_num) do
      case needs_bin_audit_calc(store_num) do
        true ->
          Map.new(Enum.map(model, fn {period, period_model} ->
          percentage = calculate(period_model)
          {period, period_model
          |> Map.put("bin_audit", %{})
          |> put_in(["bin_audit", "bin_percentage"], percentage)
          |> put_in(["bin_audit", "bin_score"], bin_audit_score(percentage))} end))
        false -> model
      end
    end

    def needs_bin_audit_calc(store_num) do
      clearance_stores = ["4", "21", "23", "24", "36", "44", "50", "66", "109", "134", "148", "161", "174", "204", "218", "340", "347", "377"]
      canada_stores = ["100049", "100050", "100052", "100053", "100088", "120002", "120010", "120018", "120022", "120026", "120027", "120029", "120030", "120031", "120032", "120034", "120035", "120039"]
      Enum.member?(clearance_stores, store_num) || Enum.member?(canada_stores, store_num)
    end

    def calculate(period_model)do
      (week_percentage(period_model) / 70) * 100
    end

    def week_percentage(period_model) do
      pre_score = get_score(period_model, "pre", "pre_score")
      pre_accuracy = get_score(period_model, "pre", "accuracy_score")
      refill_score = get_score(period_model, "refill", "refill_score")
      refill_accuracy = get_score(period_model, "refill", "accuracy_score")
      pre_score + pre_accuracy + refill_score + refill_accuracy
    end

    def get_score(period_model, metric, score)do
      cond  do
        Map.has_key?(period_model, metric) ->
          cond do
            Map.has_key?(period_model[metric], score) -> period_model[metric][score]
            true -> 0
          end
        true -> 0
      end
    end

  def percentage_score(percent) do
    cond do
      percent >= 1.20 -> 15
      percent >= 1.00 -> 10
      percent >= 0.9 -> 5
      true -> 0
    end
  end

  def bin_audit_score(percent) do
    cond do
      percent >= 100 -> 15
      percent >= 90 -> 10
      percent >= 80 -> 5
      true -> 0
    end
  end

  def accuracy_score(percent) do
    cond do
      percent >= 1.10 -> 0
      percent >= 1.00 -> 20
      percent >= 0.9 -> 10
      percent >= 0.8 -> 8
      percent >= 0.7 -> 6
      percent >= 0.6 -> 4
      percent >= 0.5 -> 2
      true -> 0
    end
  end

    def total(model)do
      Map.new(model
      |> Enum.map(fn {period, data} ->
          {period,  calc_total(dat) } end))
    end

    def calc_total(weekly_model)do
      Map.put(weekly_model, "weekly_total", total_week(weekly_model))
    end

    def total_week(week_stats) do
      bin_score = get_score(week_stats, "bin_audit", "bin_score")
      pre_score = get_score(week_stats, "pre", "pre_score")
      pre_accuracy = get_score(week_stats, "pre", "accuracy_score")
      refill_score = get_score(week_stats, "refill", "refill_score")
      refill_accuracy = get_score(week_stats, "refill", "accuracy_score")
      bin_score + pre_score + pre_accuracy + refill_score + refill_accuracy
    end

end

require Logger

defmodule Service.Stats do
  @behaviour Reducer

  alias DB.Event
  alias Reducer.State

  @domains [:stats]
  @orderable false
  @types [:bin_audit, :actuals, :challenge]
  def domains, do: @domains
  def types, do: @types
  def orderable, do: @orderable

  @spec call(list(Event.t), State.t) :: State.t
  def call(events, %State{} = state) when is_list(events) do
    new_model = events
    |> validate()
    |> stats_reducer_recursive(state.model)
    %State{state | model: new_model, new_events: []}
  end

  def stats_reducer_recursive([], model), do: model
  def stats_reducer_recursive([event | remaining_events], model) do
    stats_reducer_recursive(remaining_events, play(event, model))
  end

  def play(event, model) do
    data = event.meta["data"]
    store_num = event.meta["store"]
    type = String.to_existing_atom(event.type)
    apply(__MODULE__, type, [model, data])
    |> accuracy()
    |> calc_sub_bin_audit(store_num)
    |> total()
  end

  def correct_type?(event) do
    Enum.member?([
      "actuals",
      "bin_audit",
      "challenge",
      ], event.type)
  end

  def validate(event_list) do
    Enum.filter(event_list, fn(event) -> correct_type?(event) end)
  end

  def merge_models(model, new_model)do
    Map.merge(model, new_model, fn _K, v1, v2 ->
      Map.merge(v1, v2, fn _K, v1, v2 ->
        Map.merge(v1, v2)
      end)
    end)
  end

  def actuals(model, meta) do
    new_model = Map.new(meta
    |> Enum.map(fn {period, list_of_metrics} ->
        {period, Enum.group_by(list_of_metrics, fn(metric) -> metric["Metrics"]end)}
      end)
    |> Enum.map(fn {period, data} -> {period, calculate_actuals(data)}end))
    merge_models(model, new_model)
  end

  def calculate_actuals(data)do
    Map.new(
    Enum.map(data, fn{metric, lines} ->
      case metric do
        "Actual Receipts" -> {"pre",
          %{"actual_units" => Enum.reduce(lines, 0, fn (line, acc) ->
            case Float.parse(line["Count"]) do
              {count, _ } -> count + acc
              :error -> acc
            end
          end)}}
        "Actual Units Sold" -> {"refill",
          %{"actual_units" => Enum.reduce(lines, 0, fn (line, acc) ->
            case Float.parse(line["Count"]) do
              {count, _ } ->
                case reduce_refill_actuals?(line["Store"]) do
                  true -> count * 0.52 + acc
                  _ -> count + acc
                end
              :error -> acc
            end
          end)}}
      end
    end))
  end

    #Nike asked to reduce the actuals for NSO concepts to be reduced by 48%, because they dont refill footwear.
    def reduce_refill_actuals?(store_number) do
      nike_stores = [
        "297", "81", "350", "100053", "60", "120022", "360", "301", "307", "382", "322", "93", "201", "240", "367", "269", "381", "19", "28", "352", "84", "323", "51", "368", "371", "86", "379", "364", "82", "325", "303", "305", "246", "237", "351", "359", "383"]
      Enum.member?(nike_stores, store_number)
    end

  def challenge(model, meta) do
    new_model = Map.new(
    meta
    |> Enum.map(fn {period, challenges} ->
        {period, Enum.map(challenges, fn(challenge) ->
          type = case challenge["challenge_type"] do
            "apparel" -> "pre"
            "equipment" -> "pre"
            "footwear" -> "pre"
            "product refill" -> "refill"
            _ -> :reject
          end
          Map.take(challenge, ["Goal", "UPH", "complete_units"])
          |> Map.put("challenge_type", type)
        end)
        |> Enum.group_by(fn(challenge) -> challenge["challenge_type"]end)
      }
    end)
    |> Enum.map(fn {period, data} -> {period, calc_challenge(data)}end))
    merge_models(model, new_model)
  end

  def calc_challenge(data)do
    Map.new(Enum.map(data, fn{type, lines} ->
      score = Enum.reduce(lines, %{"units" => 0, "percentage_sum" => 0, "count" => 0}, fn (line, acc)
        -> case {Float.parse(line["Goal"]), Float.parse(line["UPH"]), Float.parse(line["complete_units"])} do
          {:error, _, _} -> acc
          {_, :error, _} -> acc
          {_, _, :error} -> acc
          {{goal_uph, _}, {uph, _}, {units, _}} ->
            percent = uph/goal_uph
            %{"units" => acc["units"] + units,
              "percentage_sum" => acc["percentage_sum"] + percent,
              "count" => acc["count"] + 1
            }
        end
      end)
      {type,
      case {score["count"], type} do
        {0, _} -> %{}
        {_, :reject} -> %{}
        {_, "pre"} ->
           Map.put(score, "pre_percentage", score["percentage_sum"] / score["count"])
        |> Map.put("pre_score", percentage_score(score["percentage_sum"] / score["count"]))
        |> Map.drop(["percentage_sum", "count"])
        {_, "refill"} ->
           Map.put(score, "refill_percentage", score["percentage_sum"] / score["count"])
        |> Map.put("refill_score", percentage_score(score["percentage_sum"] / score["count"]))
        |> Map.drop(["percentage_sum", "count"])
      end}

    end))
    |> Map.drop([:reject])
  end

    def accuracy(model)do
      Map.new(model
      |> Enum.map(fn {period, data} ->
          {period,  Map.new(Enum.map(data, fn {type, model}-> {type, calc_accuracy(model)} end))} end))
    end

    def calc_accuracy(%{"actual_units" => actual_units, "units" => app_units} = model)do
      case actual_units do
        0 -> model
        0.0 -> model
        _ -> Map.put(model, "accuracy_percentage", app_units / actual_units)
            |> Map.put("accuracy_score", accuracy_score(app_units / actual_units))
      end
    end
    def calc_accuracy(model), do: model

    def bin_audit(model, meta) do
      new_model = Map.new(meta
      |> Enum.map(fn {period, data} -> {period, %{"bin_audit" => calc_bin_audit(data)}}end))
      merge_models(model, new_model)
    end

    def calc_bin_audit(lines)do
      model = Enum.reduce(lines, %{"bin_percentage" => 0, "bin_score" => 0, "count" => 0}, fn (line, acc) ->
       case Float.parse(line["BIN_PERCENTAGE"]) do
         {bin_percentage, _} ->
           acc
           |> Map.put("count", acc["count"] + 1)
           |> Map.put("bin_percentage", bin_percentage + acc["bin_percentage"])
           |> Map.put("bin_score", bin_audit_score((bin_percentage + acc["bin_percentage"])/ (acc["count"] + 1)))
         _ -> acc
       end
     end)
     Map.drop(model, ["count"])
    end

  # Calculates bin_audit_score for stores that do not do bin_audits, Clearance stores and Canada stores
    def calc_sub_bin_audit(model, store_num) do
      case needs_bin_audit_calc(store_num) do
        true ->
          Map.new(Enum.map(model, fn {period, period_model} ->
          percentage = calculate(period_model)
          {period, period_model
          |> Map.put("bin_audit", %{})
          |> put_in(["bin_audit", "bin_percentage"], percentage)
          |> put_in(["bin_audit", "bin_score"], bin_audit_score(percentage))} end))
        false -> model
      end
    end

    def needs_bin_audit_calc(store_num) do
      clearance_stores = ["4", "21", "23", "24", "36", "44", "50", "66", "109", "134", "148", "161", "174", "204", "218", "340", "347", "377"]
      canada_stores = ["100049", "100050", "100052", "100053", "100088", "120002", "120010", "120018", "120022", "120026", "120027", "120029", "120030", "120031", "120032", "120034", "120035", "120039"]
      Enum.member?(clearance_stores, store_num) || Enum.member?(canada_stores, store_num)
    end

    def calculate(period_model)do
      (week_percentage(period_model) / 70) * 100
    end

    def week_percentage(period_model) do
      pre_score = get_score(period_model, "pre", "pre_score")
      pre_accuracy = get_score(period_model, "pre", "accuracy_score")
      refill_score = get_score(period_model, "refill", "refill_score")
      refill_accuracy = get_score(period_model, "refill", "accuracy_score")
      pre_score + pre_accuracy + refill_score + refill_accuracy
    end

    def get_score(period_model, metric, score)do
      cond  do
        Map.has_key?(period_model, metric) ->
          cond do
            Map.has_key?(period_model[metric], score) -> period_model[metric][score]
            true -> 0
          end
        true -> 0
      end
    end

  def percentage_score(percent) do
    cond do
      percent >= 1.20 -> 15
      percent >= 1.00 -> 10
      percent >= 0.9 -> 5
      true -> 0
    end
  end

  def bin_audit_score(percent) do
    cond do
      percent >= 100 -> 15
      percent >= 90 -> 10
      percent >= 80 -> 5
      true -> 0
    end
  end

  def accuracy_score(percent) do
    cond do
      percent >= 1.10 -> 0
      percent >= 1.00 -> 20
      percent >= 0.9 -> 10
      percent >= 0.8 -> 8
      percent >= 0.7 -> 6
      percent >= 0.6 -> 4
      percent >= 0.5 -> 2
      true -> 0
    end
  end

    def total(model)do
      Map.new(model
      |> Enum.map(fn {period, data} ->
          {period,  calc_total(dat) } end))
    end

    def calc_total(weekly_model)do
      Map.put(weekly_model, "weekly_total", total_week(weekly_model))
    end

    def total_week(week_stats) do
      bin_score = get_score(week_stats, "bin_audit", "bin_score")
      pre_score = get_score(week_stats, "pre", "pre_score")
      pre_accuracy = get_score(week_stats, "pre", "accuracy_score")
      refill_score = get_score(week_stats, "refill", "refill_score")
      refill_accuracy = get_score(week_stats, "refill", "accuracy_score")
      bin_score + pre_score + pre_accuracy + refill_score + refill_accuracy
    end

end


remove duplicate database seeder

remove duplicate database seeder

defmodule What3things.DatabaseSeeder do
  alias What3things.{Service, Quote, Repo, Weight}
  alias Ecto.Multi
  import Ecto.Query

  @quotes_list ["I’d feel most comfortable if support staff came to see me at home.",
    "I’ll just wait and see what information and support I’m offered.",
    "I want to say what I feel without anyone knowing who I am, but know that someone will still be there listening and supporting me.",
    "I want to find somewhere local where I can meet other people with Parkinson’s and share my experiences.",
    "I don’t want to wait for someone to help me, I want to go out and help myself.",
    "There’s something specific I want to know about Parkinson’s and how it’s going to affect me.",
    "I want to have a private one-to-one quite soon to talk things through",
    "I’m happy chatting on forums and social media.",
    "I want to understand my condition so I can plan for the long term."]

  @services_list ["Peer Support Service",
    "Forum",
    "Groups",
    "Parkinson's Nurse",
    "Self-management programme",
    "Parkinson's Local Advisor",
    "First Steps",
    "Helpline",
    "Facebook",
    "Newly diagnosed landing page",
    "Early onset web page",
    "Publications"]

  @weights_list [[0,0, 0, 0.5, 0, 1, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0.25, 0, 0.5, 0, 0, 0.25, 0, 0],
    [0.5, 1, 0, 0, 0, 0, 0, 0.5, 0.25, 0, 0, 0],
    [0, 0, 1, 0, 0.25, 0, 0.5, 0, 0, 0, 0, 0],
    [0, 0, 0.25, 0, 0, 0, 0, 0, 0.25, 1, 0.5, 0.5],
    [0.5, 0, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0.25, 0, 1],
    [0.25, 0, 0, 0.25, 0, 0.5, 0, 1, 0, 0, 0, 0],
    [0, 0.75, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
    [0, 0, 0, 0.25, 0.5, 0.5, 0, 0, 0, 0.25, 0.25, 0.5]]

  defp make_quotes do
    @quotes_list |> Enum.map(&make_quote/1)
  end

  def make_services do
    @services_list |> Enum.map(&make_service/1)
  end

  def make_quote(q) do
    %{body: q}
  end

  def make_service(s) do
    %{title: s,
      body: "lorem ipsum",
      cta: "lorem",
      url: "www.#{s |> String.replace(" ", "")}.com"}
  end

  def make_weight(q_id, s_id, w) do
    %{quote_id: q_id,
      service_id: s_id,
      weight: w * 1.0}
  end

  def formated_weights do
    @weights_list |> Enum.map(fn(x) -> Enum.map(x, fn(y) -> y * 1.0 end) end)
  end

  def zip_service_weights(services) do
    for weights <- formated_weights() do
      services
      |> Enum.map(fn(x) -> %{service_id: x.id} end)
      |> Enum.zip(weights)
    end
  end

  def make_weights(quotes, services) do
    quotes
    |> Enum.map(fn(x) -> %{quote_id: x.id} end)
    |> Enum.zip(zip_service_weights(services))
    |> Enum.map(fn({q, s_weights}) -> Enum.map(s_weights, fn({s, w}) -> %{service_id: s.service_id, quote_id: q.quote_id, weight: w} end) end)
    |> List.flatten()
  end

  defp remove_service_query(title) do
    from s in Service, where: s.title == ^title
  end

  defp attach_empty_weights(multi) do
    q_id = multi.add_extra_quote.id
    services = Repo.all(Service)
    weights = Enum.map(services, fn(x) -> make_weight(q_id, x.id, 0) end)

    case Repo.insert_all(Weight, weights) do
      {n, nil} ->
        {:ok, {n, nil}}
      err ->
        {:error, {:add_weights_error, err}}
    end
  end

  defp add_weights(_multi) do
    quotes = Repo.all(Quote)
    services = Repo.all(Service)
    weights = make_weights(quotes, services)

    case Repo.insert_all(Weight, weights) do
      {n, nil}
        -> {:ok, {n, nil}}
      err
        -> {:error, {:add_weights_error, err}}
    end
  end

  def flush_db do
    Multi.new
    |> Multi.delete_all(:delete_weights, Weight)
    |> Multi.delete_all(:delete_services, Service)
    |> Multi.delete_all(:delete_quotes, Quote)
  end

  def populate_db do
    flush_db()
    |> Multi.insert_all(:add_quotes, Quote, make_quotes())
    |> Multi.insert_all(:add_services, Service, make_services())
    |> Multi.delete_all(:remove_service, remove_service_query("First Steps"))
    |> Multi.run(:add_weights, &add_weights/1)
    |> Multi.insert(:add_extra_quote, Quote.changeset(%Quote{}, make_quote("Claires quote")))
    |> Multi.run(:add_empty_weights, &attach_empty_weights/1)
    |> Repo.transaction
  end
end


[WIP] More debug log fixes

[WIP] More debug log fixes

require Logger
require Timex

defmodule Serverboards.Auth.User.Token do
	import Ecto.Query

	alias Serverboards.Auth.User.Model
	alias Serverboards.Auth.Model.User
  alias Serverboards.Repo

  @doc ~S"""
  Adds an entry token to the user.
  """
  def create(user, perms \\ nil) do
    require UUID
    token = UUID.uuid4
		Logger.info("Create token for user #{user.email}", user: user, perms: perms)
    {:ok, _tokenv} = Repo.insert(Model.Token.changeset(%Model.Token{}, %{
      :user_id => user.id,
      :token => token,
			:perms => perms
      }))
    token
  end

	def invalidate(token) do
		now_1s_ago = Timex.to_erlang_datetime(Timex.shift(Timex.DateTime.now, seconds: -1))
		res = (from t in Model.Token, where: t.token == ^token)
			|> Repo.update_all(
				set: [time_limit: now_1s_ago]
				)
		Logger.debug(inspect res) # Fails at CI, should not.
		:ok
	end

	@doc ~S"""
	Set the end time again for a given token.

	This allows to keep using the token for longer time.
	"""
	def refresh(token, email) do
		time_limit = Timex.to_erlang_datetime( Timex.shift( Timex.DateTime.now, days: 1 ) )
		{:ok, time_limit} = Ecto.DateTime.cast( time_limit )

		[user_id] = Repo.all( from u in User, where: u.email == ^email, select: u.id )

		(from t in Model.Token, where: t.token == ^token and t.user_id == ^user_id)
			|> Repo.update_all(
				set: [time_limit: time_limit]
				)
		:ok
	end

	def auth(token_uuid) do
		token = Repo.one(
			from u in User,
			join: t in Model.Token,
			  on: t.user_id == u.id,
			where: t.token == ^token_uuid and t.time_limit > fragment("NOW()"),
			select: %{ user: u, perms: t.perms }
		)
		case token do
			nil -> false
				Logger.error("Try to use invalid token #{token_uuid}")
				false
			_ ->
				{:ok, user} = Serverboards.Auth.User.user_info token.user
				#Logger.debug("Got token #{token_uuid} for user #{inspect token.user.email}", token: token, user: user)
				case token.perms do
					nil ->
						user
					[] ->
						user
					perms ->
						Logger.debug("Set custom perms: #{inspect perms}")
						%{ user | perms: perms }
				end
		end
	end
end

require Logger
require Timex

defmodule Serverboards.Auth.User.Token do
	import Ecto.Query

	alias Serverboards.Auth.User.Model
	alias Serverboards.Auth.Model.User
  alias Serverboards.Repo

  @doc ~S"""
  Adds an entry token to the user.
  """
  def create(user, perms \\ nil) do
    require UUID
    token = UUID.uuid4
		Logger.info("Create token for user #{user.email}", user: user, perms: perms)
    {:ok, _tokenv} = Repo.insert(Model.Token.changeset(%Model.Token{}, %{
      :user_id => user.id,
      :token => token,
			:perms => perms
      }))
    token
  end

	def invalidate(token) do
		now_1s_ago = Timex.to_erlang_datetime(Timex.shift(Timex.DateTime.now, seconds: -1))
		res = (from t in Model.Token, where: t.token == ^token)
			|> Repo.update_all(
				set: [time_limit: now_1s_ago]
				)
		Logger.debug(res) # Fails at CI, should not.
		:ok
	end

	@doc ~S"""
	Set the end time again for a given token.

	This allows to keep using the token for longer time.
	"""
	def refresh(token, email) do
		time_limit = Timex.to_erlang_datetime( Timex.shift( Timex.DateTime.now, days: 1 ) )
		{:ok, time_limit} = Ecto.DateTime.cast( time_limit )

		[user_id] = Repo.all( from u in User, where: u.email == ^email, select: u.id )

		(from t in Model.Token, where: t.token == ^token and t.user_id == ^user_id)
			|> Repo.update_all(
				set: [time_limit: time_limit]
				)
		:ok
	end

	def auth(token_uuid) do
		token = Repo.one(
			from u in User,
			join: t in Model.Token,
			  on: t.user_id == u.id,
			where: t.token == ^token_uuid and t.time_limit > fragment("NOW()"),
			select: %{ user: u, perms: t.perms }
		)
		case token do
			nil -> false
				Logger.error("Try to use invalid token #{token_uuid}")
				false
			_ ->
				{:ok, user} = Serverboards.Auth.User.user_info token.user
				#Logger.debug("Got token #{token_uuid} for user #{inspect token.user.email}", token: token, user: user)
				case token.perms do
					nil ->
						user
					[] ->
						user
					perms ->
						Logger.debug("Set custom perms: #{inspect perms}")
						%{ user | perms: perms }
				end
		end
	end
end


Update rel/config.exs?

Update rel/config.exs?

use Mix.Releases.Config,
    # This sets the default release built by `mix release`
    default_release: :default,
    # This sets the default environment used by `mix release`
    default_environment: :dev

# For a full list of config options for both releases
# and environments, visit https://hexdocs.pm/distillery/configuration.html

# You may define one or more environments in this file,
# an environment's settings will override those of a release
# when building in that environment, this combination of release
# and environment configuration is called a profile

environment :dev do
  set cookie: :"djSy[}ux`f;uj?&0nL@G,cUnf|Bu1%2Mfn{_a>Hw<X1Fk1.sOF4%AO]WmgLVO0JN"
end

environment :prod do
  set cookie: :"djSy[}ux`f;uj?&0nL@G,cUnf|Bu1%2Mfn{_a>Hw<X1Fk1.sOF4%AO]WmgLVO0JN"
end

# You may define one or more releases in this file.
# If you have not set a default release, or selected one
# when running `mix release`, the first release in the file
# will be used by default

release :upside_down_leds do
  set version: current_version(:upside_down_leds)
  plugin Shoehorn
  if System.get_env("NERVES_SYSTEM") do
    set dev_mode: false
    set include_src: false
    set include_erts: System.get_env("ERL_LIB_DIR")
    set include_system_libs: System.get_env("ERL_SYSTEM_LIB_DIR")
    set vm_args: "rel/vm.args"
  end
end

use Mix.Releases.Config,
    # This sets the default release built by `mix release`
    default_release: :default,
    # This sets the default environment used by `mix release`
    default_environment: :dev

# For a full list of config options for both releases
# and environments, visit https://hexdocs.pm/distillery/configuration.html

# You may define one or more environments in this file,
# an environment's settings will override those of a release
# when building in that environment, this combination of release
# and environment configuration is called a profile

environment :dev do
  set cookie: :"rvh}^o6a/an7K_$:SL4zQWJ`uu9K[d)N9Nd_*OuQUVN>Z_u.UJfP=2v^Xs:Ev9Z1"
end

environment :prod do
  set cookie: :"rvh}^o6a/an7K_$:SL4zQWJ`uu9K[d)N9Nd_*OuQUVN>Z_u.UJfP=2v^Xs:Ev9Z1"
end

# You may define one or more releases in this file.
# If you have not set a default release, or selected one
# when running `mix release`, the first release in the file
# will be used by default

release :upside_down_leds do
  set version: current_version(:upside_down_leds)
  plugin Shoehorn
  if System.get_env("NERVES_SYSTEM") do
    set dev_mode: false
    set include_src: false
    set include_erts: System.get_env("ERL_LIB_DIR")
    set include_system_libs: System.get_env("ERL_SYSTEM_LIB_DIR")
    set vm_args: "rel/vm.args"
  end
end


change feed url

change feed url


Remove support for auto app-versioning with git_describe

defmodule ReleaseManager.Utils do
  @moduledoc """
  This module provides helper functions for the `mix release` and
  `mix release.clean` tasks.
  """
  import Mix.Shell,    only: [cmd: 2]

  # Relx constants
  @relx_config_path      "rel/relx.config"
  @relx_output_path      "rel"

  @doc """
  Perform some actions within the context of a specific mix environment
  """
  defmacro with_env(env, body) do
    quote do
      old_env = Mix.env
      # Change env
      Mix.env(unquote(env))
      result = unquote(body)
      # Change back
      Mix.env(old_env)
      result
    end
  end

  @doc """
  Load the current project's configuration
  """
  def load_config() do
    with_env :prod do
      if File.regular?("config/config.exs") do
        Mix.Config.read! "config/config.exs"
      else
        []
      end
    end
  end

  @doc """
  Call make in the current working directory.
  """
  def make(:quiet),                  do: make("", "", :quiet)
  def make(:verbose),                do: make("", "", :verbose)
  def make(command, :quiet),         do: make(command, "", :quiet)
  def make(command, :verbose),       do: make(command, "", :verbose)
  def make(command, args),           do: make(command, args, :quiet)
  def make(command, args, :quiet),   do: do_cmd("make #{command} #{args}", &ignore/1)
  def make(command, args, :verbose), do: do_cmd("make #{command} #{args}", &IO.write/1)
  @doc """
  Call the _elixir mix binary with the given arguments
  """
  def mix(command, :quiet),        do: mix(command, :dev, :quiet)
  def mix(command, :verbose),      do: mix(command, :dev, :verbose)
  def mix(command, env),           do: mix(command, env, :quiet)
  def mix(command, env, :quiet),   do: do_cmd("MIX_ENV=#{env} mix #{command}", &ignore/1)
  def mix(command, env, :verbose), do: do_cmd("MIX_ENV=#{env} mix #{command}", &IO.write/1)
  @doc """
  Download a file from a url to the provided destination.
  """
  def wget(url, destination), do: do_cmd("wget -O #{destination} #{url}", &ignore/1)
  @doc """
  Change user permissions for a target file or directory
  """
  def chmod(target, flags), do: do_cmd("chmod #{flags} #{target}", &ignore/1)
  @doc """
  Clone a git repository to the provided destination, or current directory
  """
  def clone(repo_url, destination), do: do_cmd("git clone #{repo_url} #{destination}", &ignore/1)
  def clone(repo_url, destination, branch) do
    case branch do
      :default -> clone repo_url, destination
      ""       -> clone repo_url, destination
      _        -> do_cmd "git clone --branch #{branch} #{repo_url} #{destination}", &ignore/1
    end
  end
  @doc """
  Execute `relx`
  """
  def relx(name, version, verbosity, upgrade?, dev) do
    # Setup paths
    config     = @relx_config_path
    output_dir = @relx_output_path
    # Determine whether to pass --dev-mode or not
    dev_mode?  = case dev do
      true  -> "--dev-mode"
      false -> ""
    end
    # Convert friendly verbosity names to relx values
    v = case verbosity do
      :silent  -> 0
      :quiet   -> 1
      :normal  -> 2
      :verbose -> 3
      _        -> 2 # Normal if we get an odd value
    end
    # Let relx do the heavy lifting
    relx_path = Path.join([priv_path, "bin", "relx"])
    command = case upgrade? do
      false -> "#{relx_path} release tar -V #{v} --root #{File.cwd!} --config #{config} --relname #{name} --relvsn #{version} --output-dir #{output_dir} #{dev_mode?}"
      true  ->
        last_release = get_last_release(name)
        "#{relx_path} release relup tar -V #{v} --root #{File.cwd!} --config #{config} --relname #{name} --relvsn #{version} --output-dir #{output_dir} --upfrom \"#{last_release}\" #{dev_mode?}"
    end
    case do_cmd command do
      :ok         -> :ok
      {:error, _} ->
        {:error, "Failed to build release. Please fix any errors and try again."}
    end
  end

  @doc "Print an informational message without color"
  def debug(message), do: IO.puts "==> #{message}"
  @doc "Print an informational message in green"
  def info(message),  do: IO.puts "==> #{IO.ANSI.green}#{message}#{IO.ANSI.reset}"
  @doc "Print a warning message in yellow"
  def warn(message),  do: IO.puts "==> #{IO.ANSI.yellow}#{message}#{IO.ANSI.reset}"
  @doc "Print a notice in yellow"
  def notice(message), do: IO.puts "#{IO.ANSI.yellow}#{message}#{IO.ANSI.reset}"
  @doc "Print an error message in red"
  def error(message), do: IO.puts "==> #{IO.ANSI.red}#{message}#{IO.ANSI.reset}"

  @doc """
  Get a list of tuples representing the previous releases:

  ## Examples

      get_releases #=> [{"test", "0.0.1"}, {"test", "0.0.2"}]

  """
  def get_releases(project) do
    release_path = Path.join([File.cwd!, "rel", project, "releases"])
    case release_path |> File.exists? do
      false -> []
      true  ->
        release_path
        |> File.ls!
        |> Enum.reject(fn entry -> entry == "RELEASES" end)
        |> Enum.map(fn version -> {project, version} end)
    end
  end

  @doc """
  Get the most recent release prior to the current one
  """
  def get_last_release(project) do
    [{_,version} | _] = project |> get_releases |> Enum.sort(fn {_, v1}, {_, v2} -> v1 > v2 end)
    version
  end

  @doc """
  Get the local path of the current elixir executable
  """
  def get_elixir_path() do
    System.find_executable("elixir") |> get_real_path
  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]))
  end

  @doc "Get the priv path of the exrm dependency"
  def priv_path, do: Path.join([__DIR__, "..", "..", "priv"]) |> Path.expand
  @doc "Get the priv/rel path of the exrm dependency"
  def rel_source_path,       do: Path.join(priv_path, "rel")
  @doc "Get the path to a file located in priv/rel of the exrm dependency"
  def rel_source_path(file), do: Path.join(rel_source_path, file)
  @doc "Get the priv/rel/files path of the exrm dependency"
  def rel_file_source_path,       do: Path.join([priv_path, "rel", "files"])
  @doc "Get the path to a file located in priv/rel/files of the exrm dependency"
  def rel_file_source_path(file), do: Path.join(rel_file_source_path, file)
  @doc """
  Get the path to a file located in the rel directory of the current project.
  You can pass either a file name, or a list of directories to a file, like:

      iex> ReleaseManager.Utils.rel_dest_path "relx.config"
      "path/to/project/rel/relx.config"

      iex> ReleaseManager.Utils.rel_dest_path ["<project>", "lib", "<project>.appup"]
      "path/to/project/rel/<project>/lib/<project>.appup"

  """
  def rel_dest_path(files) when is_list(files), do: Path.join([rel_dest_path] ++ files)
  def rel_dest_path(file),                      do: Path.join(rel_dest_path, file)
  @doc "Get the rel path of the current project."
  def rel_dest_path,                            do: Path.join(File.cwd!, "rel")
  @doc """
  Get the path to a file located in the rel/files directory of the current project.
  You can pass either a file name, or a list of directories to a file, like:

      iex> ReleaseManager.Utils.rel_file_dest_path "sys.config"
      "path/to/project/rel/files/sys.config"

      iex> ReleaseManager.Utils.rel_dest_path ["some", "path", "file.txt"]
      "path/to/project/rel/files/some/path/file.txt"

  """
  def rel_file_dest_path(files) when is_list(files), do: Path.join([rel_file_dest_path] ++ files)
  def rel_file_dest_path(file),                      do: Path.join(rel_file_dest_path, file)
  @doc "Get the rel/files path of the current project."
  def rel_file_dest_path,                            do: Path.join([File.cwd!, "rel", "files"])

  # Ignore a message when used as the callback for Mix.Shell.cmd
  defp ignore(_), do: nil

  defp do_cmd(command), do: do_cmd(command, &IO.write/1)
  defp do_cmd(command, callback) do
    case cmd(command, callback) do
      0 -> :ok
      _ -> {:error, "Release step failed. Please fix any errors and try again."}
    end
  end

  defp get_real_path(path) do
    case path |> String.to_char_list |> :file.read_link_info do
      {:ok, {:file_info, _, :regular, _, _, _, _, _, _, _, _, _, _, _}} ->
        path
      {:ok, {:file_info, _, :symlink, _, _, _, _, _, _, _, _, _, _, _}} ->
        {:ok, sym} = path |> String.to_char_list |> :file.read_link
        case sym |> :filename.pathtype do
          :absolute ->
            sym |> IO.iodata_to_binary
          :relative ->
            symlink = sym |> IO.iodata_to_binary
            path |> Path.dirname |> Path.join(symlink) |> Path.expand
        end
    end |> String.replace("/bin/elixir", "")
  end

end

defmodule ReleaseManager.Utils do
  @moduledoc """
  This module provides helper functions for the `mix release` and
  `mix release.clean` tasks.
  """
  import Mix.Shell,    only: [cmd: 2]

  # Relx constants
  @relx_config_path      "rel/relx.config"
  @relx_output_path      "rel"

  @doc """
  Perform some actions within the context of a specific mix environment
  """
  defmacro with_env(env, body) do
    quote do
      old_env = Mix.env
      # Change env
      Mix.env(unquote(env))
      result = unquote(body)
      # Change back
      Mix.env(old_env)
      result
    end
  end

  @doc """
  Load the current project's configuration
  """
  def load_config() do
    with_env :prod do
      if File.regular?("config/config.exs") do
        Mix.Config.read! "config/config.exs"
      else
        []
      end
    end
  end

  @doc """
  Call make in the current working directory.
  """
  def make(:quiet),                  do: make("", "", :quiet)
  def make(:verbose),                do: make("", "", :verbose)
  def make(command, :quiet),         do: make(command, "", :quiet)
  def make(command, :verbose),       do: make(command, "", :verbose)
  def make(command, args),           do: make(command, args, :quiet)
  def make(command, args, :quiet),   do: do_cmd("make #{command} #{args}", &ignore/1)
  def make(command, args, :verbose), do: do_cmd("make #{command} #{args}", &IO.write/1)
  @doc """
  Call the _elixir mix binary with the given arguments
  """
  def mix(command, :quiet),        do: mix(command, :dev, :quiet)
  def mix(command, :verbose),      do: mix(command, :dev, :verbose)
  def mix(command, env),           do: mix(command, env, :quiet)
  def mix(command, env, :quiet),   do: do_cmd("MIX_ENV=#{env} mix #{command}", &ignore/1)
  def mix(command, env, :verbose), do: do_cmd("MIX_ENV=#{env} mix #{command}", &IO.write/1)
  @doc """
  Download a file from a url to the provided destination.
  """
  def wget(url, destination), do: do_cmd("wget -O #{destination} #{url}", &ignore/1)
  @doc """
  Change user permissions for a target file or directory
  """
  def chmod(target, flags), do: do_cmd("chmod #{flags} #{target}", &ignore/1)
  @doc """
  Clone a git repository to the provided destination, or current directory
  """
  def clone(repo_url, destination), do: do_cmd("git clone #{repo_url} #{destination}", &ignore/1)
  def clone(repo_url, destination, branch) do
    case branch do
      :default -> clone repo_url, destination
      ""       -> clone repo_url, destination
      _        -> do_cmd "git clone --branch #{branch} #{repo_url} #{destination}", &ignore/1
    end
  end
  @doc """
  Execute `relx`
  """
  def relx(name, version, verbosity, upgrade?, dev) do
    # Setup paths
    config     = @relx_config_path
    output_dir = @relx_output_path
    # Determine whether to pass --dev-mode or not
    dev_mode?  = case dev do
      true  -> "--dev-mode"
      false -> ""
    end
    # Get the release version
    ver = case version do
      "" -> git_describe
      _  -> version
    end
    # Convert friendly verbosity names to relx values
    v = case verbosity do
      :silent  -> 0
      :quiet   -> 1
      :normal  -> 2
      :verbose -> 3
      _        -> 2 # Normal if we get an odd value
    end
    # Let relx do the heavy lifting
    relx_path = Path.join([priv_path, "bin", "relx"])
    command = case upgrade? do
      false -> "#{relx_path} release tar -V #{v} --root #{File.cwd!} --config #{config} --relname #{name} --relvsn #{ver} --output-dir #{output_dir} #{dev_mode?}"
      true  ->
        last_release = get_last_release(name)
        "#{relx_path} release relup tar -V #{v} --root #{File.cwd!} --config #{config} --relname #{name} --relvsn #{ver} --output-dir #{output_dir} --upfrom \"#{last_release}\" #{dev_mode?}"
    end
    case do_cmd command do
      :ok         -> :ok
      {:error, _} ->
        {:error, "Failed to build release. Please fix any errors and try again."}
    end
  end
  @doc """
  Get the current project revision's short hash from git
  """
  def git_describe do
    System.cmd "git describe --always --tags | sed -e s/^v//"
  end

  @doc "Print an informational message without color"
  def debug(message), do: IO.puts "==> #{message}"
  @doc "Print an informational message in green"
  def info(message),  do: IO.puts "==> #{IO.ANSI.green}#{message}#{IO.ANSI.reset}"
  @doc "Print a warning message in yellow"
  def warn(message),  do: IO.puts "==> #{IO.ANSI.yellow}#{message}#{IO.ANSI.reset}"
  @doc "Print a notice in yellow"
  def notice(message), do: IO.puts "#{IO.ANSI.yellow}#{message}#{IO.ANSI.reset}"
  @doc "Print an error message in red"
  def error(message), do: IO.puts "==> #{IO.ANSI.red}#{message}#{IO.ANSI.reset}"

  @doc """
  Get a list of tuples representing the previous releases:

  ## Examples

      get_releases #=> [{"test", "0.0.1"}, {"test", "0.0.2"}]

  """
  def get_releases(project) do
    release_path = Path.join([File.cwd!, "rel", project, "releases"])
    case release_path |> File.exists? do
      false -> []
      true  ->
        release_path
        |> File.ls!
        |> Enum.reject(fn entry -> entry == "RELEASES" end)
        |> Enum.map(fn version -> {project, version} end)
    end
  end

  @doc """
  Get the most recent release prior to the current one
  """
  def get_last_release(project) do
    [{_,version} | _] = project |> get_releases |> Enum.sort(fn {_, v1}, {_, v2} -> v1 > v2 end)
    version
  end

  @doc """
  Get the local path of the current elixir executable
  """
  def get_elixir_path() do
    System.find_executable("elixir") |> get_real_path
  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]))
  end

  @doc "Get the priv path of the exrm dependency"
  def priv_path, do: Path.join([__DIR__, "..", "..", "priv"]) |> Path.expand
  @doc "Get the priv/rel path of the exrm dependency"
  def rel_source_path,       do: Path.join(priv_path, "rel")
  @doc "Get the path to a file located in priv/rel of the exrm dependency"
  def rel_source_path(file), do: Path.join(rel_source_path, file)
  @doc "Get the priv/rel/files path of the exrm dependency"
  def rel_file_source_path,       do: Path.join([priv_path, "rel", "files"])
  @doc "Get the path to a file located in priv/rel/files of the exrm dependency"
  def rel_file_source_path(file), do: Path.join(rel_file_source_path, file)
  @doc """
  Get the path to a file located in the rel directory of the current project.
  You can pass either a file name, or a list of directories to a file, like:

      iex> ReleaseManager.Utils.rel_dest_path "relx.config"
      "path/to/project/rel/relx.config"

      iex> ReleaseManager.Utils.rel_dest_path ["<project>", "lib", "<project>.appup"]
      "path/to/project/rel/<project>/lib/<project>.appup"

  """
  def rel_dest_path(files) when is_list(files), do: Path.join([rel_dest_path] ++ files)
  def rel_dest_path(file),                      do: Path.join(rel_dest_path, file)
  @doc "Get the rel path of the current project."
  def rel_dest_path,                            do: Path.join(File.cwd!, "rel")
  @doc """
  Get the path to a file located in the rel/files directory of the current project.
  You can pass either a file name, or a list of directories to a file, like:

      iex> ReleaseManager.Utils.rel_file_dest_path "sys.config"
      "path/to/project/rel/files/sys.config"

      iex> ReleaseManager.Utils.rel_dest_path ["some", "path", "file.txt"]
      "path/to/project/rel/files/some/path/file.txt"

  """
  def rel_file_dest_path(files) when is_list(files), do: Path.join([rel_file_dest_path] ++ files)
  def rel_file_dest_path(file),                      do: Path.join(rel_file_dest_path, file)
  @doc "Get the rel/files path of the current project."
  def rel_file_dest_path,                            do: Path.join([File.cwd!, "rel", "files"])

  # Ignore a message when used as the callback for Mix.Shell.cmd
  defp ignore(_), do: nil

  defp do_cmd(command), do: do_cmd(command, &IO.write/1)
  defp do_cmd(command, callback) do
    case cmd(command, callback) do
      0 -> :ok
      _ -> {:error, "Release step failed. Please fix any errors and try again."}
    end
  end

  defp get_real_path(path) do
    case path |> String.to_char_list |> :file.read_link_info do
      {:ok, {:file_info, _, :regular, _, _, _, _, _, _, _, _, _, _, _}} ->
        path
      {:ok, {:file_info, _, :symlink, _, _, _, _, _, _, _, _, _, _, _}} ->
        {:ok, sym} = path |> String.to_char_list |> :file.read_link
        case sym |> :filename.pathtype do
          :absolute ->
            sym |> IO.iodata_to_binary
          :relative ->
            symlink = sym |> IO.iodata_to_binary
            path |> Path.dirname |> Path.join(symlink) |> Path.expand
        end
    end |> String.replace("/bin/elixir", "")
  end

end


feat(repo): add scrivener to repo and default page size

defmodule Lyn.Repo do
  use Ecto.Repo, otp_app: :lyn
  use Scrivener, page_size: 20
end

defmodule Lyn.Repo do
  use Ecto.Repo, otp_app: :lyn
end


Minor refactoring

Minor refactoring

defmodule Battleship.Game do
  @moduledoc """
  Game server
  """
  use GenServer
  require Logger
  alias Battleship.{Game}
  alias Battleship.Game.Board

  defstruct [
    id: nil,
    attacker: nil,
    defender: nil,
    channels: [],
    rounds: [],
    over: false
  ]

  # API

  def start_link(id) do
    GenServer.start_link(__MODULE__, [id], name: ref(id))
  end

  def join(id, player_id, pid), do: try_call(id, {:join, player_id, pid})

  def get_data(id), do: try_call(id, :get_data)
  def get_data(id, player_id), do: try_call(id, {:get_data, player_id})

  def add_message(id, player_id, text), do: try_call(id, {:add_message, player_id, text})

  def player_shot(id, player_id, x: x, y: y), do: try_call(id, {:player_shot, player_id, x: x, y: y})

  # SERVER

  def init(id), do: {:ok, %__MODULE__{id: id}}

  def handle_call({:join, player_id, pid}, _from, game) do
    Logger.debug "Joinning Player to Game"

    cond do
      game.attacker != nil and game.defender != nil ->
        {:reply, {:error, "No more players allowed"}, game}
      Enum.member?([game.attacker, game.defender], player_id) ->
        {:reply, {:ok, self}, game}
      true ->
        Process.monitor(pid)
        Process.flag(:trap_exit, true)

        create_board(player_id)

        game = game
        |> add_player(player_id)
        |> add_channel(pid)

        {:reply, {:ok, self}, game}
    end
  end

  def handle_call(:get_data, _from, game), do: {:reply, %{game | channels: nil}, game}
  def handle_call({:get_data, player_id}, _from, game) do
    Logger.debug "Getting Game data for player #{player_id}"

    game_data = game
    |> Map.delete(:channels)
    |> Map.put(:my_board, Board.get_data(player_id))

    opponent_id = get_opponents_id(game, player_id)

    if opponent_id != nil do
      game_data = Map.put(game_data, :opponents_board, Board.get_opponents_data(opponent_id))
    end

    {:reply, game_data, game}
  end

  def handle_call({:player_shot, player_id, x: x, y: y}, _from, game) do
    opponent_id = get_opponents_id(game, player_id)

    Board.take_shot(opponent_id, x: x, y: y)

    game = %{game | rounds: [%{player_id: player_id, x: x, y: y}]}

    {:reply, {:ok, game}, game}
  end

  def get_opponents_id(%Game{attacker: player_id, defender: nil}, player_id), do: nil
  def get_opponents_id(%Game{attacker: player_id, defender: defender}, player_id), do: defender
  def get_opponents_id(%Game{attacker: attacker, defender: player_id}, player_id), do: attacker

  @doc """
  Handles exit messages from linked game channels processes, destroying boards and
  sopping the game process.
  - {:DOWN, _ref, :process, _pid, _reason}
  - {:EXIT, _pid, {:shutdown, :closed}}
  """
  def handle_info(message, game) do
    Logger.debug "Handling message #{inspect message} in Game server"

    stop(game)
  end

  @doc """
  Creates a new Board for a given Player
  """
  defp create_board(player_id), do: Board.create(player_id)

  @doc """
  Generates global reference
  """
  defp ref(id), do: {:global, {:game, id}}

  defp add_player(%__MODULE__{attacker: nil} = game, player_id), do: %{game | attacker: player_id}
  defp add_player(%__MODULE__{defender: nil} = game, player_id), do: %{game | defender: player_id}

  defp add_channel(game, pid), do: %{game | channels: [pid | game.channels]}

  defp destroy_board(nil), do: :ok
  defp destroy_board(player_id), do: Board.destroy(player_id)

  defp stop(game) do
    for player <- [game.attacker, game.defender], do: destroy_board(player)

    {:stop, :normal, game}
  end

  defp try_call(id, message) do
    case GenServer.whereis(ref(id)) do
      nil ->
        {:error, "Game does not exist"}
      game ->
        GenServer.call(game, message)
    end
  end
end

defmodule Battleship.Game do
  @moduledoc """
  Game server
  """
  use GenServer
  require Logger
  alias Battleship.{Game}
  alias Battleship.Game.Board

  defstruct [
    id: nil,
    attacker: nil,
    defender: nil,
    channels: [],
    rounds: [],
    over: false
  ]

  # API

  def start_link(id) do
    GenServer.start_link(__MODULE__, [id], name: ref(id))
  end

  def join(id, player_id, pid), do: try_call(id, {:join, player_id, pid})

  def get_data(id), do: try_call(id, :get_data)
  def get_data(id, player_id), do: try_call(id, {:get_data, player_id})

  def add_message(id, player_id, text), do: try_call(id, {:add_message, player_id, text})

  def player_shot(id, player_id, x: x, y: y), do: try_call(id, {:player_shot, player_id, x: x, y: y})

  # SERVER

  def init(id), do: {:ok, %__MODULE__{id: id}}

  def handle_call({:join, player_id, pid}, _from, game) do
    Logger.debug "Joinning Player to Game"

    cond do
      game.attacker != nil and game.defender != nil ->
        {:reply, {:error, "No more players allowed"}, game}
      Enum.member?([game.attacker, game.defender], player_id) ->
        {:reply, {:ok, self}, game}
      true ->
        Process.monitor(pid)
        Process.flag(:trap_exit, true)

        create_board(player_id)

        game = game
        |> add_player(player_id)
        |> add_channel(pid)

        {:reply, {:ok, self}, game}
    end
  end

  def handle_call(:get_data, _from, game), do: {:reply, %{game | channels: nil}, game}
  def handle_call({:get_data, player_id}, _from, game) do
    Logger.debug "Getting Game data for player #{player_id}"

    game_data = game
    |> Map.delete(:channels)
    |> Map.put(:my_board, Board.get_data(player_id))

    opponent_id = get_opponents_id(game, player_id)

    if opponent_id != nil do
      game_data = Map.put(game_data, :opponents_board, Board.get_opponents_data(opponent_id))
    end

    {:reply, game_data, game}
  end

  def handle_call({:player_shot, player_id, x: x, y: y}, _from, game) do
    opponent_id = get_opponents_id(game, player_id)

    Board.take_shot(opponent_id, x: x, y: y)

    game = %{game | rounds: [%{player_id: player_id, x: x, y: y}]}

    {:reply, {:ok, game}, game}
  end

  def get_opponents_id(%Game{attacker: player_id, defender: nil}, player_id), do: nil
  def get_opponents_id(%Game{attacker: player_id, defender: defender}, player_id), do: defender
  def get_opponents_id(%Game{attacker: attacker, defender: player_id}, player_id), do: attacker

  def handle_info({:DOWN, _ref, :process, _pid, _reason}, game) do
    Logger.debug "Handling DOWN message in Game server"

    for player <- [game.attacker, game.defender], do: destroy_board(player)

    {:stop, :normal, game}
  end

  def handle_info({:EXIT, _pid, {:shutdown, :closed}}, game) do
    Logger.debug "Handling EXIT message in Game server"

    for player <- [game.attacker, game.defender], do: destroy_board(player)

    {:stop, :normal, game}
  end

  @doc """
  Creates a new Board for a given Player
  """
  defp create_board(player_id), do: Board.create(player_id)

  @doc """
  Generates global reference
  """
  defp ref(id), do: {:global, {:game, id}}

  defp add_player(%__MODULE__{attacker: nil} = game, player_id), do: %{game | attacker: player_id}
  defp add_player(%__MODULE__{defender: nil} = game, player_id), do: %{game | defender: player_id}

  defp add_channel(game, pid), do: %{game | channels: [pid | game.channels]}

  defp destroy_board(nil), do: :ok
  defp destroy_board(player_id), do: Board.destroy(player_id)

  defp try_call(id, message) do
    case GenServer.whereis(ref(id)) do
      nil ->
        {:error, "Game does not exist"}
      game ->
        GenServer.call(game, message)
    end
  end
end


Introduce `List.keytake/3`

Introduce `List.keytake/3`

defmodule List do
  @moduledoc """
  Implements functions that only make sense for lists
  and cannot be part of the Enum protocol. In general,
  favor using the Enum API instead of List.

  Some functions in this module expect an index. Index
  access for list is linear. Negative indexes are also
  supported but they imply the list will be iterated twice,
  one to calculate the proper index and another to the
  operation.

  A decision was taken to delegate most functions to
  Erlang's standard library but follow Elixir's convention
  of receiving the target (in this case, a list) as the
  first argument.
  """

  @compile :inline_list_funcs

  @doc """
  Deletes the given item from the list. Returns a list without
  the item. If the item occurs more than once in the list, just
  the first occurrence is removed.

  ## Examples

      iex> List.delete([1, 2, 3], 1)
      [2, 3]

      iex> List.delete([1, 2, 2, 3], 2)
      [1, 2, 3]

  """
  @spec delete(list, any) :: list
  def delete(list, item) do
    :lists.delete(item, list)
  end

  @doc """
  Duplicates the given element `n` times in a list.

  ## Examples

      iex> List.duplicate("hello", 3)
      ["hello", "hello", "hello"]

      iex> List.duplicate([1, 2], 2)
      [[1, 2], [1, 2]]

  """
  @spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
  def duplicate(elem, n) do
    :lists.duplicate(n, elem)
  end

  @doc """
  Flattens the given `list` of nested lists.

  ## Examples

      iex> List.flatten([1, [[2], 3]])
      [1, 2, 3]

  """
  @spec flatten(deep_list) :: list when deep_list: [any | deep_list]
  def flatten(list) do
    :lists.flatten(list)
  end

  @doc """
  Flattens the given `list` of nested lists.
  The list `tail` will be added at the end of
  the flattened list.

  ## Examples

      iex> List.flatten([1, [[2], 3]], [4, 5])
      [1, 2, 3, 4, 5]

  """
  @spec flatten(deep_list, [elem]) :: [elem] when elem: var, deep_list: [elem | deep_list]
  def flatten(list, tail) do
    :lists.flatten(list, tail)
  end

  @doc """
  Folds (reduces) the given list from the left with
  a function. Requires an accumulator.

  ## Examples

      iex> List.foldl([5, 5], 10, fn (x, acc) -> x + acc end)
      20

      iex> List.foldl([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
      2

  """
  @spec foldl([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
  def foldl(list, acc, function) when is_list(list) and is_function(function) do
    :lists.foldl(function, acc, list)
  end

  @doc """
  Folds (reduces) the given list from the right with
  a function. Requires an accumulator.

  ## Examples

      iex> List.foldr([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
      -2

  """
  @spec foldr([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
  def foldr(list, acc, function) when is_list(list) and is_function(function) do
    :lists.foldr(function, acc, list)
  end

  @doc """
  Returns the first element in `list` or `nil` if `list` is empty.

  ## Examples

      iex> List.first([])
      nil

      iex> List.first([1])
      1

      iex> List.first([1, 2, 3])
      1

  """
  @spec first([elem]) :: nil | elem when elem: var
  def first([]),    do: nil
  def first([h|_]), do: h

  @doc """
  Returns the last element in `list` or `nil` if `list` is empty.

  ## Examples

      iex> List.last([])
      nil

      iex> List.last([1])
      1

      iex> List.last([1, 2, 3])
      3

  """
  @spec last([elem]) :: nil | elem when elem: var
  def last([]),    do: nil
  def last([h]),   do: h
  def last([_|t]), do: last(t)

  @doc """
  Receives a list of tuples and returns the first tuple
  where the item at `position` in the tuple matches the
  given `key`.

  ## Examples

      iex> List.keyfind([a: 1, b: 2], :a, 0)
      {:a, 1}

      iex> List.keyfind([a: 1, b: 2], 2, 1)
      {:b, 2}

      iex> List.keyfind([a: 1, b: 2], :c, 0)
      nil

  """
  @spec keyfind([tuple], any, non_neg_integer, any) :: any
  def keyfind(list, key, position, default \\ nil) do
    :lists.keyfind(key, position + 1, list) || default
  end

  @doc """
  Receives a list of tuples and returns `true` if there is
  a tuple where the item at `position` in the tuple matches
  the given `key`.

  ## Examples

      iex> List.keymember?([a: 1, b: 2], :a, 0)
      true

      iex> List.keymember?([a: 1, b: 2], 2, 1)
      true

      iex> List.keymember?([a: 1, b: 2], :c, 0)
      false

  """
  @spec keymember?([tuple], any, non_neg_integer) :: any
  def keymember?(list, key, position) do
    :lists.keymember(key, position + 1, list)
  end

  @doc """
  Receives a list of tuples and replaces the item
  identified by `key` at `position` if it exists.

  ## Examples

      iex> List.keyreplace([a: 1, b: 2], :a, 0, {:a, 3})
      [a: 3, b: 2]

  """
  @spec keyreplace([tuple], any, non_neg_integer, tuple) :: [tuple]
  def keyreplace(list, key, position, new_tuple) do
    :lists.keyreplace(key, position + 1, list, new_tuple)
  end

  @doc """
  Receives a list of tuples and sorts the items
  at `position` of the tuples. The sort is stable.

  ## Examples

      iex> List.keysort([a: 5, b: 1, c: 3], 1)
      [b: 1, c: 3, a: 5]

      iex> List.keysort([a: 5, c: 1, b: 3], 0)
      [a: 5, b: 3, c: 1]

  """
  @spec keysort([tuple], non_neg_integer) :: [tuple]
  def keysort(list, position) do
    :lists.keysort(position + 1, list)
  end

  @doc """
  Receives a list of tuples and replaces the item
  identified by `key` at `position`. If the item
  does not exist, it is added to the end of the list.

  ## Examples

      iex> List.keystore([a: 1, b: 2], :a, 0, {:a, 3})
      [a: 3, b: 2]

      iex> List.keystore([a: 1, b: 2], :c, 0, {:c, 3})
      [a: 1, b: 2, c: 3]

  """
  @spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple, ...]
  def keystore(list, key, position, new_tuple) do
    :lists.keystore(key, position + 1, list, new_tuple)
  end

  @doc """
  Receives a list of tuples and deletes the first tuple
  where the item at `position` matches the
  given `key`. Returns the new list.

  ## Examples

      iex> List.keydelete([a: 1, b: 2], :a, 0)
      [b: 2]

      iex> List.keydelete([a: 1, b: 2], 2, 1)
      [a: 1]

      iex> List.keydelete([a: 1, b: 2], :c, 0)
      [a: 1, b: 2]

  """
  @spec keydelete([tuple], any, non_neg_integer) :: [tuple]
  def keydelete(list, key, position) do
    :lists.keydelete(key, position + 1, list)
  end

  @doc """
  Receives a `list` of tuples and returns the first tuple
  where the element at `position` in the tuple matches the
  given `key`, as well as the `list` without found tuple.

  If such a tuple is not found, `nil` will be returned.

  ## Examples

      iex> List.keytake([a: 1, b: 2], :a, 0)
      {{:a, 1}, [b: 2]}

      iex> List.keytake([a: 1, b: 2], 2, 1)
      {{:b, 2}, [a: 1]}

      iex> List.keytake([a: 1, b: 2], :c, 0)
      nil

  """
  @spec keytake([tuple], any, non_neg_integer) :: {tuple, [tuple]} | nil
  def keytake(list, key, position) do
    case :lists.keytake(key, position + 1, list) do
      {:value, item, list} -> {item, list}
      false                -> nil
    end
  end

  @doc """
  Wraps the argument in a list.
  If the argument is already a list, returns the list.
  If the argument is `nil`, returns an empty list.

  ## Examples

      iex> List.wrap("hello")
      ["hello"]

      iex> List.wrap([1, 2, 3])
      [1, 2, 3]

      iex> List.wrap(nil)
      []

  """
  @spec wrap(list | any) :: list
  def wrap(list) when is_list(list) do
    list
  end

  def wrap(nil) do
    []
  end

  def wrap(other) do
    [other]
  end

  @doc """
  Zips corresponding elements from each list in `list_of_lists`.

  The zipping finishes as soon as any list terminates.

  ## Examples

      iex> List.zip([[1, 2], [3, 4], [5, 6]])
      [{1, 3, 5}, {2, 4, 6}]

      iex> List.zip([[1, 2], [3], [5, 6]])
      [{1, 3, 5}]

  """
  @spec zip([list]) :: [tuple]
  def zip([]), do: []
  def zip(list_of_lists) when is_list(list_of_lists) do
    do_zip(list_of_lists, [])
  end

  @doc """
  Returns a list with `value` inserted at the specified `index`.
  Note that `index` is capped at the list length. Negative indices
  indicate an offset from the end of the list.

  ## Examples

      iex> List.insert_at([1, 2, 3, 4], 2, 0)
      [1, 2, 0, 3, 4]

      iex> List.insert_at([1, 2, 3], 10, 0)
      [1, 2, 3, 0]

      iex> List.insert_at([1, 2, 3], -1, 0)
      [1, 2, 3, 0]

      iex> List.insert_at([1, 2, 3], -10, 0)
      [0, 1, 2, 3]

  """
  @spec insert_at(list, integer, any) :: list
  def insert_at(list, index, value) do
    if index < 0 do
      do_insert_at(list, length(list) + index + 1, value)
    else
      do_insert_at(list, index, value)
    end
  end

  @doc """
  Returns a list with a replaced value at the specified `index`.
  Negative indices indicate an offset from the end of the list.
  If `index` is out of bounds, the original `list` is returned.

  ## Examples

      iex> List.replace_at([1, 2, 3], 0, 0)
      [0, 2, 3]

      iex> List.replace_at([1, 2, 3], 10, 0)
      [1, 2, 3]

      iex> List.replace_at([1, 2, 3], -1, 0)
      [1, 2, 0]

      iex> List.replace_at([1, 2, 3], -10, 0)
      [1, 2, 3]

  """
  @spec replace_at(list, integer, any) :: list
  def replace_at(list, index, value) do
    if index < 0 do
      do_replace_at(list, length(list) + index, value)
    else
      do_replace_at(list, index, value)
    end
  end

  @doc """
  Returns a list with an updated value at the specified `index`.
  Negative indices indicate an offset from the end of the list.
  If `index` is out of bounds, the original `list` is returned.

  ## Examples

      iex> List.update_at([1, 2, 3], 0, &(&1 + 10))
      [11, 2, 3]

      iex> List.update_at([1, 2, 3], 10, &(&1 + 10))
      [1, 2, 3]

      iex> List.update_at([1, 2, 3], -1, &(&1 + 10))
      [1, 2, 13]

      iex> List.update_at([1, 2, 3], -10, &(&1 + 10))
      [1, 2, 3]

  """
  @spec update_at([elem], integer, (elem -> any)) :: list when elem: var
  def update_at(list, index, fun) do
    if index < 0 do
      do_update_at(list, length(list) + index, fun)
    else
      do_update_at(list, index, fun)
    end
  end

  @doc """
  Produces a new list by removing the value at the specified `index`.
  Negative indices indicate an offset from the end of the list.
  If `index` is out of bounds, the original `list` is returned.

  ## Examples

      iex> List.delete_at([1, 2, 3], 0)
      [2, 3]

      iex List.delete_at([1, 2, 3], 10)
      [1, 2, 3]

      iex> List.delete_at([1, 2, 3], -1)
      [1, 2]

  """
  @spec delete_at(list, integer) :: list
  def delete_at(list, index) do
    if index < 0 do
      do_delete_at(list, length(list) + index)
    else
      do_delete_at(list, index)
    end
  end

  @doc """
  Converts a char list to an atom.

  Currently Elixir does not support conversions from char lists
  which contains Unicode codepoints greater than 0xFF.

  Inlined by the compiler.

  ## Examples

      iex> List.to_atom('elixir')
      :elixir

  """
  @spec to_atom(char_list) :: atom
  def to_atom(char_list) do
    :erlang.list_to_atom(char_list)
  end

  @doc """
  Converts a char list to an existing atom. Raises an `ArguementError`
  if the atom does not exist.

  Currently Elixir does not support conversions from char lists
  which contains Unicode codepoints greater than 0xFF.

  Inlined by the compiler.

  ## Examples

      iex> :barney
      iex> List.to_existing_atom('barney')
      :barney

      iex> List.to_existing_atom('fred')
      ** (ArgumentError) argument error
         :erlang.list_to_existing_atom('fred')

  """
  @spec to_existing_atom(char_list) :: atom
  def to_existing_atom(char_list) do
    :erlang.list_to_existing_atom(char_list)
  end

  @doc """
  Returns the float whose text representation is `char_list`.

  Inlined by the compiler.

  ## Examples

      iex> List.to_float('2.2017764e+0')
      2.2017764

  """
  @spec to_float(char_list) :: float
  def to_float(char_list) do
    :erlang.list_to_float(char_list)
  end

  @doc """
  Returns an integer whose text representation is `char_list`.

  Inlined by the compiler.

  ## Examples

      iex> List.to_integer('123')
      123

  """
  @spec to_integer(char_list) :: integer
  def to_integer(char_list) do
    :erlang.list_to_integer(char_list)
  end

  @doc """
  Returns an integer whose text representation is `char_list` in base `base`.

  Inlined by the compiler.

  ## Examples

      iex> List.to_integer('3FF', 16)
      1023

  """
  @spec to_integer(char_list, 2..36) :: integer
  def to_integer(char_list, base) do
    :erlang.list_to_integer(char_list, base)
  end

  @doc """
  Converts a list to a tuple.

  Inlined by the compiler.

  ## Examples

      iex> List.to_tuple([:share, [:elixir, 163]])
      {:share, [:elixir, 163]}

  """
  @spec to_tuple(list) :: tuple
  def to_tuple(list) do
    :erlang.list_to_tuple(list)
  end

  @doc """
  Converts a list of integers representing codepoints, lists or
  strings into a string.

  Notice that this function expects a list of integers representing
  UTF-8 codepoints. If you have a list of bytes, you must instead use
  [the `:binary` module](http://erlang.org/doc/man/binary.html).

  ## Examples

      iex> List.to_string([0x00E6, 0x00DF])
      "æß"

      iex> List.to_string([0x0061, "bc"])
      "abc"

  """
  @spec to_string(:unicode.charlist) :: String.t
  def to_string(list) when is_list(list) do
    case :unicode.characters_to_binary(list) do
      result when is_binary(result) ->
        result

      {:error, encoded, rest} ->
        raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid

      {:incomplete, encoded, rest} ->
        raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
    end
  end

  ## Helpers

  # replace_at

  defp do_replace_at([], _index, _value) do
    []
  end

  defp do_replace_at(list, index, _value) when index < 0 do
    list
  end

  defp do_replace_at([_old|rest], 0, value) do
    [ value | rest ]
  end

  defp do_replace_at([h|t], index, value) do
    [ h | do_replace_at(t, index - 1, value) ]
  end

  # insert_at

  defp do_insert_at([], _index, value) do
    [ value ]
  end

  defp do_insert_at(list, index, value) when index <= 0 do
    [ value | list ]
  end

  defp do_insert_at([h|t], index, value) do
    [ h | do_insert_at(t, index - 1, value) ]
  end

  # update_at

  defp do_update_at([value|list], 0, fun) do
    [ fun.(value) | list ]
  end

  defp do_update_at(list, index, _fun) when index < 0 do
    list
  end

  defp do_update_at([h|t], index, fun) do
    [ h | do_update_at(t, index - 1, fun) ]
  end

  defp do_update_at([], _index, _fun) do
    []
  end

  # delete_at

  defp do_delete_at([], _index) do
    []
  end

  defp do_delete_at([_|t], 0) do
    t
  end

  defp do_delete_at(list, index) when index < 0 do
    list
  end

  defp do_delete_at([h|t], index) do
    [h | do_delete_at(t, index-1)]
  end

  # zip

  defp do_zip(list, acc) do
    converter = fn x, acc -> do_zip_each(to_list(x), acc) end
    {mlist, heads} = :lists.mapfoldl converter, [], list

    case heads do
      nil -> :lists.reverse acc
      _   -> do_zip mlist, [:erlang.list_to_tuple(:lists.reverse(heads))|acc]
    end
  end

  defp do_zip_each(_, nil) do
    {nil, nil}
  end

  defp do_zip_each([h|t], acc) do
    {t, [h|acc]}
  end

  defp do_zip_each([], _) do
    {nil, nil}
  end

  defp to_list(tuple) when is_tuple(tuple), do: Tuple.to_list(tuple)
  defp to_list(list)  when is_list(list),   do: list
end

defmodule List do
  @moduledoc """
  Implements functions that only make sense for lists
  and cannot be part of the Enum protocol. In general,
  favor using the Enum API instead of List.

  Some functions in this module expect an index. Index
  access for list is linear. Negative indexes are also
  supported but they imply the list will be iterated twice,
  one to calculate the proper index and another to the
  operation.

  A decision was taken to delegate most functions to
  Erlang's standard library but follow Elixir's convention
  of receiving the target (in this case, a list) as the
  first argument.
  """

  @compile :inline_list_funcs

  @doc """
  Deletes the given item from the list. Returns a list without
  the item. If the item occurs more than once in the list, just
  the first occurrence is removed.

  ## Examples

      iex> List.delete([1, 2, 3], 1)
      [2, 3]

      iex> List.delete([1, 2, 2, 3], 2)
      [1, 2, 3]

  """
  @spec delete(list, any) :: list
  def delete(list, item) do
    :lists.delete(item, list)
  end

  @doc """
  Duplicates the given element `n` times in a list.

  ## Examples

      iex> List.duplicate("hello", 3)
      ["hello", "hello", "hello"]

      iex> List.duplicate([1, 2], 2)
      [[1, 2], [1, 2]]

  """
  @spec duplicate(elem, non_neg_integer) :: [elem] when elem: var
  def duplicate(elem, n) do
    :lists.duplicate(n, elem)
  end

  @doc """
  Flattens the given `list` of nested lists.

  ## Examples

      iex> List.flatten([1, [[2], 3]])
      [1, 2, 3]

  """
  @spec flatten(deep_list) :: list when deep_list: [any | deep_list]
  def flatten(list) do
    :lists.flatten(list)
  end

  @doc """
  Flattens the given `list` of nested lists.
  The list `tail` will be added at the end of
  the flattened list.

  ## Examples

      iex> List.flatten([1, [[2], 3]], [4, 5])
      [1, 2, 3, 4, 5]

  """
  @spec flatten(deep_list, [elem]) :: [elem] when elem: var, deep_list: [elem | deep_list]
  def flatten(list, tail) do
    :lists.flatten(list, tail)
  end

  @doc """
  Folds (reduces) the given list from the left with
  a function. Requires an accumulator.

  ## Examples

      iex> List.foldl([5, 5], 10, fn (x, acc) -> x + acc end)
      20

      iex> List.foldl([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
      2

  """
  @spec foldl([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
  def foldl(list, acc, function) when is_list(list) and is_function(function) do
    :lists.foldl(function, acc, list)
  end

  @doc """
  Folds (reduces) the given list from the right with
  a function. Requires an accumulator.

  ## Examples

      iex> List.foldr([1, 2, 3, 4], 0, fn (x, acc) -> x - acc end)
      -2

  """
  @spec foldr([elem], acc, (elem, acc -> acc)) :: acc when elem: var, acc: var
  def foldr(list, acc, function) when is_list(list) and is_function(function) do
    :lists.foldr(function, acc, list)
  end

  @doc """
  Returns the first element in `list` or `nil` if `list` is empty.

  ## Examples

      iex> List.first([])
      nil

      iex> List.first([1])
      1

      iex> List.first([1, 2, 3])
      1

  """
  @spec first([elem]) :: nil | elem when elem: var
  def first([]),    do: nil
  def first([h|_]), do: h

  @doc """
  Returns the last element in `list` or `nil` if `list` is empty.

  ## Examples

      iex> List.last([])
      nil

      iex> List.last([1])
      1

      iex> List.last([1, 2, 3])
      3

  """
  @spec last([elem]) :: nil | elem when elem: var
  def last([]),    do: nil
  def last([h]),   do: h
  def last([_|t]), do: last(t)

  @doc """
  Receives a list of tuples and returns the first tuple
  where the item at `position` in the tuple matches the
  given `key`.

  ## Examples

      iex> List.keyfind([a: 1, b: 2], :a, 0)
      {:a, 1}

      iex> List.keyfind([a: 1, b: 2], 2, 1)
      {:b, 2}

      iex> List.keyfind([a: 1, b: 2], :c, 0)
      nil

  """
  @spec keyfind([tuple], any, non_neg_integer, any) :: any
  def keyfind(list, key, position, default \\ nil) do
    :lists.keyfind(key, position + 1, list) || default
  end

  @doc """
  Receives a list of tuples and returns `true` if there is
  a tuple where the item at `position` in the tuple matches
  the given `key`.

  ## Examples

      iex> List.keymember?([a: 1, b: 2], :a, 0)
      true

      iex> List.keymember?([a: 1, b: 2], 2, 1)
      true

      iex> List.keymember?([a: 1, b: 2], :c, 0)
      false

  """
  @spec keymember?([tuple], any, non_neg_integer) :: any
  def keymember?(list, key, position) do
    :lists.keymember(key, position + 1, list)
  end

  @doc """
  Receives a list of tuples and replaces the item
  identified by `key` at `position` if it exists.

  ## Examples

      iex> List.keyreplace([a: 1, b: 2], :a, 0, {:a, 3})
      [a: 3, b: 2]

  """
  @spec keyreplace([tuple], any, non_neg_integer, tuple) :: [tuple]
  def keyreplace(list, key, position, new_tuple) do
    :lists.keyreplace(key, position + 1, list, new_tuple)
  end

  @doc """
  Receives a list of tuples and sorts the items
  at `position` of the tuples. The sort is stable.

  ## Examples

      iex> List.keysort([a: 5, b: 1, c: 3], 1)
      [b: 1, c: 3, a: 5]

      iex> List.keysort([a: 5, c: 1, b: 3], 0)
      [a: 5, b: 3, c: 1]

  """
  @spec keysort([tuple], non_neg_integer) :: [tuple]
  def keysort(list, position) do
    :lists.keysort(position + 1, list)
  end

  @doc """
  Receives a list of tuples and replaces the item
  identified by `key` at `position`. If the item
  does not exist, it is added to the end of the list.

  ## Examples

      iex> List.keystore([a: 1, b: 2], :a, 0, {:a, 3})
      [a: 3, b: 2]

      iex> List.keystore([a: 1, b: 2], :c, 0, {:c, 3})
      [a: 1, b: 2, c: 3]

  """
  @spec keystore([tuple], any, non_neg_integer, tuple) :: [tuple, ...]
  def keystore(list, key, position, new_tuple) do
    :lists.keystore(key, position + 1, list, new_tuple)
  end

  @doc """
  Receives a list of tuples and deletes the first tuple
  where the item at `position` matches the
  given `key`. Returns the new list.

  ## Examples

      iex> List.keydelete([a: 1, b: 2], :a, 0)
      [b: 2]

      iex> List.keydelete([a: 1, b: 2], 2, 1)
      [a: 1]

      iex> List.keydelete([a: 1, b: 2], :c, 0)
      [a: 1, b: 2]

  """
  @spec keydelete([tuple], any, non_neg_integer) :: [tuple]
  def keydelete(list, key, position) do
    :lists.keydelete(key, position + 1, list)
  end

  @doc """
  Wraps the argument in a list.
  If the argument is already a list, returns the list.
  If the argument is `nil`, returns an empty list.

  ## Examples

      iex> List.wrap("hello")
      ["hello"]

      iex> List.wrap([1, 2, 3])
      [1, 2, 3]

      iex> List.wrap(nil)
      []

  """
  @spec wrap(list | any) :: list
  def wrap(list) when is_list(list) do
    list
  end

  def wrap(nil) do
    []
  end

  def wrap(other) do
    [other]
  end

  @doc """
  Zips corresponding elements from each list in `list_of_lists`.

  The zipping finishes as soon as any list terminates.

  ## Examples

      iex> List.zip([[1, 2], [3, 4], [5, 6]])
      [{1, 3, 5}, {2, 4, 6}]

      iex> List.zip([[1, 2], [3], [5, 6]])
      [{1, 3, 5}]

  """
  @spec zip([list]) :: [tuple]
  def zip([]), do: []
  def zip(list_of_lists) when is_list(list_of_lists) do
    do_zip(list_of_lists, [])
  end

  @doc """
  Returns a list with `value` inserted at the specified `index`.
  Note that `index` is capped at the list length. Negative indices
  indicate an offset from the end of the list.

  ## Examples

      iex> List.insert_at([1, 2, 3, 4], 2, 0)
      [1, 2, 0, 3, 4]

      iex> List.insert_at([1, 2, 3], 10, 0)
      [1, 2, 3, 0]

      iex> List.insert_at([1, 2, 3], -1, 0)
      [1, 2, 3, 0]

      iex> List.insert_at([1, 2, 3], -10, 0)
      [0, 1, 2, 3]

  """
  @spec insert_at(list, integer, any) :: list
  def insert_at(list, index, value) do
    if index < 0 do
      do_insert_at(list, length(list) + index + 1, value)
    else
      do_insert_at(list, index, value)
    end
  end

  @doc """
  Returns a list with a replaced value at the specified `index`.
  Negative indices indicate an offset from the end of the list.
  If `index` is out of bounds, the original `list` is returned.

  ## Examples

      iex> List.replace_at([1, 2, 3], 0, 0)
      [0, 2, 3]

      iex> List.replace_at([1, 2, 3], 10, 0)
      [1, 2, 3]

      iex> List.replace_at([1, 2, 3], -1, 0)
      [1, 2, 0]

      iex> List.replace_at([1, 2, 3], -10, 0)
      [1, 2, 3]

  """
  @spec replace_at(list, integer, any) :: list
  def replace_at(list, index, value) do
    if index < 0 do
      do_replace_at(list, length(list) + index, value)
    else
      do_replace_at(list, index, value)
    end
  end

  @doc """
  Returns a list with an updated value at the specified `index`.
  Negative indices indicate an offset from the end of the list.
  If `index` is out of bounds, the original `list` is returned.

  ## Examples

      iex> List.update_at([1, 2, 3], 0, &(&1 + 10))
      [11, 2, 3]

      iex> List.update_at([1, 2, 3], 10, &(&1 + 10))
      [1, 2, 3]

      iex> List.update_at([1, 2, 3], -1, &(&1 + 10))
      [1, 2, 13]

      iex> List.update_at([1, 2, 3], -10, &(&1 + 10))
      [1, 2, 3]

  """
  @spec update_at([elem], integer, (elem -> any)) :: list when elem: var
  def update_at(list, index, fun) do
    if index < 0 do
      do_update_at(list, length(list) + index, fun)
    else
      do_update_at(list, index, fun)
    end
  end

  @doc """
  Produces a new list by removing the value at the specified `index`.
  Negative indices indicate an offset from the end of the list.
  If `index` is out of bounds, the original `list` is returned.

  ## Examples

      iex> List.delete_at([1, 2, 3], 0)
      [2, 3]

      iex List.delete_at([1, 2, 3], 10)
      [1, 2, 3]

      iex> List.delete_at([1, 2, 3], -1)
      [1, 2]

  """
  @spec delete_at(list, integer) :: list
  def delete_at(list, index) do
    if index < 0 do
      do_delete_at(list, length(list) + index)
    else
      do_delete_at(list, index)
    end
  end

  @doc """
  Converts a char list to an atom.

  Currently Elixir does not support conversions from char lists
  which contains Unicode codepoints greater than 0xFF.

  Inlined by the compiler.

  ## Examples

      iex> List.to_atom('elixir')
      :elixir

  """
  @spec to_atom(char_list) :: atom
  def to_atom(char_list) do
    :erlang.list_to_atom(char_list)
  end

  @doc """
  Converts a char list to an existing atom. Raises an `ArguementError`
  if the atom does not exist.

  Currently Elixir does not support conversions from char lists
  which contains Unicode codepoints greater than 0xFF.

  Inlined by the compiler.

  ## Examples

      iex> :barney
      iex> List.to_existing_atom('barney')
      :barney

      iex> List.to_existing_atom('fred')
      ** (ArgumentError) argument error
         :erlang.list_to_existing_atom('fred')

  """
  @spec to_existing_atom(char_list) :: atom
  def to_existing_atom(char_list) do
    :erlang.list_to_existing_atom(char_list)
  end

  @doc """
  Returns the float whose text representation is `char_list`.

  Inlined by the compiler.

  ## Examples

      iex> List.to_float('2.2017764e+0')
      2.2017764

  """
  @spec to_float(char_list) :: float
  def to_float(char_list) do
    :erlang.list_to_float(char_list)
  end

  @doc """
  Returns an integer whose text representation is `char_list`.

  Inlined by the compiler.

  ## Examples

      iex> List.to_integer('123')
      123

  """
  @spec to_integer(char_list) :: integer
  def to_integer(char_list) do
    :erlang.list_to_integer(char_list)
  end

  @doc """
  Returns an integer whose text representation is `char_list` in base `base`.

  Inlined by the compiler.

  ## Examples

      iex> List.to_integer('3FF', 16)
      1023

  """
  @spec to_integer(char_list, 2..36) :: integer
  def to_integer(char_list, base) do
    :erlang.list_to_integer(char_list, base)
  end

  @doc """
  Converts a list to a tuple.

  Inlined by the compiler.

  ## Examples

      iex> List.to_tuple([:share, [:elixir, 163]])
      {:share, [:elixir, 163]}

  """
  @spec to_tuple(list) :: tuple
  def to_tuple(list) do
    :erlang.list_to_tuple(list)
  end

  @doc """
  Converts a list of integers representing codepoints, lists or
  strings into a string.

  Notice that this function expects a list of integers representing
  UTF-8 codepoints. If you have a list of bytes, you must instead use
  [the `:binary` module](http://erlang.org/doc/man/binary.html).

  ## Examples

      iex> List.to_string([0x00E6, 0x00DF])
      "æß"

      iex> List.to_string([0x0061, "bc"])
      "abc"

  """
  @spec to_string(:unicode.charlist) :: String.t
  def to_string(list) when is_list(list) do
    case :unicode.characters_to_binary(list) do
      result when is_binary(result) ->
        result

      {:error, encoded, rest} ->
        raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid

      {:incomplete, encoded, rest} ->
        raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :incomplete
    end
  end

  ## Helpers

  # replace_at

  defp do_replace_at([], _index, _value) do
    []
  end

  defp do_replace_at(list, index, _value) when index < 0 do
    list
  end

  defp do_replace_at([_old|rest], 0, value) do
    [ value | rest ]
  end

  defp do_replace_at([h|t], index, value) do
    [ h | do_replace_at(t, index - 1, value) ]
  end

  # insert_at

  defp do_insert_at([], _index, value) do
    [ value ]
  end

  defp do_insert_at(list, index, value) when index <= 0 do
    [ value | list ]
  end

  defp do_insert_at([h|t], index, value) do
    [ h | do_insert_at(t, index - 1, value) ]
  end

  # update_at

  defp do_update_at([value|list], 0, fun) do
    [ fun.(value) | list ]
  end

  defp do_update_at(list, index, _fun) when index < 0 do
    list
  end

  defp do_update_at([h|t], index, fun) do
    [ h | do_update_at(t, index - 1, fun) ]
  end

  defp do_update_at([], _index, _fun) do
    []
  end

  # delete_at

  defp do_delete_at([], _index) do
    []
  end

  defp do_delete_at([_|t], 0) do
    t
  end

  defp do_delete_at(list, index) when index < 0 do
    list
  end

  defp do_delete_at([h|t], index) do
    [h | do_delete_at(t, index-1)]
  end

  # zip

  defp do_zip(list, acc) do
    converter = fn x, acc -> do_zip_each(to_list(x), acc) end
    {mlist, heads} = :lists.mapfoldl converter, [], list

    case heads do
      nil -> :lists.reverse acc
      _   -> do_zip mlist, [:erlang.list_to_tuple(:lists.reverse(heads))|acc]
    end
  end

  defp do_zip_each(_, nil) do
    {nil, nil}
  end

  defp do_zip_each([h|t], acc) do
    {t, [h|acc]}
  end

  defp do_zip_each([], _) do
    {nil, nil}
  end

  defp to_list(tuple) when is_tuple(tuple), do: Tuple.to_list(tuple)
  defp to_list(list)  when is_list(list),   do: list
end