Less rocks, less firing

Less rocks, less firing

defmodule Game.Server do

  @moduledoc """
  Game process. One process per running Game.
  Each object in the game is represented by a Process.
  The game tells the processes to update themselves,
  and they report their new state to the Game.

  To use, start a Game, and periodically send it tick messages:

      {:ok, game} = Game.Server.start_link(4)
      Game.Server.tick(game)
      ...
      Game.Server.tick(game)
      ...

  To retrieve the game state, periodically send a message:

      game_state = Game.Server.state(game)

  To start a running Game at 60 fps:

      {:ok, game} = Game.Server.start_link(60)
      Game.Server.show(game)

  """

  use GenServer

  alias Asteroid.Server, as: Asteroid
  alias Bullet.Server, as: Bullet
  alias Ship.Server, as: Ship
  alias Game.Identifiers, as: Identifiers
  alias World.Clock, as: Clock
  alias Game.Collision, as: Collision

  @initial_asteroid_count   6
  @initial_ship_count      20

  def start_link(fps \\ 0) do
    GenServer.start_link(__MODULE__, {:ok, fps}, [])
  end

  def show(pid) do
    GenServer.cast(pid, :show)
  end

  @doc """
  Update the game state. Called by external process running at
  known FPS (frame-per-second)
  """
  def tick(pid) do
    GenServer.call(pid, :tick)
  end

  @doc """
  Retrieve the game state as a Map.
  """
  def state(pid) do
    GenServer.call(pid, :state)
  end

  def update_asteroid(pid, new_state) do
    GenServer.cast(pid, {:update_asteroid, new_state})
  end

  def update_ship(pid, new_state) do
    GenServer.cast(pid, {:update_ship, new_state})
  end

  def ship_fires_bullet(pid, ship_id) do
    GenServer.cast(pid, {:ship_fires_bullet, ship_id})
  end

  def update_bullet(pid, new_state) do
    GenServer.cast(pid, {:update_bullet, new_state})
  end

  def delete_bullet(pid, id) do
    GenServer.cast(pid, {:delete_bullet, id})
  end

  def explosion(pid, x, y) do
    GenServer.cast(pid, {:explosion, x, y})
  end

  ## Initial state

  @doc """
  Generate n new asteroids and store as a map of their
  identifier to a tuple of their {pid, state}.
  """
  def generate_asteroids(ids, n) do
    Enum.reduce(1..n, %{}, fn(_i, rocks) ->
      id = Identifiers.next(ids)
      {:ok, pid} = Asteroid.start_link(id)
      Map.put(rocks, id, pid)
    end)
  end

  @doc """
  Generate n new ships and store as a map of their
  identifier to a tuple of their {pid, state}.
  """
  def generate_ships(ids, n) do
    Enum.reduce(1..n, %{}, fn(_i, ships) ->
      id = Identifiers.next(ids)
      {:ok, pid} = Ship.start_link(id)
      Map.put(ships, id, pid)
    end)
  end

  ## Server Callbacks

  def init({:ok, fps}) do
    {:ok, ids} = Identifiers.start_link
    rocks = generate_asteroids(ids, @initial_asteroid_count)
    ships = generate_ships(ids, @initial_ship_count)
    game_state = %{:ids => ids,
              :pids =>  %{:asteroids => rocks,
                          :bullets => %{},
                          :ships => ships},
              :state => %{:asteroids => %{},
                          :bullets => %{},
                          :ships => %{}},
              :explosions => [],
              :clock_ms => Clock.now_ms}
    start_ticker(self(), fps)
    {:ok, game_state}
  end

  @doc """
  Echo the state of the game to the console.

      {:ok, game} = Game.Server.start_link
      Game.Server.show(game)

  """
  def handle_cast(:show, game) do
    game
    |> Kernel.inspect(pretty: true)
    |> IO.puts
    {:noreply, game}
  end

  def handle_cast({:update_asteroid, asteroid_state}, game) do
    id = elem(asteroid_state, 0)
    new_game = put_in(game.state.asteroids[id], asteroid_state)
    {:noreply, new_game}
  end

  def handle_cast({:update_ship, ship_state}, game) do
    id = elem(ship_state, 0)
    new_game = put_in(game.state.ships[id], ship_state)
    {:noreply, new_game}
  end

  @doc """
  Ship fires a bullet in the direction it is facing.

      {:ok, game} = Game.Server.start_link(4)
      Game.Server.tick(game)
      Game.Server.show(game)
      Game.Server.ship_fires_bullet(game, 1)

  """
  def handle_cast({:ship_fires_bullet, ship_id}, game) do
    ship_pid = game.pids.ships[ship_id]
    if ((ship_pid != nil) && Process.alive?(ship_pid)) do
      case Ship.nose(game.pids.ships[ship_id]) do
        {ship_pos, theta} -> fire_bullet_in_game(game, ship_pos, theta)
        _ -> {:noreply, game}
      end
    end
  end

  def handle_cast({:update_bullet, b}, game) do
    id = elem(b, 0)
    new_game = put_in(game.state.bullets[id], b)
    {:noreply, new_game}
  end

  def handle_cast({:delete_bullet, id}, game) do
    new_game = update_in(game.state.bullets, &Map.delete(&1, id))
    new_game2 = update_in(new_game.pids.bullets, &Map.delete(&1, id))
    {:noreply, new_game2}
  end

  def handle_cast({:explosion, x, y}, game) do
    new_game = update_in(game.explosions, &[{x,y} | &1])
    {:noreply, new_game}
  end

  @doc """
  Update the game state and return the number of ms elpased since last tick.

      {:ok, game} = Game.Server.start_link
      Game.Server.show(game)
      Game.Server.tick(game)
      Game.Server.show(game)
  """
  def handle_call(:tick, _from, game) do
    elapsed_ms = Clock.now_ms - game.clock_ms

    move_asteroids(game, elapsed_ms)
    move_ships(game, elapsed_ms)
    move_bullets(game, elapsed_ms)
    fire_bullets(game)
    check_for_collisions(game)

    {:reply,
      {:elapsed_ms, elapsed_ms},
      Map.put(game, :clock_ms, Clock.now_ms)}
  end

  def handle_call(:state, _from, game) do
    game_state = %{
      :dim => Elixoids.Space.dimensions,
      :a => game.state.asteroids |> map_of_tuples_to_list,
      :s => game.state.ships |> map_of_tuples_to_list |> map_rest,
      :x => game.explosions |> list_of_tuples_to_list,
      :b => game.state.bullets |> map_of_tuples_to_list
    }
    {:reply, game_state, %{game | :explosions => []}}
  end

  @doc """
  Convert a list of tuples into a list of lists
  """
  def list_of_tuples_to_list(m) do
    m
    |> Enum.map(fn(t) -> Tuple.to_list(t) end)
  end

  @doc """
  Convert a map of tuples into a list of lists
  """
  def map_of_tuples_to_list(m) do
    m
    |> Map.values
    |> Enum.map(fn(t) -> Tuple.to_list(t) end)
  end

  @doc """
  Drop the head of each list in the given list.
  """
  def map_rest(m) do
    m
    |> Enum.map(fn([_h|t]) -> t end )
  end

  defp start_ticker(pid, fps) do
    if ((fps > 0) && (fps <= 60)) do
      Game.Ticker.start_link(pid, fps)
    end
  end

  defp move_asteroids(game, elapsed_ms) do
    Enum.each(Map.values(game.pids.asteroids),
      fn(a) -> Asteroid.move(a, elapsed_ms, self()) end)
  end

  defp move_ships(game, elapsed_ms) do
    Enum.each(Map.values(game.pids.ships),
      fn(s) -> Ship.move(s, elapsed_ms, self()) end)
  end

  defp move_bullets(game, elapsed_ms) do
    Enum.each(Map.values(game.pids.bullets),
      fn(b) -> Bullet.move(b, elapsed_ms, self()) end)
  end

  # Collisions

  defp check_for_collisions(game) do
    all_bullets = Map.values(game.state.bullets)
    all_ships   = Map.values(game.state.ships)

    collisions = Collision.detect_bullets_hitting_ships(all_bullets, all_ships)

    collisions
    |> Collision.unique_bullets
    |> Enum.map(fn(b) ->
      {_, x, y} = game.state.bullets[b]
      Game.Server.explosion(self(), x, y)
    end)
  end

  # Development

  defp fire_bullets(game) do
    trigger = rem(World.Clock.now_ms, 200)
    Enum.each(Map.keys(game.pids.ships),
      fn(ship_id) ->
        if (ship_id == trigger) do
          Game.Server.ship_fires_bullet(self(), ship_id)
        end;
      end)
  end

  defp fire_bullet_in_game(game, ship_pos, theta) do
    id = Identifiers.next(game.ids)
    {:ok, b} = Bullet.start_link(id, ship_pos, theta)
    new_game = put_in(game.pids.bullets[id], b)
    {:noreply, new_game}
  end

end

defmodule Game.Server do

  @moduledoc """
  Game process. One process per running Game.
  Each object in the game is represented by a Process.
  The game tells the processes to update themselves,
  and they report their new state to the Game.

  To use, start a Game, and periodically send it tick messages:

      {:ok, game} = Game.Server.start_link(4)
      Game.Server.tick(game)
      ...
      Game.Server.tick(game)
      ...

  To retrieve the game state, periodically send a message:

      game_state = Game.Server.state(game)

  To start a running Game at 60 fps:

      {:ok, game} = Game.Server.start_link(60)
      Game.Server.show(game)

  """

  use GenServer

  alias Asteroid.Server, as: Asteroid
  alias Bullet.Server, as: Bullet
  alias Ship.Server, as: Ship
  alias Game.Identifiers, as: Identifiers
  alias World.Clock, as: Clock
  alias Game.Collision, as: Collision

  @initial_asteroid_count  16
  @initial_ship_count      20

  def start_link(fps \\ 0) do
    GenServer.start_link(__MODULE__, {:ok, fps}, [])
  end

  def show(pid) do
    GenServer.cast(pid, :show)
  end

  @doc """
  Update the game state. Called by external process running at
  known FPS (frame-per-second)
  """
  def tick(pid) do
    GenServer.call(pid, :tick)
  end

  @doc """
  Retrieve the game state as a Map.
  """
  def state(pid) do
    GenServer.call(pid, :state)
  end

  def update_asteroid(pid, new_state) do
    GenServer.cast(pid, {:update_asteroid, new_state})
  end

  def update_ship(pid, new_state) do
    GenServer.cast(pid, {:update_ship, new_state})
  end

  def ship_fires_bullet(pid, ship_id) do
    GenServer.cast(pid, {:ship_fires_bullet, ship_id})
  end

  def update_bullet(pid, new_state) do
    GenServer.cast(pid, {:update_bullet, new_state})
  end

  def delete_bullet(pid, id) do
    GenServer.cast(pid, {:delete_bullet, id})
  end

  def explosion(pid, x, y) do
    GenServer.cast(pid, {:explosion, x, y})
  end

  ## Initial state

  @doc """
  Generate n new asteroids and store as a map of their
  identifier to a tuple of their {pid, state}.
  """
  def generate_asteroids(ids, n) do
    Enum.reduce(1..n, %{}, fn(_i, rocks) ->
      id = Identifiers.next(ids)
      {:ok, pid} = Asteroid.start_link(id)
      Map.put(rocks, id, pid)
    end)
  end

  @doc """
  Generate n new ships and store as a map of their
  identifier to a tuple of their {pid, state}.
  """
  def generate_ships(ids, n) do
    Enum.reduce(1..n, %{}, fn(_i, ships) ->
      id = Identifiers.next(ids)
      {:ok, pid} = Ship.start_link(id)
      Map.put(ships, id, pid)
    end)
  end

  ## Server Callbacks

  def init({:ok, fps}) do
    {:ok, ids} = Identifiers.start_link
    rocks = generate_asteroids(ids, @initial_asteroid_count)
    ships = generate_ships(ids, @initial_ship_count)
    game_state = %{:ids => ids,
              :pids =>  %{:asteroids => rocks,
                          :bullets => %{},
                          :ships => ships},
              :state => %{:asteroids => %{},
                          :bullets => %{},
                          :ships => %{}},
              :explosions => [],
              :clock_ms => Clock.now_ms}
    start_ticker(self(), fps)
    {:ok, game_state}
  end

  @doc """
  Echo the state of the game to the console.

      {:ok, game} = Game.Server.start_link
      Game.Server.show(game)

  """
  def handle_cast(:show, game) do
    game
    |> Kernel.inspect(pretty: true)
    |> IO.puts
    {:noreply, game}
  end

  def handle_cast({:update_asteroid, asteroid_state}, game) do
    id = elem(asteroid_state, 0)
    new_game = put_in(game.state.asteroids[id], asteroid_state)
    {:noreply, new_game}
  end

  def handle_cast({:update_ship, ship_state}, game) do
    id = elem(ship_state, 0)
    new_game = put_in(game.state.ships[id], ship_state)
    {:noreply, new_game}
  end

  @doc """
  Ship fires a bullet in the direction it is facing.

      {:ok, game} = Game.Server.start_link(4)
      Game.Server.tick(game)
      Game.Server.show(game)
      Game.Server.ship_fires_bullet(game, 1)

  """
  def handle_cast({:ship_fires_bullet, ship_id}, game) do
    ship_pid = game.pids.ships[ship_id]
    if ((ship_pid != nil) && Process.alive?(ship_pid)) do
      case Ship.nose(game.pids.ships[ship_id]) do
        {ship_pos, theta} -> fire_bullet_in_game(game, ship_pos, theta)
        _ -> {:noreply, game}
      end
    end
  end

  def handle_cast({:update_bullet, b}, game) do
    id = elem(b, 0)
    new_game = put_in(game.state.bullets[id], b)
    {:noreply, new_game}
  end

  def handle_cast({:delete_bullet, id}, game) do
    new_game = update_in(game.state.bullets, &Map.delete(&1, id))
    new_game2 = update_in(new_game.pids.bullets, &Map.delete(&1, id))
    {:noreply, new_game2}
  end

  def handle_cast({:explosion, x, y}, game) do
    new_game = update_in(game.explosions, &[{x,y} | &1])
    {:noreply, new_game}
  end

  @doc """
  Update the game state and return the number of ms elpased since last tick.

      {:ok, game} = Game.Server.start_link
      Game.Server.show(game)
      Game.Server.tick(game)
      Game.Server.show(game)
  """
  def handle_call(:tick, _from, game) do
    elapsed_ms = Clock.now_ms - game.clock_ms

    move_asteroids(game, elapsed_ms)
    move_ships(game, elapsed_ms)
    move_bullets(game, elapsed_ms)
    fire_bullets(game)
    check_for_collisions(game)

    {:reply,
      {:elapsed_ms, elapsed_ms},
      Map.put(game, :clock_ms, Clock.now_ms)}
  end

  def handle_call(:state, _from, game) do
    game_state = %{
      :dim => Elixoids.Space.dimensions,
      :a => game.state.asteroids |> map_of_tuples_to_list,
      :s => game.state.ships |> map_of_tuples_to_list |> map_rest,
      :x => game.explosions |> list_of_tuples_to_list,
      :b => game.state.bullets |> map_of_tuples_to_list
    }
    {:reply, game_state, %{game | :explosions => []}}
  end

  @doc """
  Convert a list of tuples into a list of lists
  """
  def list_of_tuples_to_list(m) do
    m
    |> Enum.map(fn(t) -> Tuple.to_list(t) end)
  end

  @doc """
  Convert a map of tuples into a list of lists
  """
  def map_of_tuples_to_list(m) do
    m
    |> Map.values
    |> Enum.map(fn(t) -> Tuple.to_list(t) end)
  end

  @doc """
  Drop the head of each list in the given list.
  """
  def map_rest(m) do
    m
    |> Enum.map(fn([_h|t]) -> t end )
  end

  defp start_ticker(pid, fps) do
    if ((fps > 0) && (fps <= 60)) do
      Game.Ticker.start_link(pid, fps)
    end
  end

  defp move_asteroids(game, elapsed_ms) do
    Enum.each(Map.values(game.pids.asteroids),
      fn(a) -> Asteroid.move(a, elapsed_ms, self()) end)
  end

  defp move_ships(game, elapsed_ms) do
    Enum.each(Map.values(game.pids.ships),
      fn(s) -> Ship.move(s, elapsed_ms, self()) end)
  end

  defp move_bullets(game, elapsed_ms) do
    Enum.each(Map.values(game.pids.bullets),
      fn(b) -> Bullet.move(b, elapsed_ms, self()) end)
  end

  # Collisions

  defp check_for_collisions(game) do
    all_bullets = Map.values(game.state.bullets)
    all_ships   = Map.values(game.state.ships)

    collisions = Collision.detect_bullets_hitting_ships(all_bullets, all_ships)

    collisions
    |> Collision.unique_bullets
    |> Enum.map(fn(b) ->
      {_, x, y} = game.state.bullets[b]
      Game.Server.explosion(self(), x, y)
    end)
  end

  # Development

  defp fire_bullets(game) do
    trigger = rem(World.Clock.now_ms, 100)
    Enum.each(Map.keys(game.pids.ships),
      fn(ship_id) ->
        if (ship_id == trigger) do
          Game.Server.ship_fires_bullet(self(), ship_id)
        end;
      end)
  end

  defp fire_bullet_in_game(game, ship_pos, theta) do
    id = Identifiers.next(game.ids)
    {:ok, b} = Bullet.start_link(id, ship_pos, theta)
    new_game = put_in(game.pids.bullets[id], b)
    {:noreply, new_game}
  end

end


Adding string_parse to Survey

Adding string_parse to Survey

defmodule Survey.HTML.Survey do
  import MultiDef

  #--------------------------------------------------------------------------------
  # main pipeline

  def gen_survey(file, form) do
    parse(file)
    |> sectionify
    |> Enum.with_index
    |> proc_sections(form)
    |> IO.iodata_to_binary
  end

  #--------------------------------------------------------------------------------
  # sections

  # only print section headers if more than one section
  def proc_sections([{seq, 0}], form), do: do_section({seq, 0}, form, :single)
  def proc_sections(seqs, form), do: seqs |> Enum.map(fn x -> do_section(x, form, :many) end)

  def do_section({seq, i}, form, many) do
    content = Enum.map(seq, fn(x) -> gen_elements(x, form) end)
    display = if i == 0, do: "", else: "display: none"
    sectionheader = case many do
      :single -> ""
      :many   -> if i > 0 do
        "
Section #{i + 1}
"
      else
        ""
      end
    end

    ["
", sectionheader, content,
      "

"]
  end

  #--------------------------------------------------------------------------------
  # elements

  mdef gen_elements do
    {:header, txt}, _   -> ["
", txt, "
"]
    {:para, txt}, _     -> ["
", txt, "
"]
    {:function, txt}, _ -> elem(Code.eval_string(txt), 0)
    h, form             -> fs render_question(h, form)
  end

  mdef render_question do
    %{type: "text"} = h, form                 -> textinput(form, h)
    %{type: "radio"} = h, form                -> multi(form, h, "radio")
    %{type: "multi"} = h, form                -> multi(form, h, "checkbox")
    %{type: "textbox"} = h, form              -> textbox(form, h)
    %{type: "grid", choicerange: _} = h, form -> grid_select(form, h,
      h.rows, List.to_tuple(h.choicerange))
    %{type: "grid", choices: _} = h, form     -> grid_select(form, h,
      h.rows, h.choices)
  end

  #--------------------------------------------------------------------------------
  # individual question types

  def textinput(form, h) do
    desc = desc(h)
    class = case h do
      %{meta: %{class: x} } -> ["class='", x, "'"]
      _ -> ""
    end

    ["
", numstr(h), h.name,
      "
", desc, "<input name='#{form}[#{name(h)}]' type=text ", class, ">
"]
  end

  def textbox(form, h) do
    desc = desc(h)
    length = case h do
      %{meta: %{length: x} } -> ["

"]
      %{meta: %{minwords: x} } -> ["

"]
      _ -> ""
    end

    ["
", numstr(h), h.name, "
", desc, "<textarea name='#{form}[#{name(h)}]'></textarea>",
      length]
  end

  def multi(form, h, type) do
    desc = desc(h)

    opts = h.options
    |> Enum.with_index
    |> Enum.map(
      fn {x, i} ->
        case type do
          "checkbox" -> ["<input name='#{form}[#{name(h)}|#{[?a + i]}]'",
            "value='true' type=checkbox>", x, ""]
          "radio" -> ["<input name='#{form}[#{name(h)}]' value='#{[?a + i]}'",
            "type=radio>", x, ""]
        end
      end)

    ["
", numstr(h), h.name, "
", desc, opts]
  end

  #--------------------------------------------------------------------------------
  # grid selection

  def grid_select(form, h, rows, elem_list) do
    if is_tuple(elem_list) do
      {min, max, num} = elem_list
      elems = List.flatten [min, List.duplicate("", String.to_integer(num) - 2), max]
      labels = Enum.to_list(1..length(elems)) |> Enum.map(&Integer.to_string/1)
    else
      elems = elem_list
      labels = elem_list
    end

    headercells = elems
    |> Enum.with_index
    |> Enum.map(fn {x, i} -> ["
", x, "
"] end)

    header = ["
", numstr(h), h.name,
      "

",
      "

", headercells, "
"]
    body = rows |> Enum.with_index |> Enum.map(fn x -> body_row(h, form, x, labels) end)
    footer = "
"
    [header, body, footer]
  end

  defp body_row(h, form, {desc, i}, elem_list) do
    selname = "#{form}[##{name(h)}.#{[i + ?a]}]"
    sels = elem_list |> Enum.map(&(sel_elem(selname, &1)))
    ["

", desc, "
", sels, "
"]
  end

  defp sel_elem(name, val) do
    ["
  <input type='radio' name='", name, "' value='", val,
      "'> ", val, "
"]
  end

  #--------------------------------------------------------------------------------
  # helper functions

  def fs(x), do: ["<fieldset>", x, "</fieldset>"]

  mdef name do
    %{meta: %{name: x} } -> x
    h                    -> Integer.to_string(h.number)
  end

  def numstr(h), do: [Integer.to_string(h.number), ". "]

  mdef desc do
    %{meta: %{desc: x} } -> ["
", x, "
"]
    _                    -> []
  end

  #================================================================================
  # parsing
  #================================================================================

  def parse(file) do
    File.stream!(file)
    |> do_parse
  end

  def string_parse(str) do
    String.split(str, "\n")
    |> do_parse
  end

  def do_parse(input) do
    input
    |> Stream.filter(&remove_blank_lines/1)
    |> Stream.map(&String.rstrip/1)
    |> Stream.map(&classify_line_types/1)
    |> concat_blocks
  end

  def index_mapping(struct) do
    Enum.reduce(struct, %{}, fn x, acc ->
      if is_map(x) and x[:number] do
        Map.put acc, x.number, x
      else
        acc
      end
    end)
  end
  #--------------------------------------------------------------------------------

  def remove_blank_lines(x) do
    x = String.strip(x)
    x != "" && !String.starts_with?(x, "!")
  end

  mdef classify_line_types do
    "#"<>rest     -> {:header, rest}
    " "<>rest     -> {:sub, rest}
    "\t"<>rest    -> {:sub, rest}
    "choices"     -> :choices
    "choicerange" -> :choicerange
    "rows"        -> :rows
    "section"     -> :section
    "meta"        -> :meta
    rest          -> [type, q] = String.split(rest, ",", parts: 2); {:question, type, String.strip(q)}
  end

  def concat_blocks(x) do
    Enum.reduce(x, {:wait, 1, []}, &concat_blocks_proc/2)
    |> elem(2)
    |> Enum.reverse
  end

  mdef concat_blocks_proc do
    :section, {_, num, acc}                      -> {:wait, num, [:section | acc]}
    {:header, _} = h, {_, num, acc}              -> {:wait, num, [h | acc]}
    {:question, "para", name}, {_, num, acc}     -> {:wait, num, [{:para, name} | acc]}
    {:question, "function", name}, {_, num, acc} -> {:wait, num, [{:function, name} | acc]}
    :meta, {_, num, acc}                         -> {:meta, num, acc}
    :choicerange, {_, num, acc}                  -> {:choicerange, num, acc}
    :rows, {_, num, acc}                         -> {:rows, num, acc}
    :choices, {_, num, acc}                      -> {:choices, num, acc}

    {:question, "multi", name}, {_, num, acc}    -> {:options, num + 1,
      [ %{name: name, number: num, type: "multi"} | acc]}

    {:question, "radio", name}, {_, num, acc}    -> {:options, num + 1,
      [ %{name: name, number: num, type: "radio"} | acc]}

    {:question, type, name}, {_, num, acc}       -> {:wait, num + 1,
      [ %{name: name, number: num, type: type} | acc]}

    {:sub, str}, {:meta, num, [h | tl] }         -> { :meta, num,
      [ map_merge(h, :meta, proc_meta( str )) | tl ] }

    {:sub, str}, {elem, num, [h | tl] }          -> { elem, num,
      [ append_in(h, elem, str) | tl ] }
  end

  def proc_meta(str) do
    [k, v] = String.split(str, "=", parts: 2)
    |> Enum.map(&String.strip/1)
    Map.put(%{}, String.to_atom(k), v)
  end

  # takes a list like [1, 2, 3, :section, 4, 5, 6, :section, 8, 9] and
  # creates a set of nested lists: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
  mdef sectionify do
    seq                  -> sectionify seq, [[]]
    [:section | tl], acc -> sectionify tl, [[] | acc]
    [h | t], [h2 | t2]   -> sectionify t, [ List.insert_at(h2, 999, h) | t2 ]
    [], acc              -> Enum.reverse acc
  end

  #--------------------------------------------------------------------------------
  # helpers

  def append_in(h, elem, str) do
    Map.update(h, elem, [str], fn x -> List.insert_at(x, 999, str) end)
  end

  def map_merge(h, elem, kv) do
    Map.update(h, elem, kv, fn x -> Map.merge(x, kv) end)
  end

end

defmodule Survey.HTML.Survey do
  import MultiDef

  #--------------------------------------------------------------------------------
  # main pipeline

  def gen_survey(file, form) do
    parse(file)
    |> sectionify
    |> Enum.with_index
    |> proc_sections(form)
    |> IO.iodata_to_binary
  end

  #--------------------------------------------------------------------------------
  # sections

  # only print section headers if more than one section
  def proc_sections([{seq, 0}], form), do: do_section({seq, 0}, form, :single)
  def proc_sections(seqs, form), do: seqs |> Enum.map(fn x -> do_section(x, form, :many) end)

  def do_section({seq, i}, form, many) do
    content = Enum.map(seq, fn(x) -> gen_elements(x, form) end)
    display = if i == 0, do: "", else: "display: none"
    sectionheader = case many do
      :single -> ""
      :many   -> if i > 0 do
        "
Section #{i + 1}
"
      else
        ""
      end
    end

    ["
", sectionheader, content,
      "

"]
  end

  #--------------------------------------------------------------------------------
  # elements

  mdef gen_elements do
    {:header, txt}, _   -> ["
", txt, "
"]
    {:para, txt}, _     -> ["
", txt, "
"]
    {:function, txt}, _ -> elem(Code.eval_string(txt), 0)
    h, form             -> fs render_question(h, form)
  end

  mdef render_question do
    %{type: "text"} = h, form                 -> textinput(form, h)
    %{type: "radio"} = h, form                -> multi(form, h, "radio")
    %{type: "multi"} = h, form                -> multi(form, h, "checkbox")
    %{type: "textbox"} = h, form              -> textbox(form, h)
    %{type: "grid", choicerange: _} = h, form -> grid_select(form, h,
      h.rows, List.to_tuple(h.choicerange))
    %{type: "grid", choices: _} = h, form     -> grid_select(form, h,
      h.rows, h.choices)
  end

  #--------------------------------------------------------------------------------
  # individual question types

  def textinput(form, h) do
    desc = desc(h)
    class = case h do
      %{meta: %{class: x} } -> ["class='", x, "'"]
      _ -> ""
    end

    ["
", numstr(h), h.name,
      "
", desc, "<input name='#{form}[#{name(h)}]' type=text ", class, ">
"]
  end

  def textbox(form, h) do
    desc = desc(h)
    length = case h do
      %{meta: %{length: x} } -> ["

"]
      %{meta: %{minwords: x} } -> ["

"]
      _ -> ""
    end

    ["
", numstr(h), h.name, "
", desc, "<textarea name='#{form}[#{name(h)}]'></textarea>",
      length]
  end

  def multi(form, h, type) do
    desc = desc(h)

    opts = h.options
    |> Enum.with_index
    |> Enum.map(
      fn {x, i} ->
        case type do
          "checkbox" -> ["<input name='#{form}[#{name(h)}|#{[?a + i]}]'",
            "value='true' type=checkbox>", x, ""]
          "radio" -> ["<input name='#{form}[#{name(h)}]' value='#{[?a + i]}'",
            "type=radio>", x, ""]
        end
      end)

    ["
", numstr(h), h.name, "
", desc, opts]
  end

  #--------------------------------------------------------------------------------
  # grid selection

  def grid_select(form, h, rows, elem_list) do
    if is_tuple(elem_list) do
      {min, max, num} = elem_list
      elems = List.flatten [min, List.duplicate("", String.to_integer(num) - 2), max]
      labels = Enum.to_list(1..length(elems)) |> Enum.map(&Integer.to_string/1)
    else
      elems = elem_list
      labels = elem_list
    end

    headercells = elems
    |> Enum.with_index
    |> Enum.map(fn {x, i} -> ["
", x, "
"] end)

    header = ["
", numstr(h), h.name,
      "

",
      "

", headercells, "
"]
    body = rows |> Enum.with_index |> Enum.map(fn x -> body_row(h, form, x, labels) end)
    footer = "
"
    [header, body, footer]
  end

  defp body_row(h, form, {desc, i}, elem_list) do
    selname = "#{form}[##{name(h)}.#{[i + ?a]}]"
    sels = elem_list |> Enum.map(&(sel_elem(selname, &1)))
    ["

", desc, "
", sels, "
"]
  end

  defp sel_elem(name, val) do
    ["
  <input type='radio' name='", name, "' value='", val,
      "'> ", val, "
"]
  end

  #--------------------------------------------------------------------------------
  # helper functions

  def fs(x), do: ["<fieldset>", x, "</fieldset>"]

  mdef name do
    %{meta: %{name: x} } -> x
    h                    -> Integer.to_string(h.number)
  end

  def numstr(h), do: [Integer.to_string(h.number), ". "]

  mdef desc do
    %{meta: %{desc: x} } -> ["
", x, "
"]
    _                    -> []
  end

  #================================================================================
  # parsing
  #================================================================================

  def parse(file) do
    File.stream!(file)
    |> Stream.filter(&remove_blank_lines/1)
    |> Stream.map(&String.rstrip/1)
    |> Stream.map(&classify_line_types/1)
    |> concat_blocks
  end

  def index_mapping(struct) do
    Enum.reduce(struct, %{}, fn x, acc ->
      if is_map(x) and x[:number] do
        Map.put acc, x.number, x
      else
        acc
      end
    end)
  end
  #--------------------------------------------------------------------------------

  def remove_blank_lines(x) do
    x = String.strip(x)
    x != "" && !String.starts_with?(x, "!")
  end

  mdef classify_line_types do
    "#"<>rest     -> {:header, rest}
    " "<>rest     -> {:sub, rest}
    "\t"<>rest    -> {:sub, rest}
    "choices"     -> :choices
    "choicerange" -> :choicerange
    "rows"        -> :rows
    "section"     -> :section
    "meta"        -> :meta
    rest          -> [type, q] = String.split(rest, ",", parts: 2); {:question, type, String.strip(q)}
  end

  def concat_blocks(x) do
    Enum.reduce(x, {:wait, 1, []}, &concat_blocks_proc/2)
    |> elem(2)
    |> Enum.reverse
  end

  mdef concat_blocks_proc do
    :section, {_, num, acc}                      -> {:wait, num, [:section | acc]}
    {:header, _} = h, {_, num, acc}              -> {:wait, num, [h | acc]}
    {:question, "para", name}, {_, num, acc}     -> {:wait, num, [{:para, name} | acc]}
    {:question, "function", name}, {_, num, acc} -> {:wait, num, [{:function, name} | acc]}
    :meta, {_, num, acc}                         -> {:meta, num, acc}
    :choicerange, {_, num, acc}                  -> {:choicerange, num, acc}
    :rows, {_, num, acc}                         -> {:rows, num, acc}
    :choices, {_, num, acc}                      -> {:choices, num, acc}

    {:question, "multi", name}, {_, num, acc}    -> {:options, num + 1,
      [ %{name: name, number: num, type: "multi"} | acc]}

    {:question, "radio", name}, {_, num, acc}    -> {:options, num + 1,
      [ %{name: name, number: num, type: "radio"} | acc]}

    {:question, type, name}, {_, num, acc}       -> {:wait, num + 1,
      [ %{name: name, number: num, type: type} | acc]}

    {:sub, str}, {:meta, num, [h | tl] }         -> { :meta, num,
      [ map_merge(h, :meta, proc_meta( str )) | tl ] }

    {:sub, str}, {elem, num, [h | tl] }          -> { elem, num,
      [ append_in(h, elem, str) | tl ] }
  end

  def proc_meta(str) do
    [k, v] = String.split(str, "=", parts: 2)
    |> Enum.map(&String.strip/1)
    Map.put(%{}, String.to_atom(k), v)
  end

  # takes a list like [1, 2, 3, :section, 4, 5, 6, :section, 8, 9] and
  # creates a set of nested lists: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
  mdef sectionify do
    seq                  -> sectionify seq, [[]]
    [:section | tl], acc -> sectionify tl, [[] | acc]
    [h | t], [h2 | t2]   -> sectionify t, [ List.insert_at(h2, 999, h) | t2 ]
    [], acc              -> Enum.reverse acc
  end

  #--------------------------------------------------------------------------------
  # helpers

  def append_in(h, elem, str) do
    Map.update(h, elem, [str], fn x -> List.insert_at(x, 999, str) end)
  end

  def map_merge(h, elem, kv) do
    Map.update(h, elem, kv, fn x -> Map.merge(x, kv) end)
  end

end


Documented the closest points program and added a 4D example based on a 5-cell.

#!/usr/bin/env elixir

# PROBLEM:
#   Write a routine that takes a set of points and returns the points
#   closest to the origin.  The routine takes a set of points and the
#   number of points to return.

# EXAMPLE:
#   inputs: points = [(2,1), (0,1), (0,2), (5,5), (0,1), (2,1)], desired number of points = 3
#   outputs: [(0,1), (0,2), (0,1)]

# NOTES:
#   The output can be in any order.

# A single file solution written in Elixir follows.
# To see a list of the command line arguments, run the following.
# This that solution also support working with N-dimensional points,
# and the origin can be specified from the command line.
#   chmod +x closest_points.exs
#   ./closest_points.exs --help
# Examples:
#   ./closest_points.exs
#   ./closest_points.exs -p [2,1],[0,1],[0,2],[5,5],[0,1],[2,1] -r 3
#   ./closest_points.exs -o 3,3 -p [2,1],[0,1],[0,2],[5,5],[0,1],[2,1]
#   ./closest_points.exs -o=-1,1 -p [-1,0],[0,-1],[0,1],[1,0] -r 2 -v
#   ./closest_points.exs -p [0,0,0,0],[-1,3,3,3],[3,-1,3,3],[3,3,-1,3],[3,3,3,-1] -o 0,1,2,3 -r 2

defmodule ClosestPoints do
  def process(pOrigin, pPoints, pResultCount) do
    pPoints
    |> Enum.map(&subtractPoint(&1, pOrigin))
    |> Enum.map(&distanceFromOrigin/1)
    |> Enum.zip(pPoints)
    |> Enum.sort(fn {distanceA,_pa}, {distanceB,_pb} -> distanceA <= distanceB end)
    |> Enum.map(fn {_distance, point} -> point end)
    |> Enum.take(pResultCount)
  end

  def subtractPoint(pA, pB) do
    pA
    |> Enum.zip(pB)
    |> Enum.map(fn {a, b} -> a - b end)
  end

  def distanceFromOrigin(pPoint) do
    pPoint
    |> Enum.reduce(0, fn value, total -> total + value*value end)
  end
end

defmodule Parse do
  def stringToPoint(pString) do
    pString
    |> sanitize
    |> String.split(",", trim: true)
    |> Enum.map(&String.to_integer/1)
  end

  def stringToPointList(pString) do
    pString
    |> sanitize
    |> String.split("[", trim: true)
    |> Enum.map(&stringToPoint/1)
  end

  def sanitize(pString) do
    pString
    |> standardizeListSeparator
    |> standardizeItemSeparator
    |> stripWhitespace
    |> stripUnusedCharacters
  end

  def standardizeListSeparator(pString) do
    Regex.replace(~r/[<([]/, pString, "[")
  end

  def standardizeItemSeparator(pString) do
    Regex.replace(~r/[,.;:]/, pString, ",")
  end

  def stripWhitespace(pString) do
    Regex.replace(~r/ */, pString, "")
  end

  def stripUnusedCharacters(pString) do
    Regex.replace(~r/[^0-9-,[]/, pString, "")
  end
end

defmodule Script do
  def main(args) do
    defaults = [
      origin: "0,0",
      points: "[0,1],[-1,0]",
      result_count: 1,
      help: false,
    ]
    switches = [
      origin: :string,
      points: :string,
      result_count: :integer,
      verbose: :boolean,
      help: :boolean,
    ]
    aliases = [
      o: :origin,
      p: :points,
      r: :result_count,
      v: :verbose,
      "?": :help,
    ]
    {parsed, remaining, invalid} = OptionParser.parse(args, strict: switches, aliases: aliases)
    options = defaults
    |> Keyword.merge(parsed)
    |> Enum.into(%{})
    cond do
      options[:help] or (0 < length remaining) or (0 < length invalid) ->
        usage
      true ->
        process(options)
    end
  end

  def usage() do
    IO.puts("Given a list of points, find the closest points to the origin.")
    IO.puts("The points, origin and desired number of results can be specified.")
    IO.puts("Usage:")
    IO.puts("  closest_points [--origin ORIGIN] [--points POINTS] [--result-count RESULT_COUNT] [--verbose] [--help]")
    IO.puts("    -o | --origin ORIGIN             : coordinates of origin, default \"0,0\"")
    IO.puts("    -p | --points POINTS             : list of points to use, default \"[0,1],[-1,0]\"")
    IO.puts("    -r | --result-count RESULT_COUNT : number of closest points to return, default 1")
    IO.puts("    -v | --verbose                   : print verbose output, default false")
    IO.puts("    -? | --help                      : display this usage summary")
    IO.puts("Examples:")
    IO.puts("  closest_points")
    IO.puts("  closest_points -p [2,1],[0,1],[0,2],[5,5],[0,1],[2,1] -r 3")
    IO.puts("  closest_points -o 3,3 -p [2,1],[0,1],[0,2],[5,5],[0,1],[2,1]")
    IO.puts("  closest_points -o=-1,1 -p [-1,0],[0,-1],[0,1],[1,0] -r 2 -v")
    IO.puts("  closest_points -p [0,0,0,0],[-1,3,3,3],[3,-1,3,3],[3,3,-1,3],[3,3,3,-1] -o 0,1,2,3 -r 2")
  end

  def process(pOptions) do
    # Parameters
    origin = pOptions[:origin] |> Parse.stringToPoint
    points = pOptions[:points] |> Parse.stringToPointList
    result_count = pOptions[:result_count]
    verbose = pOptions[:verbose]

    # Verbose Output
    if verbose do
      # BUG: When the origin costists of values Erlang considers characters,
      #      these characters are printed instead of the numerical values.
      IO.write "Origin: "
      IO.inspect origin
      IO.write "Points: "
      IO.inspect points
      IO.write "Result Count: "
      IO.inspect result_count
      IO.write "Result: "
    end

    # Result
    ClosestPoints.process(origin, points, result_count)
    |> IO.inspect
  end
end

Script.main(System.argv)

#!/usr/bin/env elixir

# PROBLEM:
#   Write a routine that takes a set of points and returns the points
#   closest to the origin.  The routine takes a set of points and the
#   number of points to return.

# EXAMPLE:
#   inputs: points = [(2,1), (0,1), (0,2), (5,5), (0,1), (2,1)], desired = 3
#   outputs: [(0,1), (0,2), (0,1)]

# N dimensional
# set origin

# NOTES:
#   The output can be in any order.

#ETODO Fixme
# A single file solution written in Elixir follows.
# To see a list of the command line arguments, run the following.
# Note that fizz and buzz multiples can be specified from the command line.
#   chmod +x fizz_buzz.exs
#   ./fizz_buzz.exs --help
# Examples:
#   ./fizz_buzz.exs
#   ./fizz_buzz.exs -s 2 -F Big... -B Scary... -W Monsters!
#   ./fizz_buzz.exs -n 50 -x 75
#   ./fizz_buzz.exs -n 50 -x 75 -s 5
#   ./fizz_buzz.exs -f 2 -b 3
#   ./fizz_buzz.exs -n 0 -x 300 -s 3 -f 5 -b 7
#   ./fizz_buzz.exs -n 0 -x 300 -s 11 -f 5 -b 7 -i

defmodule ClosestPoints do
  def process(pOrigin, pPoints, pResultCount) do
    pPoints
    |> Enum.map(&subtractPoint(&1, pOrigin))
    |> Enum.map(&distanceFromOrigin/1)
    |> Enum.zip(pPoints)
    |> Enum.sort(fn {distanceA,_pa}, {distanceB,_pb} -> distanceA <= distanceB end)
    |> Enum.map(fn {_distance, point} -> point end)
    |> Enum.take(pResultCount)
  end

  def subtractPoint(pA, pB) do
    pA
    |> Enum.zip(pB)
    |> Enum.map(fn {a, b} -> a - b end)
  end

  def distanceFromOrigin(pPoint) do
    pPoint
    |> Enum.reduce(0, fn value, total -> total + value*value end)
  end
end

defmodule Parse do
  def stringToPoint(pString) do
    pString
    |> sanitize
    |> String.split(",", trim: true)
    |> Enum.map(&String.to_integer/1)
  end

  def stringToPointList(pString) do
    pString
    |> sanitize
    |> String.split("[", trim: true)
    |> Enum.map(&stringToPoint/1)
  end

  def sanitize(pString) do
    pString
    |> standardizeListSeparator
    |> standardizeItemSeparator
    |> stripWhitespace
    |> stripUnusedCharacters
  end

  def standardizeListSeparator(pString) do
    Regex.replace(~r/[<([]/, pString, "[")
  end

  def standardizeItemSeparator(pString) do
    Regex.replace(~r/[,.;:]/, pString, ",")
  end

  def stripWhitespace(pString) do
    Regex.replace(~r/ */, pString, "")
  end

  def stripUnusedCharacters(pString) do
    Regex.replace(~r/[^0-9-,[]/, pString, "")
  end
end

defmodule Script do
  def main(args) do
    defaults = [
      origin: "0,0",
      points: "[0,1],[-1,0]",
      result_count: 1,
      help: false,
    ]
    switches = [
      origin: :string,
      points: :string,
      result_count: :integer,
      verbose: :boolean,
      help: :boolean,
    ]
    aliases = [
      o: :origin,
      p: :points,
      r: :result_count,
      v: :verbose,
      "?": :help,
    ]
    {parsed, remaining, invalid} = OptionParser.parse(args, strict: switches, aliases: aliases)
    options = defaults
    |> Keyword.merge(parsed)
    |> Enum.into(%{})
    cond do
      options[:help] or (0 < length remaining) or (0 < length invalid) ->
        usage
      true ->
        process(options)
    end
  end

  def usage() do
    IO.puts("Given a list of points, find the closest points to the origin.")
    IO.puts("The points, origin and desired number of results can be specified.")
    IO.puts("Usage:")
    IO.puts("  closest_points [--origin ORIGIN] [--points POINTS] [--result-count RESULT_COUNT] [--verbose] [--help]")
    IO.puts("    --origin ORIGIN             : coordinates of origin, default \"0,0\"")
    IO.puts("    --o ORIGIN                  : coordinates of origin, default \"0,0\"")
    IO.puts("    --points POINTS             : list of points to use, default \"[0,1],[-1,0]\"")
    IO.puts("    --p POINTS                  : list of points to use, default \"[0,1],[-1,0]\"")
    IO.puts("    --result-count RESULT_COUNT : number of closest points to return, default 1")
    IO.puts("    --r RESULT_COUNT            : number of closest points to return, default 1")
    IO.puts("    --verbose                   : print verbose output, default false")
    IO.puts("    -v                          : print verbose output, default false")
    IO.puts("    --help                      : display this usage summary")
    IO.puts("    -?                          : display this usage summary")
    IO.puts("Examples:")
    IO.puts("  closest_points")
    IO.puts("  closest_points -o 3,3 -p [2,1],[0,1],[0,2],[5,5],[0,1],[2,1]")
    IO.puts("  closest_points -p [2,1],[0,1],[0,2],[5,5],[0,1],[2,1] -r 3")
    IO.puts("  closest_points -o=-1,1 -p [-1,0],[0,-1],[0,1],[1,0] -r 2 -v")
  end

  def process(pOptions) do
    # Parameters
    origin = pOptions[:origin] |> Parse.stringToPoint
    points = pOptions[:points] |> Parse.stringToPointList
    result_count = pOptions[:result_count]
    verbose = pOptions[:verbose]

    # Verbose Output
    if verbose do
      # BUG: When the origin costists of values Erlang considers characters,
      #      these characters are printed instead of the numerical values.
      IO.write "Origin: "
      IO.inspect origin
      IO.write "Points: "
      IO.inspect points
      IO.write "Result Count: "
      IO.inspect result_count
      IO.write "Result: "
    end

    # Result
    ClosestPoints.process(origin, points, result_count)
    |> IO.inspect
  end
end

Script.main(System.argv)


Add extra test for string concatenation as match usage

This case has been fixed in 78c7ddc03f5c67b10bc81a4b30695d055f419661.

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

defmodule Kernel.BinaryTest do
  use ExUnit.Case, async: true

  test "heredoc" do
    assert 7 == __ENV__.line
    assert "foo\nbar\n" == """
foo
bar
"""

    assert 13 == __ENV__.line
    assert "foo\nbar \"\"\"\n" == """
foo
bar \"""
"""
  end

  test "aligned heredoc" do
    assert "foo\nbar\n" == """
    foo
    bar
    """
  end

  test "heredoc with interpolation" do
    assert "29\n" == """
    #{__ENV__.line}
    """

    assert "\n34\n" == """

    #{__ENV__.line}
    """
  end

  test "heredoc in call" do
    assert "foo\nbar" == Kernel.<>("""
    foo
    """, "bar")
  end

  test "utf8" do
    assert byte_size(" ゆんゆん") == 13
  end

  test "utf8 char" do
    assert ?ゆ == 12422
    assert ?\ゆ == 12422
  end

  test "string concatenation as match" do
    "foo" <> x = "foobar"
    assert x == "bar"

    <<"foo">> <> x = "foobar"
    assert x == "bar"

    <<"f", "oo">> <> x = "foobar"
    assert x == "bar"

    <<x::binary-size(3)>> <> _ = "foobar"
    assert x == "foo"

    size = 3
    <<x::binary-size(size)>> <> _ = "foobar"
    assert x == "foo"

    size = 16
    <<x::size(size)>> <> _ = "foobar"
    assert x == 26223

    <<x::6*4-binary>> <> _ = "foobar"
    assert x == "foo"

    <<x::3-bytes>> <> _ = "foobar"
    assert x == "foo"

    <<x::24-bits>> <> _ = "foobar"
    assert x == "foo"

    assert_raise MatchError, fn ->
      Code.eval_string(~s{<<x::binary-size(3)-unit(4)>> <> _ = "foobar"})
    end

    assert_raise MatchError, fn ->
      Code.eval_string(~s{<<x::integer-size(4)>> <> _ = "foobar"})
    end
  end

  test "hex" do
    assert "\x76" == "v"
    assert "\u00FF" == "ÿ"
    assert "\u{A}"== "\n"
    assert "\u{E9}"== "é"
    assert "\u{10F}" == <<196, 143>>
    assert "\u{10FF}" == <<225, 131, 191>>
    assert "\u{10FFF}" == <<240, 144, 191, 191>>
    assert "\u{10FFFF}" == <<244, 143, 191, 191>>
  end

  test "match" do
    assert match?(<<?a, _::binary>>, "ab")
    refute match?(<<?a, _::binary>>, "cd")
    assert match?(<<_::utf8>> <> _, "éf")
  end

  test "interpolation" do
    res = "hello \\abc"
    assert "hello #{"\\abc"}" == res
    assert "hello #{"\\abc" <> ""}" == res
  end

  test "pattern match" do
    s = 16
    assert <<_a, _b :: size(s)>> = "foo"
  end

  test "pattern match with splice" do
    assert <<1, <<2, 3, 4>>, 5>> = <<1, 2, 3, 4, 5>>
  end

  test "partial application" do
    assert (&<<&1, 2>>).(1) == <<1, 2>>
    assert (&<<&1, &2>>).(1, 2) == <<1, 2>>
    assert (&<<&2, &1>>).(2, 1) == <<1, 2>>
  end

  test "literal" do
    assert <<106, 111, 115, 195, 169>> == <<"josé">>
    assert <<106, 111, 115, 195, 169>> == <<"#{:"josé"}">>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::binary>>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::bits>>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::bitstring>>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::bytes>>

    assert <<106, 111, 115, 195, 169>> == <<"josé"::utf8>>
    assert <<0, 106, 0, 111, 0, 115, 0, 233>> == <<"josé"::utf16>>
    assert <<106, 0, 111, 0, 115, 0, 233, 0>> == <<"josé"::little-utf16>>
    assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 233>> == <<"josé"::utf32>>
  end

  test "literal errors" do
    assert_raise CompileError, fn ->
      Code.eval_string(~s[<<"foo"::integer>>])
    end

    assert_raise CompileError, fn ->
      Code.eval_string(~s[<<"foo"::float>>])
    end

    assert_raise CompileError, fn ->
      Code.eval_string(~s[<<'foo'::binary>>])
    end

    assert_raise ArgumentError, fn ->
      Code.eval_string(~s[<<1::size(4)>> <> "foo"])
    end
  end

  @binary "new "

  test "bitsyntax expansion" do
    assert <<@binary, "world">> == "new world"
  end

  test "bitsyntax translation" do
    refb = "sample"
    sec_data = "another"
    <<byte_size(refb)::size(1)-big-signed-integer-unit(8),
      refb::binary,
      byte_size(sec_data)::1*16-big-signed-integer,
      sec_data::binary>>
  end

  test "bitsyntax size shortcut" do
    assert <<1::3>> == <<1::size(3)>>
    assert <<1::3*8>> == <<1::size(3)-unit(8)>>
  end

  test "bitsyntax variable size" do
    x = 8
    assert <<_, _::size(x)>> = <<?a, ?b>>
    assert (fn <<_, _::size(x)>> -> true end).(<<?a, ?b>>)
  end

  defmacrop signed_16 do
    quote do
      big-signed-integer-unit(16)
    end
  end

  defmacrop refb_spec do
    quote do
      1*8-big-signed-integer
    end
  end

  test "bitsyntax macro" do
    refb = "sample"
    sec_data = "another"
    <<byte_size(refb)::refb_spec,
      refb::binary,
      byte_size(sec_data)::size(1)-signed_16,
      sec_data::binary>>
  end
end

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

defmodule Kernel.BinaryTest do
  use ExUnit.Case, async: true

  test "heredoc" do
    assert 7 == __ENV__.line
    assert "foo\nbar\n" == """
foo
bar
"""

    assert 13 == __ENV__.line
    assert "foo\nbar \"\"\"\n" == """
foo
bar \"""
"""
  end

  test "aligned heredoc" do
    assert "foo\nbar\n" == """
    foo
    bar
    """
  end

  test "heredoc with interpolation" do
    assert "29\n" == """
    #{__ENV__.line}
    """

    assert "\n34\n" == """

    #{__ENV__.line}
    """
  end

  test "heredoc in call" do
    assert "foo\nbar" == Kernel.<>("""
    foo
    """, "bar")
  end

  test "utf8" do
    assert byte_size(" ゆんゆん") == 13
  end

  test "utf8 char" do
    assert ?ゆ == 12422
    assert ?\ゆ == 12422
  end

  test "string concatenation as match" do
    "foo" <> x = "foobar"
    assert x == "bar"

    <<"foo">> <> x = "foobar"
    assert x == "bar"

    <<"f", "oo">> <> x = "foobar"
    assert x == "bar"

    <<x::binary-size(3)>> <> _ = "foobar"
    assert x == "foo"

    size = 3
    <<x::binary-size(size)>> <> _ = "foobar"
    assert x == "foo"

    <<x::6*4-binary>> <> _ = "foobar"
    assert x == "foo"

    <<x::3-bytes>> <> _ = "foobar"
    assert x == "foo"

    <<x::24-bits>> <> _ = "foobar"
    assert x == "foo"

    assert_raise MatchError, fn ->
      Code.eval_string(~s{<<x::binary-size(3)-unit(4)>> <> _ = "foobar"})
    end

    assert_raise MatchError, fn ->
      Code.eval_string(~s{<<x::integer-size(4)>> <> _ = "foobar"})
    end
  end

  test "hex" do
    assert "\x76" == "v"
    assert "\u00FF" == "ÿ"
    assert "\u{A}"== "\n"
    assert "\u{E9}"== "é"
    assert "\u{10F}" == <<196, 143>>
    assert "\u{10FF}" == <<225, 131, 191>>
    assert "\u{10FFF}" == <<240, 144, 191, 191>>
    assert "\u{10FFFF}" == <<244, 143, 191, 191>>
  end

  test "match" do
    assert match?(<<?a, _::binary>>, "ab")
    refute match?(<<?a, _::binary>>, "cd")
    assert match?(<<_::utf8>> <> _, "éf")
  end

  test "interpolation" do
    res = "hello \\abc"
    assert "hello #{"\\abc"}" == res
    assert "hello #{"\\abc" <> ""}" == res
  end

  test "pattern match" do
    s = 16
    assert <<_a, _b :: size(s)>> = "foo"
  end

  test "pattern match with splice" do
    assert <<1, <<2, 3, 4>>, 5>> = <<1, 2, 3, 4, 5>>
  end

  test "partial application" do
    assert (&<<&1, 2>>).(1) == <<1, 2>>
    assert (&<<&1, &2>>).(1, 2) == <<1, 2>>
    assert (&<<&2, &1>>).(2, 1) == <<1, 2>>
  end

  test "literal" do
    assert <<106, 111, 115, 195, 169>> == <<"josé">>
    assert <<106, 111, 115, 195, 169>> == <<"#{:"josé"}">>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::binary>>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::bits>>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::bitstring>>
    assert <<106, 111, 115, 195, 169>> == <<"josé"::bytes>>

    assert <<106, 111, 115, 195, 169>> == <<"josé"::utf8>>
    assert <<0, 106, 0, 111, 0, 115, 0, 233>> == <<"josé"::utf16>>
    assert <<106, 0, 111, 0, 115, 0, 233, 0>> == <<"josé"::little-utf16>>
    assert <<0, 0, 0, 106, 0, 0, 0, 111, 0, 0, 0, 115, 0, 0, 0, 233>> == <<"josé"::utf32>>
  end

  test "literal errors" do
    assert_raise CompileError, fn ->
      Code.eval_string(~s[<<"foo"::integer>>])
    end

    assert_raise CompileError, fn ->
      Code.eval_string(~s[<<"foo"::float>>])
    end

    assert_raise CompileError, fn ->
      Code.eval_string(~s[<<'foo'::binary>>])
    end

    assert_raise ArgumentError, fn ->
      Code.eval_string(~s[<<1::size(4)>> <> "foo"])
    end
  end

  @binary "new "

  test "bitsyntax expansion" do
    assert <<@binary, "world">> == "new world"
  end

  test "bitsyntax translation" do
    refb = "sample"
    sec_data = "another"
    <<byte_size(refb)::size(1)-big-signed-integer-unit(8),
      refb::binary,
      byte_size(sec_data)::1*16-big-signed-integer,
      sec_data::binary>>
  end

  test "bitsyntax size shortcut" do
    assert <<1::3>> == <<1::size(3)>>
    assert <<1::3 * 8>> == <<1::size(3)-unit(8)>>
  end

  test "bitsyntax variable size" do
    x = 8
    assert <<_, _::size(x)>> = <<?a, ?b>>
    assert (fn <<_, _::size(x)>> -> true end).(<<?a, ?b>>)
  end

  defmacrop signed_16 do
    quote do
      big-signed-integer-unit(16)
    end
  end

  defmacrop refb_spec do
    quote do
      1*8-big-signed-integer
    end
  end

  test "bitsyntax macro" do
    refb = "sample"
    sec_data = "another"
    <<byte_size(refb)::refb_spec,
      refb::binary,
      byte_size(sec_data)::size(1)-signed_16,
      sec_data::binary>>
  end
end


update missing counters async

update missing counters async

defmodule PanWeb.PodcastController do
  use Pan.Web, :controller
  alias PanWeb.{AlternateFeed, Chapter, Category, Enclosure, Episode, Feed, Gig, Like,
                Podcast, Recommendation}
  require Logger

  plug :scrub_params, "podcast" when action in [:create, :update]

  def orphans(conn, _params) do
    unassigned_podcasts =
      from(p in Podcast, left_join: c in assoc(p, :categories),
                         where: is_nil(c.id) and is_false(p.blocked))
      |> Repo.all

    podcasts_without_episodes = from(p in Podcast, where: p.episodes_count == 0)
                                |> Repo.all()

    render(conn, "orphans.html", unassigned_podcasts: unassigned_podcasts,
                                 podcasts_without_episodes: podcasts_without_episodes)
  end

  def assign_to_unsorted(conn, _params) do
    podcast_ids = from(a in "categories_podcasts", group_by: a.podcast_id,
                                                   select:   a.podcast_id)
                  |> Repo.all

    podcasts = from(p in Podcast, where: p.id not in ^podcast_ids and is_false(p.blocked))
               |> Repo.all

    category = Repo.get_by(Category, title: "Unsorted")
               |> Repo.preload(:podcasts)

    Ecto.Changeset.change(category)
    |> Ecto.Changeset.put_assoc(:podcasts, category.podcasts ++ podcasts)
    |> Repo.update!

    put_flash(conn, :info, "Podcasts assigned successfully.")
    |> redirect(to: podcast_path(conn, :orphans))
  end

  def index(conn, _params) do
    render(conn, "index.html")
  end

  def datatable(conn, params) do
    search = params["search"]["value"]
    searchfrag = "%#{params["search"]["value"]}%"

    limit = String.to_integer(params["length"])
    offset = String.to_integer(params["start"])
    draw = String.to_integer(params["draw"])

    columns = params["columns"]

    order_by = Enum.map(params["order"], fn({_key, value}) ->
                 column_number = value["column"]
                 {String.to_atom(value["dir"]), String.to_atom(columns[column_number]["data"])}
               end)

    records_total = Repo.aggregate(Podcast, :count, :id)

    query =
      if search != "" do
        from(p in Podcast, where: ilike(p.title, ^searchfrag) or
                                  ilike(p.website, ^searchfrag) or
                                  ilike(fragment("cast (? as text)", p.id), ^searchfrag))
      else
        from(p in Podcast)
      end

    records_filtered = query
                       |> Repo.aggregate(:count, :id)

    podcasts = from(p in query, limit: ^limit,
                                offset: ^offset,
                                order_by: ^order_by,
                                select: %{id: p.id,
                                          title: p.title,
                                          update_paused: p.update_paused,
                                          updated_at: p.updated_at,
                                          update_intervall: p.update_intervall,
                                          next_update: p.next_update,
                                          website: p.website,
                                          failure_count: p.failure_count})
           |> Repo.all()

    render(conn, "datatable.json", podcasts: podcasts,
                                   draw: draw,
                                   records_total: records_total,
                                   records_filtered: records_filtered)
  end

  def stale(conn, _params) do
    render(conn, "stale.html")
  end

  def datatable_stale(conn, params) do
    search = params["search"]["value"]
    searchfrag = "%#{params["search"]["value"]}%"

    limit = String.to_integer(params["length"])
    offset = String.to_integer(params["start"])
    draw = String.to_integer(params["draw"])

    columns = params["columns"]

    order_by = Enum.map(params["order"], fn({_key, value}) ->
                 column_number = value["column"]
                 {String.to_atom(value["dir"]), String.to_atom(columns[column_number]["data"])}
               end)

    records_total = Repo.aggregate(Podcast, :count, :id)

    query =
      if search != "" do
        from(p in Podcast, where: p.next_update <= ^Timex.now() and
                                  is_false(p.update_paused) and is_false(p.retired) and
                                  (ilike(p.title, ^searchfrag) or
                                   ilike(p.website, ^searchfrag) or
                                   ilike(fragment("cast (? as text)", p.id), ^searchfrag)))
      else
        from(p in Podcast, where: p.next_update <= ^Timex.now() and
                                  is_false(p.update_paused) and is_false(p.retired))
      end

    records_filtered = query
                       |> Repo.aggregate(:count, :id)

    podcasts = from(p in query, join: f in assoc(p, :feeds),
                                limit: ^limit,
                                offset: ^offset,
                                order_by: ^order_by,
                                select: %{id: p.id,
                                          title: p.title,
                                          update_paused: p.update_paused,
                                          updated_at: p.updated_at,
                                          update_intervall: p.update_intervall,
                                          feed_url: f.self_link_url,
                                          next_update: p.next_update,
                                          website: p.website,
                                          failure_count: p.failure_count})
           |> Repo.all()

    render(conn, "datatable_stale.json", podcasts: podcasts,
                                         draw: draw,
                                         records_total: records_total,
                                         records_filtered: records_filtered)
  end

  def factory(conn, _params) do
    podcasts = from(p in Podcast, order_by: [asc: :updated_at],
                                  where: p.update_paused == true and is_false(p.retired),
                                  preload: :feeds)
               |> Repo.all()

    render(conn, "factory.html", podcasts: podcasts)
  end

  def new(conn, _params) do
    changeset = Podcast.changeset(%Podcast{})
    render(conn, "new.html", changeset: changeset)
  end

  def create(conn, %{"podcast" => podcast_params}) do
    changeset = Podcast.changeset(%Podcast{}, podcast_params)

    case Repo.insert(changeset) do
      {:ok, _podcast} ->
        put_flash(conn, :info, "Podcast created successfully.")
        |> redirect(to: podcast_path(conn, :index))
      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

  def show(conn, %{"id" => id}) do
    podcast = Repo.get!(Podcast, id)
              |> Repo.preload(episodes: from(e in Episode, order_by: [desc: e.publishing_date],
                                                           limit: 100))
              |> Repo.preload(episodes: :podcast)
              |> Repo.preload(feeds: :podcast)
              |> Repo.preload([:languages, :categories, :contributors])

    render(conn, "show.html", podcast: podcast)
  end

  def edit(conn, %{"id" => id}) do
    podcast = Repo.get!(Podcast, id)
    changeset = Podcast.changeset(podcast)
    render(conn, "edit.html", podcast: podcast, changeset: changeset)
  end

  def update(conn, %{"id" => id, "podcast" => podcast_params}) do
    id = String.to_integer(id)
    podcast = Repo.get!(Podcast, id)
    changeset = Podcast.changeset(podcast, podcast_params)

    case Repo.update(changeset) do
      {:ok, podcast} ->
        Podcast.update_search_index(id)
        Podcast.remove_unwanted_references(id)

        put_flash(conn, :info, "Podcast updated successfully.")
        |> redirect(to: podcast_path(conn, :show, podcast))
      {:error, changeset} ->
        render(conn, "edit.html", podcast: podcast, changeset: changeset)
    end
  end

  def delete(conn, %{"id" => id}) do
    id = String.to_integer(id)
    podcast = Repo.get!(Podcast, id)
              |> Repo.preload(episodes: :chapters)
              |> Repo.preload(:feeds)

    for episode <- podcast.episodes do
      for chapter <- episode.chapters do
        Repo.delete_all(from l in Like, where: l.chapter_id == ^chapter.id)
        Repo.delete_all(from r in Recommendation, where: r.chapter_id == ^chapter.id)
      end
      Repo.delete_all(from c in Chapter, where: c.episode_id == ^episode.id)
      Repo.delete_all(from e in Enclosure, where: e.episode_id == ^episode.id)
      Repo.delete_all(from g in Gig, where: g.episode_id == ^episode.id)
    end

    for feed <- podcast.feeds do
      Repo.delete_all(from a in AlternateFeed, where: a.feed_id == ^feed.id)
    end

    Repo.delete!(podcast)
    Podcast.delete_search_index(id)

    conn
    |> put_flash(:info, "Podcast deleted successfully.")
    |> redirect(to: podcast_path(conn, :index))
  end

  def delta_import(conn, %{"id" => id}, forced \\ false, no_failure_count_increase \\ false) do
    podcast = Repo.get!(Podcast, id)
    current_user = conn.assigns.current_user

    case Pan.Updater.Podcast.import_new_episodes(podcast, current_user, forced, no_failure_count_increase) do
      {:ok,    message} -> put_flash(conn, :info, message)
      {:error, message} -> put_flash(conn, :error, message)
    end
    |> redirect(to: podcast_path(conn, :show, id))
  end

  def forced_delta_import(conn, %{"id" => id}) do
    delta_import(conn, %{"id" => id}, :forced, false)
    # delta_import(conn, %{"id" => id}, :forced, :no_failure_count_increase)
  end

  def contributor_import(conn, %{"id" => id}) do
    case Pan.Parser.Podcast.contributor_import(id) do
      {:ok,    message} -> put_flash(conn, :info, message)
      {:error, message} -> put_flash(conn, :error, message)
    end
    |> redirect(to: podcast_path(conn, :show, id))
  end

  def fix_owner(conn, %{"id" => id}) do
    Pan.Parser.Podcast.fix_owner(id)

    conn
    |> put_flash(:info, "Owner fixed successfully.")
    |> redirect(to: podcast_path(conn, :index))
  end

  def fix_languages(conn, _params) do
    podcast_ids = from(a in "languages_podcasts", group_by: a.podcast_id,
                                                  select:   a.podcast_id)
                  |> Repo.all

    podcasts_without_languages = from(p in Podcast, where: is_false(p.update_paused) and
                                                           p.id not in ^podcast_ids)
                                 |> Repo.all()
                                 |> Enum.shuffle()

    for {podcast, index} <- Enum.with_index(podcasts_without_languages) do
      Logger.info ("#{index} of #{length(podcasts_without_languages)}")
      Pan.Parser.Podcast.fix_language(podcast)
    end

    conn
    |> put_flash(:info, "Languages fixed successfully.")
    |> redirect(to: podcast_path(conn, :index))
  end

  def delta_import_all(conn, _params) do
    current_user = conn.assigns.current_user

    podcasts = from(p in Podcast, where: p.next_update <= ^Timex.now() and
                                         is_false(p.update_paused) and is_false(p.retired),
                                  order_by: [asc: :next_update],
                                  limit: 2000)
                  |> Repo.all()

    Task.start(fn -> trigger_import_new_episodes(podcasts, current_user) end)

    put_flash(conn, :info, "Async podcasts update Task started .")
    |> redirect(to: podcast_path(conn, :stale))
  end

  defp trigger_import_new_episodes(podcasts, current_user) do
    for podcast <- podcasts do
      Pan.Updater.Podcast.import_new_episodes(podcast, current_user)
    end
    Logger.info "=== Manual triggered podcast update finished ==="
  end

  def touch(conn, %{"id" => id}) do
    Repo.get!(Podcast, id)
    |> PanWeb.Podcast.changeset
    |> Repo.update([force: true])

    put_flash(conn, :info, "Podcast touched.")
    |> redirect(to: podcast_path(conn, :index))
  end

  def pause(conn, %{"id" => id}) do
    from(p in Podcast, where: p.id == ^id)
    |> Repo.update_all(set: [update_paused: true])

    put_flash(conn, :info, "Podcast paused.")
    |> redirect(to: podcast_path(conn, :index))
  end

  def retirement(conn, _params) do
    candidates = from(p in Podcast, where: is_false(p.retired),
                                    join: e in assoc(p, :episodes),
                                    group_by: p.id,
                                    having: max(e.publishing_date) < ago(1, "year"),
                                    select: %{id: p.id,
                                              title: p.title,
                                              last_build_date: p.last_build_date,
                                              last_episode_date: max(e.publishing_date)},
                                    order_by: max(e.publishing_date))
                 |> Repo.all()

    retired = from(p in Podcast, where: p.retired == true)
              |> Repo.all

    render(conn, "retirement.html", candidates: candidates,
                                    retired: retired)
  end

  def retire(conn, %{"id" => id}) do
    from(p in Podcast, where: p.id == ^id)
    |> Repo.update_all(set: [retired: true])

    conn
    |> put_flash(:info, "Podcast retired.")
    |> redirect(to: podcast_path(conn, :retirement))
  end

  def duplicates(conn, _params) do
    duplicate_feeds = from(f in Feed, group_by: f.self_link_url,
                                      having: count(f.id) > 1,
                                      select: f.self_link_url)
                      |> Repo.all()

    feeds = from(f in Feed, where: f.self_link_url in ^duplicate_feeds,
                            order_by: f.self_link_url,
                            preload: :podcast)
            |> Repo.all()

    render(conn, "duplicates.html", feeds: feeds)
  end

  def update_from_feed(conn, %{"id" => id}) do
    podcast = Repo.get!(Podcast, id)
    case Pan.Parser.Podcast.update_from_feed(podcast) do
      {:ok,    message} -> put_flash(conn, :info, message)
      {:error, message} -> put_flash(conn, :error, message)
    end
    |> redirect(to: podcast_path(conn, :show, podcast.id))
  end

  def update_missing_counters(conn, _params) do
    podcasts = from(p in Podcast, where: p.publication_frequency == 0.0)
               |> Repo.all()

    Task.start(fn -> update_missing_counters_async(podcasts) end)
    render(conn, "done.html")
  end

  defp update_missing_counters_async(podcasts) do
    for podcast <- podcasts do
      Podcast.changeset(podcast)
      |> Podcast.update_counters()
      |> Repo.update()
    end
  end
end

defmodule PanWeb.PodcastController do
  use Pan.Web, :controller
  alias PanWeb.{AlternateFeed, Chapter, Category, Enclosure, Episode, Feed, Gig, Like,
                Podcast, Recommendation}
  require Logger

  plug :scrub_params, "podcast" when action in [:create, :update]

  def orphans(conn, _params) do
    unassigned_podcasts =
      from(p in Podcast, left_join: c in assoc(p, :categories),
                         where: is_nil(c.id) and is_false(p.blocked))
      |> Repo.all

    podcasts_without_episodes = from(p in Podcast, where: p.episodes_count == 0)
                                |> Repo.all()

    render(conn, "orphans.html", unassigned_podcasts: unassigned_podcasts,
                                 podcasts_without_episodes: podcasts_without_episodes)
  end

  def assign_to_unsorted(conn, _params) do
    podcast_ids = from(a in "categories_podcasts", group_by: a.podcast_id,
                                                   select:   a.podcast_id)
                  |> Repo.all

    podcasts = from(p in Podcast, where: p.id not in ^podcast_ids and is_false(p.blocked))
               |> Repo.all

    category = Repo.get_by(Category, title: "Unsorted")
               |> Repo.preload(:podcasts)

    Ecto.Changeset.change(category)
    |> Ecto.Changeset.put_assoc(:podcasts, category.podcasts ++ podcasts)
    |> Repo.update!

    put_flash(conn, :info, "Podcasts assigned successfully.")
    |> redirect(to: podcast_path(conn, :orphans))
  end

  def index(conn, _params) do
    render(conn, "index.html")
  end

  def datatable(conn, params) do
    search = params["search"]["value"]
    searchfrag = "%#{params["search"]["value"]}%"

    limit = String.to_integer(params["length"])
    offset = String.to_integer(params["start"])
    draw = String.to_integer(params["draw"])

    columns = params["columns"]

    order_by = Enum.map(params["order"], fn({_key, value}) ->
                 column_number = value["column"]
                 {String.to_atom(value["dir"]), String.to_atom(columns[column_number]["data"])}
               end)

    records_total = Repo.aggregate(Podcast, :count, :id)

    query =
      if search != "" do
        from(p in Podcast, where: ilike(p.title, ^searchfrag) or
                                  ilike(p.website, ^searchfrag) or
                                  ilike(fragment("cast (? as text)", p.id), ^searchfrag))
      else
        from(p in Podcast)
      end

    records_filtered = query
                       |> Repo.aggregate(:count, :id)

    podcasts = from(p in query, limit: ^limit,
                                offset: ^offset,
                                order_by: ^order_by,
                                select: %{id: p.id,
                                          title: p.title,
                                          update_paused: p.update_paused,
                                          updated_at: p.updated_at,
                                          update_intervall: p.update_intervall,
                                          next_update: p.next_update,
                                          website: p.website,
                                          failure_count: p.failure_count})
           |> Repo.all()

    render(conn, "datatable.json", podcasts: podcasts,
                                   draw: draw,
                                   records_total: records_total,
                                   records_filtered: records_filtered)
  end

  def stale(conn, _params) do
    render(conn, "stale.html")
  end

  def datatable_stale(conn, params) do
    search = params["search"]["value"]
    searchfrag = "%#{params["search"]["value"]}%"

    limit = String.to_integer(params["length"])
    offset = String.to_integer(params["start"])
    draw = String.to_integer(params["draw"])

    columns = params["columns"]

    order_by = Enum.map(params["order"], fn({_key, value}) ->
                 column_number = value["column"]
                 {String.to_atom(value["dir"]), String.to_atom(columns[column_number]["data"])}
               end)

    records_total = Repo.aggregate(Podcast, :count, :id)

    query =
      if search != "" do
        from(p in Podcast, where: p.next_update <= ^Timex.now() and
                                  is_false(p.update_paused) and is_false(p.retired) and
                                  (ilike(p.title, ^searchfrag) or
                                   ilike(p.website, ^searchfrag) or
                                   ilike(fragment("cast (? as text)", p.id), ^searchfrag)))
      else
        from(p in Podcast, where: p.next_update <= ^Timex.now() and
                                  is_false(p.update_paused) and is_false(p.retired))
      end

    records_filtered = query
                       |> Repo.aggregate(:count, :id)

    podcasts = from(p in query, join: f in assoc(p, :feeds),
                                limit: ^limit,
                                offset: ^offset,
                                order_by: ^order_by,
                                select: %{id: p.id,
                                          title: p.title,
                                          update_paused: p.update_paused,
                                          updated_at: p.updated_at,
                                          update_intervall: p.update_intervall,
                                          feed_url: f.self_link_url,
                                          next_update: p.next_update,
                                          website: p.website,
                                          failure_count: p.failure_count})
           |> Repo.all()

    render(conn, "datatable_stale.json", podcasts: podcasts,
                                         draw: draw,
                                         records_total: records_total,
                                         records_filtered: records_filtered)
  end

  def factory(conn, _params) do
    podcasts = from(p in Podcast, order_by: [asc: :updated_at],
                                  where: p.update_paused == true and is_false(p.retired),
                                  preload: :feeds)
               |> Repo.all()

    render(conn, "factory.html", podcasts: podcasts)
  end

  def new(conn, _params) do
    changeset = Podcast.changeset(%Podcast{})
    render(conn, "new.html", changeset: changeset)
  end

  def create(conn, %{"podcast" => podcast_params}) do
    changeset = Podcast.changeset(%Podcast{}, podcast_params)

    case Repo.insert(changeset) do
      {:ok, _podcast} ->
        put_flash(conn, :info, "Podcast created successfully.")
        |> redirect(to: podcast_path(conn, :index))
      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

  def show(conn, %{"id" => id}) do
    podcast = Repo.get!(Podcast, id)
              |> Repo.preload(episodes: from(e in Episode, order_by: [desc: e.publishing_date],
                                                           limit: 100))
              |> Repo.preload(episodes: :podcast)
              |> Repo.preload(feeds: :podcast)
              |> Repo.preload([:languages, :categories, :contributors])

    render(conn, "show.html", podcast: podcast)
  end

  def edit(conn, %{"id" => id}) do
    podcast = Repo.get!(Podcast, id)
    changeset = Podcast.changeset(podcast)
    render(conn, "edit.html", podcast: podcast, changeset: changeset)
  end

  def update(conn, %{"id" => id, "podcast" => podcast_params}) do
    id = String.to_integer(id)
    podcast = Repo.get!(Podcast, id)
    changeset = Podcast.changeset(podcast, podcast_params)

    case Repo.update(changeset) do
      {:ok, podcast} ->
        Podcast.update_search_index(id)
        Podcast.remove_unwanted_references(id)

        put_flash(conn, :info, "Podcast updated successfully.")
        |> redirect(to: podcast_path(conn, :show, podcast))
      {:error, changeset} ->
        render(conn, "edit.html", podcast: podcast, changeset: changeset)
    end
  end

  def delete(conn, %{"id" => id}) do
    id = String.to_integer(id)
    podcast = Repo.get!(Podcast, id)
              |> Repo.preload(episodes: :chapters)
              |> Repo.preload(:feeds)

    for episode <- podcast.episodes do
      for chapter <- episode.chapters do
        Repo.delete_all(from l in Like, where: l.chapter_id == ^chapter.id)
        Repo.delete_all(from r in Recommendation, where: r.chapter_id == ^chapter.id)
      end
      Repo.delete_all(from c in Chapter, where: c.episode_id == ^episode.id)
      Repo.delete_all(from e in Enclosure, where: e.episode_id == ^episode.id)
      Repo.delete_all(from g in Gig, where: g.episode_id == ^episode.id)
    end

    for feed <- podcast.feeds do
      Repo.delete_all(from a in AlternateFeed, where: a.feed_id == ^feed.id)
    end

    Repo.delete!(podcast)
    Podcast.delete_search_index(id)

    conn
    |> put_flash(:info, "Podcast deleted successfully.")
    |> redirect(to: podcast_path(conn, :index))
  end

  def delta_import(conn, %{"id" => id}, forced \\ false, no_failure_count_increase \\ false) do
    podcast = Repo.get!(Podcast, id)
    current_user = conn.assigns.current_user

    case Pan.Updater.Podcast.import_new_episodes(podcast, current_user, forced, no_failure_count_increase) do
      {:ok,    message} -> put_flash(conn, :info, message)
      {:error, message} -> put_flash(conn, :error, message)
    end
    |> redirect(to: podcast_path(conn, :show, id))
  end

  def forced_delta_import(conn, %{"id" => id}) do
    delta_import(conn, %{"id" => id}, :forced, false)
    # delta_import(conn, %{"id" => id}, :forced, :no_failure_count_increase)
  end

  def contributor_import(conn, %{"id" => id}) do
    case Pan.Parser.Podcast.contributor_import(id) do
      {:ok,    message} -> put_flash(conn, :info, message)
      {:error, message} -> put_flash(conn, :error, message)
    end
    |> redirect(to: podcast_path(conn, :show, id))
  end

  def fix_owner(conn, %{"id" => id}) do
    Pan.Parser.Podcast.fix_owner(id)

    conn
    |> put_flash(:info, "Owner fixed successfully.")
    |> redirect(to: podcast_path(conn, :index))
  end

  def fix_languages(conn, _params) do
    podcast_ids = from(a in "languages_podcasts", group_by: a.podcast_id,
                                                  select:   a.podcast_id)
                  |> Repo.all

    podcasts_without_languages = from(p in Podcast, where: is_false(p.update_paused) and
                                                           p.id not in ^podcast_ids)
                                 |> Repo.all()
                                 |> Enum.shuffle()

    for {podcast, index} <- Enum.with_index(podcasts_without_languages) do
      Logger.info ("#{index} of #{length(podcasts_without_languages)}")
      Pan.Parser.Podcast.fix_language(podcast)
    end

    conn
    |> put_flash(:info, "Languages fixed successfully.")
    |> redirect(to: podcast_path(conn, :index))
  end

  def delta_import_all(conn, _params) do
    current_user = conn.assigns.current_user

    podcasts = from(p in Podcast, where: p.next_update <= ^Timex.now() and
                                         is_false(p.update_paused) and is_false(p.retired),
                                  order_by: [asc: :next_update],
                                  limit: 2000)
                  |> Repo.all()

    Task.start(fn -> trigger_import_new_episodes(podcasts, current_user) end)

    put_flash(conn, :info, "Async podcasts update Task started .")
    |> redirect(to: podcast_path(conn, :stale))
  end

  defp trigger_import_new_episodes(podcasts, current_user) do
    for podcast <- podcasts do
      Pan.Updater.Podcast.import_new_episodes(podcast, current_user)
    end
    Logger.info "=== Manual triggered podcast update finished ==="
  end

  def touch(conn, %{"id" => id}) do
    Repo.get!(Podcast, id)
    |> PanWeb.Podcast.changeset
    |> Repo.update([force: true])

    put_flash(conn, :info, "Podcast touched.")
    |> redirect(to: podcast_path(conn, :index))
  end

  def pause(conn, %{"id" => id}) do
    from(p in Podcast, where: p.id == ^id)
    |> Repo.update_all(set: [update_paused: true])

    put_flash(conn, :info, "Podcast paused.")
    |> redirect(to: podcast_path(conn, :index))
  end

  def retirement(conn, _params) do
    candidates = from(p in Podcast, where: is_false(p.retired),
                                    join: e in assoc(p, :episodes),
                                    group_by: p.id,
                                    having: max(e.publishing_date) < ago(1, "year"),
                                    select: %{id: p.id,
                                              title: p.title,
                                              last_build_date: p.last_build_date,
                                              last_episode_date: max(e.publishing_date)},
                                    order_by: max(e.publishing_date))
                 |> Repo.all()

    retired = from(p in Podcast, where: p.retired == true)
              |> Repo.all

    render(conn, "retirement.html", candidates: candidates,
                                    retired: retired)
  end

  def retire(conn, %{"id" => id}) do
    from(p in Podcast, where: p.id == ^id)
    |> Repo.update_all(set: [retired: true])

    conn
    |> put_flash(:info, "Podcast retired.")
    |> redirect(to: podcast_path(conn, :retirement))
  end

  def duplicates(conn, _params) do
    duplicate_feeds = from(f in Feed, group_by: f.self_link_url,
                                      having: count(f.id) > 1,
                                      select: f.self_link_url)
                      |> Repo.all()

    feeds = from(f in Feed, where: f.self_link_url in ^duplicate_feeds,
                            order_by: f.self_link_url,
                            preload: :podcast)
            |> Repo.all()

    render(conn, "duplicates.html", feeds: feeds)
  end

  def update_from_feed(conn, %{"id" => id}) do
    podcast = Repo.get!(Podcast, id)
    case Pan.Parser.Podcast.update_from_feed(podcast) do
      {:ok,    message} -> put_flash(conn, :info, message)
      {:error, message} -> put_flash(conn, :error, message)
    end
    |> redirect(to: podcast_path(conn, :show, podcast.id))
  end

  def update_missing_counters(conn, _params) do
    podcasts = from(p in Podcast, where: p.publication_frequency == 0.0,
                                  limit: 1000)
               |> Repo.all()

    for podcast <- podcasts do
      Podcast.changeset(podcast)
      |> Podcast.update_counters()
      |> Repo.update()
    end

    render(conn, "done.html")
  end
end