Correct overtime tax calculation for Mar 2022 payroll and future

defmodule Mgp.Sync.ImportPayroll do
  @moduledoc "Import and check payroll data"

  import Mgp.Utils, only: [to_date: 1, to_time: 1]
  alias Mgp.Sync.DbaseParser

  @root_folder "/home/hvaria/backup/HPMG22/"
  @expat_salary "/home/hvaria/backup/salary.csv"
  @employee_master "H1EMP.DBF"
  @calculated_payroll "H1DETPAY.DBF"

  def get_management_payroll(month), do: get_management_pay(determine_management_month(month))
  defp get_management_pay(nil), do: []

  defp get_management_pay(m) do
    File.read!(@expat_salary)
    |> String.trim()
    |> String.split("\n")
    |> Enum.drop(1)
    |> Enum.map(fn x ->
      [
        month,
        id,
        tin_no,
        name,
        position,
        salary,
        living_percent,
        vehicle,
        non_cash_percent,
        ssnit_ded,
        ssnit_no
      ] = String.split(x, ",")

      if month === m do
        earned_salary = salary
        tax_year = 2020

        {ssnit_amount, ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2} =
          ssnit_computations(tax_year, id, earned_salary, ssnit_ded)

        living = Decimal.mult(earned_salary, string_to_percent(living_percent))
        non_cash = Decimal.mult(earned_salary, string_to_percent(non_cash_percent))
        total_additions = Decimal.add(Decimal.add(living, non_cash), vehicle)
        taxable_income = Decimal.sub(Decimal.add(earned_salary, total_additions), ssnit_amount)
        tax_ded = gra_income_tax(taxable_income, determine_tax_year(month))

        %{
          month: month,
          id: id,
          tin_no: tin_no,
          ssnit_no: ssnit_no,
          name: name,
          gra_category: position,
          base_salary: earned_salary,
          earned_salary: String.to_integer(earned_salary),
          cash_allowance: 0,
          total_cash: earned_salary,
          ssnit_ded: string_to_percent(ssnit_ded),
          ssnit_amount: ssnit_amount,
          ssnit_emp_contrib: ssnit_emp_contrib,
          ssnit_total: ssnit_total,
          ssnit_tier_1: ssnit_tier_1,
          ssnit_tier_2: ssnit_tier_2,
          pf_amount: 0,
          living_percent: string_to_percent(living_percent),
          living: living,
          vehicle: vehicle,
          non_cash_percent: string_to_percent(non_cash_percent),
          non_cash: non_cash,
          total_additions: total_additions,
          total_gra_income: Decimal.add(earned_salary, total_additions),
          total_relief: ssnit_amount,
          taxable_income: taxable_income,
          tax_ded: tax_ded,
          overtime_earned: 0,
          overtime_tax: 0,
          total_tax: tax_ded,
          net_pay: Decimal.sub(Decimal.sub(earned_salary, tax_ded), ssnit_amount)
        }
      else
        []
      end
    end)
    |> :lists.flatten()
  end

  defp string_to_percent(t), do: Decimal.div(Decimal.new(t), 100)

  def determine_management_month(month) do
    File.read!(@expat_salary)
    |> String.trim()
    |> String.split("\n")
    |> Enum.drop(1)
    |> Enum.map(fn x -> hd(String.split(x, ",")) end)
    |> Enum.uniq()
    |> Enum.find(fn x -> x <= month end)
  end

  def import_month(folder \\ @root_folder, month) do
    files = generate_file_paths(folder)

    with employees <- parse_employee_master(files.employee_master),
         payroll <- parse_and_calculate_monthly_payroll(files.payroll, month, employees) do
      Enum.sort_by(payroll, fn x -> x.id end)
    end
  end

  defp generate_file_paths(folder) do
    %{
      payroll: folder <> @calculated_payroll,
      employee_master: folder <> @employee_master
    }
  end

  def parse_employee_master(dbf_file) do
    DbaseParser.parse(
      dbf_file,
      [
        "EMP_NO",
        "EMP_NM",
        "EMP_JOINDT",
        "EMP_DISCDT",
        "EMP_RTBASE",
        "EMP_SSNO",
        "EMP_FNO1",
        "EMP_FNO2",
        "EMP_FNO3"
      ],
      fn x ->
        %{
          id: x["EMP_NO"],
          name: x["EMP_NM"],
          start_date: parse_date(x["EMP_JOINDT"]),
          end_date: parse_date(x["EMP_DISCDT"]),
          is_terminated: parse_terminated(x["EMP_RTBASE"]),
          ssnit_no: x["EMP_SSNO"],
          tin_no: x["EMP_FNO1"],
          ssnit_t2: x["EMP_FNO2"],
          pf_no: x["EMP_FNO3"]
        }
      end
    )
    |> Enum.reduce(%{}, fn x, acc ->
      Map.put(acc, x.id, x)
    end)
  end

  defp determine_tax_year(month) do
    cond do
      month > "2112M" -> 2022
      month > "1912M" -> 2020
      month > "1812M" -> 2019
      month <= "1812M" -> 2018
    end
  end

  defp parse_and_calculate_monthly_payroll(dbf_file, month, employees) do
    tax_year = determine_tax_year(month)

    DbaseParser.parse(
      dbf_file,
      [
        "PD_MTH",
        "PD_EMPNO",
        "PD_RATE",
        "PD_DEPT",
        "PD_SDEPT",
        "PD_DESIG",
        "PD_CAT",
        "PD_OTAMT",
        "PD_DAYS",
        "PD_OTHALL",
        "PD_LOAN",
        "PD_ADV",
        "PD_PLADV",
        "PD_SSRT",
        "PD_TUCRT",
        "PD_DEDRT1",
        "PD_DEDRT2",
        "PD_TOTAL",
        "PD_CB",
        "PD_BKDET",
        "PD_LMU",
        "PD_LMD",
        "PD_LMT"
      ],
      fn x ->
        if x["PD_MTH"] === month do
          %{
            month: x["PD_MTH"],
            id: x["PD_EMPNO"],
            base_salary: x["PD_RATE"],
            dept: parse_dept(x["PD_DEPT"]),
            sub_dept: parse_sub_dept(x["PD_SDEPT"]),
            title: x["PD_DESIG"],
            gra_category: x["PD_CAT"],
            overtime_earned: x["PD_OTAMT"],
            days_worked: x["PD_DAYS"],
            cash_allowance: x["PD_OTHALL"],
            loan: x["PD_LOAN"],
            advance: x["PD_ADV"],
            pvt_loan: x["PD_PLADV"],
            ssnit_ded: parse_ssnit_ded(Decimal.to_string(x["PD_SSRT"]), x["PD_EMPNO"]),
            tuc_ded: parse_tuc_ded(Decimal.to_string(x["PD_TUCRT"])),
            staff_welfare_ded: x["PD_DEDRT1"],
            pf_ded: x["PD_DEDRT2"],
            net_pay: x["PD_TOTAL"],
            is_cash: parse_cash(x["PD_CB"]),
            bank: x["PD_BKDET"],
            lmu: x["PD_LMU"],
            lmd: to_date(x["PD_LMD"]),
            lmt: to_time(x["PD_LMT"])
          }
        else
          nil
        end
      end
    )
    |> Task.async_stream(&parse_payroll_record(&1, employees, tax_year))
    |> Stream.map(fn {:ok, res} -> res end)
    # |> Stream.map(&parse_payroll_record(&1))
    |> Enum.to_list()
  end

  ### Private Functions ###
  defp parse_payroll_record(emp, employees, tax_year) do
    earned_salary =
      Decimal.round(Decimal.mult(emp.base_salary, Decimal.div(emp.days_worked, 27)), 2)

    {ssnit_amount, ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2} =
      ssnit_computations(tax_year, emp.id, earned_salary, emp.ssnit_ded)

    pf_amount = Decimal.round(Decimal.mult(Decimal.div(emp.pf_ded, 100), earned_salary), 2)

    total_cash = Decimal.add(earned_salary, emp.cash_allowance)

    total_relief = Decimal.add(ssnit_amount, pf_amount)

    taxable_income = Decimal.sub(total_cash, total_relief)

    tax_ded = gra_income_tax(taxable_income, tax_year)

    overtime_tax = gra_overtime_tax(emp.overtime_earned, earned_salary)

    total_tax = Decimal.add(tax_ded, overtime_tax)

    tuc_amount = Decimal.round(Decimal.mult(Decimal.div(emp.tuc_ded, 100), earned_salary), 2)

    total_ded =
      Decimal.add(
        emp.advance,
        Decimal.add(
          emp.loan,
          Decimal.add(
            emp.pvt_loan,
            Decimal.add(emp.staff_welfare_ded, tuc_amount)
          )
        )
      )

    total_pay = Decimal.sub(taxable_income, Decimal.add(total_tax, total_ded))

    Map.merge(
      emp,
      Map.merge(
        employees[emp.id],
        %{
          earned_salary: earned_salary,
          ssnit_emp_contrib: ssnit_emp_contrib,
          ssnit_amount: ssnit_amount,
          ssnit_total: ssnit_total,
          ssnit_tier_1: ssnit_tier_1,
          ssnit_tier_2: ssnit_tier_2,
          pf_amount: pf_amount,
          taxable_income: taxable_income,
          total_cash: total_cash,
          total_relief: total_relief,
          tax_ded: tax_ded,
          overtime_tax: overtime_tax,
          total_tax: total_tax,
          tuc_amount: tuc_amount,
          total_ded: total_ded,
          total_pay: total_pay
        }
      )
    )
  end

  defp ssnit_computations(tax_year, emp_id, earned_salary, ssnit_ded) do
    ssnit_amount =
      if emp_id === "E0053" do
        if tax_year < 2020 do
          Decimal.round(Decimal.mult(Decimal.div(ssnit_ded, 100), earned_salary), 2)
        else
          Decimal.new(0)
        end
      else
        Decimal.round(Decimal.mult(Decimal.div(ssnit_ded, 100), earned_salary), 2)
      end

    {ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2} =
      case emp_id === "E0053" do
        true ->
          if tax_year < 2020 do
            # 12.5 div 5
            ssnit_emp_contrib =
              Decimal.round(Decimal.mult(Decimal.from_float(2.5), ssnit_amount), 2)

            ssnit_total = Decimal.add(ssnit_amount, ssnit_emp_contrib)
            ssnit_tier_1 = ssnit_total
            ssnit_tier_2 = Decimal.new(0)
            {ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2}
          else
            ssnit_emp_contrib = Decimal.new(0)
            ssnit_total = Decimal.new(0)
            ssnit_tier_1 = Decimal.new(0)
            ssnit_tier_2 = Decimal.new(0)
            {ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2}
          end

        false ->
          # 13 div 5.5
          ssnit_emp_contrib =
            Decimal.round(Decimal.mult(Decimal.from_float(2.363636364), ssnit_amount), 2)

          ssnit_total = Decimal.add(ssnit_amount, ssnit_emp_contrib)

          ssnit_tier_1 =
            Decimal.round(Decimal.mult(Decimal.from_float(0.72972973), ssnit_total), 2)

          ssnit_tier_2 = Decimal.sub(ssnit_total, ssnit_tier_1)
          {ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2}
      end

    {ssnit_amount, ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2}
  end

  defp gra_overtime_tax(o, earned_salary) do
    half_earned_salary = Decimal.div(earned_salary, 2)

    case Decimal.compare(o, half_earned_salary) do
      :gt ->
        excess_tax = Decimal.mult(Decimal.sub(o, half_earned_salary), Decimal.new("0.1"))

        Decimal.round(
          Decimal.add(excess_tax, Decimal.mult(half_earned_salary, Decimal.new("0.05"))),
          2
        )

      _ ->
        Decimal.round(Decimal.mult(o, Decimal.new("0.05")), 2)
    end
  end

  defp compute_tax(income, [[tax_bracket, rate, cum_tax] | t], prev_tax_bracket) do
    if Decimal.compare(income, tax_bracket) != :gt do
      Decimal.round(
        Decimal.add(
          Decimal.mult(
            Decimal.sub(income, prev_tax_bracket),
            rate
          ),
          cum_tax
        ),
        2
      )
    else
      compute_tax(income, t, tax_bracket)
    end
  end

  defp gra_income_tax(taxable_income, 2022) do
    tax_table = [
      # [Chargeable income, % rate, cumulative tax]
      [Decimal.new(365), Decimal.new(0), Decimal.new(0)],
      [Decimal.new(475), Decimal.new("0.050"), Decimal.new(0)],
      [Decimal.new(605), Decimal.new("0.100"), Decimal.new("5.5")],
      [Decimal.new(3605), Decimal.new("0.175"), Decimal.new("18.5")],
      [Decimal.new(20000), Decimal.new("0.250"), Decimal.new("543.5")],
      [%Decimal{coef: :inf}, Decimal.new("0.300"), Decimal.new("4642.25")]
    ]

    compute_tax(taxable_income, tax_table, Decimal.new(0))
  end

  defp gra_income_tax(taxable_income, 2020) do
    tax_table = [
      # [Chargeable income, % rate, cumulative tax]
      [Decimal.new(319), Decimal.new(0), Decimal.new(0)],
      [Decimal.new(419), Decimal.new("0.050"), Decimal.new(0)],
      [Decimal.new(539), Decimal.new("0.100"), Decimal.new(5)],
      [Decimal.new(3539), Decimal.new("0.175"), Decimal.new(17)],
      [Decimal.new(20000), Decimal.new("0.250"), Decimal.new(542)],
      [%Decimal{coef: :inf}, Decimal.new("0.300"), Decimal.new("4657.25")]
    ]

    compute_tax(taxable_income, tax_table, Decimal.new(0))
  end

  defp gra_income_tax(taxable_income, 2019) do
    tax_table = [
      [Decimal.new(288), Decimal.new(0), Decimal.new(0)],
      [Decimal.new(388), Decimal.new("0.050"), Decimal.new(0)],
      [Decimal.new(528), Decimal.new("0.100"), Decimal.new(5)],
      [Decimal.new(3528), Decimal.new("0.175"), Decimal.new(19)],
      [Decimal.new(20000), Decimal.new("0.250"), Decimal.new(544)],
      [%Decimal{coef: :inf}, Decimal.new("0.300"), Decimal.new(4662)]
    ]

    compute_tax(taxable_income, tax_table, Decimal.new(0))
  end

  defp gra_income_tax(taxable_income, _) do
    tax_table = [
      [Decimal.new(216), Decimal.new(0), Decimal.new(0)],
      [Decimal.new(331), Decimal.new("0.050"), Decimal.new(0)],
      [Decimal.new(431), Decimal.new("0.100"), Decimal.new("3.5")],
      [Decimal.new(3241), Decimal.new("0.175"), Decimal.new("13.5")],
      [Decimal.new(10000), Decimal.new("0.250"), Decimal.new("505.25")],
      [%Decimal{coef: :inf}, Decimal.new("0.350"), Decimal.new(2195)]
    ]

    compute_tax(taxable_income, tax_table, Decimal.new(0))
  end

  defp parse_ssnit_ded(_, "E0053"), do: Decimal.new(5)
  defp parse_ssnit_ded("5.50", _), do: Decimal.new("5.5")
  defp parse_ssnit_ded("0.00", _), do: Decimal.new(0)
  defp parse_ssnit_ded("N", _), do: Decimal.new(0)
  defp parse_ssnit_ded("Y", "E0053"), do: Decimal.new(5)
  defp parse_ssnit_ded("Y", _), do: Decimal.new("5.5")

  defp parse_dept("M001"), do: "ADMINSTRATION"
  defp parse_dept("M002"), do: "FACTORY"
  defp parse_dept(_), do: ""

  defp parse_sub_dept("S001"), do: "DRIVERS"
  defp parse_sub_dept("S002"), do: "MARKETING"
  defp parse_sub_dept("S003"), do: "SECURITY"
  defp parse_sub_dept("S004"), do: "STORES"
  defp parse_sub_dept("S005"), do: "WORKERS"
  defp parse_sub_dept("S006"), do: "QUALITY CONTROL"
  defp parse_sub_dept("S007"), do: "MAINTENANCE"
  defp parse_sub_dept("S008"), do: "OTHERS"
  defp parse_sub_dept(_), do: ""

  defp parse_terminated("M"), do: false
  defp parse_terminated("X"), do: true
  defp parse_terminated("D"), do: true

  defp parse_cash("C"), do: true
  defp parse_cash("B"), do: false

  defp parse_tuc_ded("2.00"), do: Decimal.new(2)
  defp parse_tuc_ded("0.00"), do: Decimal.new(0)
  defp parse_tuc_ded("Y"), do: Decimal.new(2)
  defp parse_tuc_ded("N"), do: Decimal.new(0)

  defp parse_date(""), do: nil
  defp parse_date(date), do: to_date(date)
end

defmodule Mgp.Sync.ImportPayroll do
  @moduledoc "Import and check payroll data"

  import Mgp.Utils, only: [to_date: 1, to_time: 1]
  alias Mgp.Sync.DbaseParser

  @root_folder "/home/hvaria/backup/HPMG22/"
  @expat_salary "/home/hvaria/backup/salary.csv"
  @employee_master "H1EMP.DBF"
  @calculated_payroll "H1DETPAY.DBF"

  @decimal_50 Decimal.new("50")

  def get_management_payroll(month), do: get_management_pay(determine_management_month(month))
  defp get_management_pay(nil), do: []

  defp get_management_pay(m) do
    File.read!(@expat_salary)
    |> String.trim()
    |> String.split("\n")
    |> Enum.drop(1)
    |> Enum.map(fn x ->
      [
        month,
        id,
        tin_no,
        name,
        position,
        salary,
        living_percent,
        vehicle,
        non_cash_percent,
        ssnit_ded,
        ssnit_no
      ] = String.split(x, ",")

      if month === m do
        earned_salary = salary
        tax_year = 2020

        {ssnit_amount, ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2} =
          ssnit_computations(tax_year, id, earned_salary, ssnit_ded)

        living = Decimal.mult(earned_salary, string_to_percent(living_percent))
        non_cash = Decimal.mult(earned_salary, string_to_percent(non_cash_percent))
        total_additions = Decimal.add(Decimal.add(living, non_cash), vehicle)
        taxable_income = Decimal.sub(Decimal.add(earned_salary, total_additions), ssnit_amount)
        tax_ded = gra_income_tax(taxable_income, determine_tax_year(month))

        %{
          month: month,
          id: id,
          tin_no: tin_no,
          ssnit_no: ssnit_no,
          name: name,
          gra_category: position,
          base_salary: earned_salary,
          earned_salary: String.to_integer(earned_salary),
          cash_allowance: 0,
          total_cash: earned_salary,
          ssnit_ded: string_to_percent(ssnit_ded),
          ssnit_amount: ssnit_amount,
          ssnit_emp_contrib: ssnit_emp_contrib,
          ssnit_total: ssnit_total,
          ssnit_tier_1: ssnit_tier_1,
          ssnit_tier_2: ssnit_tier_2,
          pf_amount: 0,
          living_percent: string_to_percent(living_percent),
          living: living,
          vehicle: vehicle,
          non_cash_percent: string_to_percent(non_cash_percent),
          non_cash: non_cash,
          total_additions: total_additions,
          total_gra_income: Decimal.add(earned_salary, total_additions),
          total_relief: ssnit_amount,
          taxable_income: taxable_income,
          tax_ded: tax_ded,
          overtime_earned: 0,
          overtime_tax: 0,
          total_tax: tax_ded,
          net_pay: Decimal.sub(Decimal.sub(earned_salary, tax_ded), ssnit_amount)
        }
      else
        []
      end
    end)
    |> :lists.flatten()
  end

  defp string_to_percent(t), do: Decimal.div(Decimal.new(t), 100)

  def determine_management_month(month) do
    File.read!(@expat_salary)
    |> String.trim()
    |> String.split("\n")
    |> Enum.drop(1)
    |> Enum.map(fn x -> hd(String.split(x, ",")) end)
    |> Enum.uniq()
    |> Enum.find(fn x -> x <= month end)
  end

  def import_month(folder \\ @root_folder, month) do
    files = generate_file_paths(folder)

    with employees <- parse_employee_master(files.employee_master),
         payroll <- parse_and_calculate_monthly_payroll(files.payroll, month, employees) do
      Enum.sort_by(payroll, fn x -> x.id end)
    end
  end

  defp generate_file_paths(folder) do
    %{
      payroll: folder <> @calculated_payroll,
      employee_master: folder <> @employee_master
    }
  end

  def parse_employee_master(dbf_file) do
    DbaseParser.parse(
      dbf_file,
      [
        "EMP_NO",
        "EMP_NM",
        "EMP_JOINDT",
        "EMP_DISCDT",
        "EMP_RTBASE",
        "EMP_SSNO",
        "EMP_FNO1",
        "EMP_FNO2",
        "EMP_FNO3"
      ],
      fn x ->
        %{
          id: x["EMP_NO"],
          name: x["EMP_NM"],
          start_date: parse_date(x["EMP_JOINDT"]),
          end_date: parse_date(x["EMP_DISCDT"]),
          is_terminated: parse_terminated(x["EMP_RTBASE"]),
          ssnit_no: x["EMP_SSNO"],
          tin_no: x["EMP_FNO1"],
          ssnit_t2: x["EMP_FNO2"],
          pf_no: x["EMP_FNO3"]
        }
      end
    )
    |> Enum.reduce(%{}, fn x, acc ->
      Map.put(acc, x.id, x)
    end)
  end

  defp determine_tax_year(month) do
    cond do
      month > "2112M" -> 2022
      month > "1912M" -> 2020
      month > "1812M" -> 2019
      month <= "1812M" -> 2018
    end
  end

  defp parse_and_calculate_monthly_payroll(dbf_file, month, employees) do
    tax_year = determine_tax_year(month)

    DbaseParser.parse(
      dbf_file,
      [
        "PD_MTH",
        "PD_EMPNO",
        "PD_RATE",
        "PD_DEPT",
        "PD_SDEPT",
        "PD_DESIG",
        "PD_CAT",
        "PD_OTAMT",
        "PD_DAYS",
        "PD_OTHALL",
        "PD_LOAN",
        "PD_ADV",
        "PD_PLADV",
        "PD_SSRT",
        "PD_TUCRT",
        "PD_DEDRT1",
        "PD_DEDRT2",
        "PD_TOTAL",
        "PD_CB",
        "PD_BKDET",
        "PD_LMU",
        "PD_LMD",
        "PD_LMT"
      ],
      fn x ->
        if x["PD_MTH"] === month do
          %{
            month: x["PD_MTH"],
            id: x["PD_EMPNO"],
            base_salary: x["PD_RATE"],
            dept: parse_dept(x["PD_DEPT"]),
            sub_dept: parse_sub_dept(x["PD_SDEPT"]),
            title: x["PD_DESIG"],
            gra_category: x["PD_CAT"],
            overtime_earned: x["PD_OTAMT"],
            days_worked: x["PD_DAYS"],
            cash_allowance: x["PD_OTHALL"],
            loan: x["PD_LOAN"],
            advance: x["PD_ADV"],
            pvt_loan: x["PD_PLADV"],
            ssnit_ded: parse_ssnit_ded(Decimal.to_string(x["PD_SSRT"]), x["PD_EMPNO"]),
            tuc_ded: parse_tuc_ded(Decimal.to_string(x["PD_TUCRT"])),
            staff_welfare_ded: x["PD_DEDRT1"],
            pf_ded: x["PD_DEDRT2"],
            net_pay: x["PD_TOTAL"],
            is_cash: parse_cash(x["PD_CB"]),
            bank: x["PD_BKDET"],
            lmu: x["PD_LMU"],
            lmd: to_date(x["PD_LMD"]),
            lmt: to_time(x["PD_LMT"])
          }
        else
          nil
        end
      end
    )
    |> Task.async_stream(&parse_payroll_record(&1, employees, tax_year))
    |> Stream.map(fn {:ok, res} -> res end)
    # |> Stream.map(&parse_payroll_record(&1))
    |> Enum.to_list()
  end

  ### Private Functions ###
  defp parse_payroll_record(emp, employees, tax_year) do
    earned_salary =
      Decimal.round(Decimal.mult(emp.base_salary, Decimal.div(emp.days_worked, 27)), 2)

    {ssnit_amount, ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2} =
      ssnit_computations(tax_year, emp.id, earned_salary, emp.ssnit_ded)

    pf_amount = Decimal.round(Decimal.mult(Decimal.div(emp.pf_ded, 100), earned_salary), 2)

    total_cash = Decimal.add(earned_salary, emp.cash_allowance)

    total_relief = Decimal.add(ssnit_amount, pf_amount)

    taxable_income = Decimal.sub(total_cash, total_relief)

    tax_ded = gra_income_tax(taxable_income, tax_year)

    overtime_tax = gra_overtime_tax(emp.overtime_earned, emp.base_salary)

    total_tax = Decimal.add(tax_ded, overtime_tax)

    tuc_amount = Decimal.round(Decimal.mult(Decimal.div(emp.tuc_ded, 100), earned_salary), 2)

    total_ded =
      Decimal.add(
        emp.advance,
        Decimal.add(
          emp.loan,
          Decimal.add(
            emp.pvt_loan,
            Decimal.add(emp.staff_welfare_ded, tuc_amount)
          )
        )
      )

    total_pay = Decimal.sub(taxable_income, Decimal.add(total_tax, total_ded))

    Map.merge(
      emp,
      Map.merge(
        employees[emp.id],
        %{
          earned_salary: earned_salary,
          ssnit_emp_contrib: ssnit_emp_contrib,
          ssnit_amount: ssnit_amount,
          ssnit_total: ssnit_total,
          ssnit_tier_1: ssnit_tier_1,
          ssnit_tier_2: ssnit_tier_2,
          pf_amount: pf_amount,
          taxable_income: taxable_income,
          total_cash: total_cash,
          total_relief: total_relief,
          tax_ded: tax_ded,
          overtime_tax: overtime_tax,
          total_tax: total_tax,
          tuc_amount: tuc_amount,
          total_ded: total_ded,
          total_pay: total_pay
        }
      )
    )
  end

  defp ssnit_computations(tax_year, emp_id, earned_salary, ssnit_ded) do
    ssnit_amount =
      if emp_id === "E0053" do
        if tax_year < 2020 do
          Decimal.round(Decimal.mult(Decimal.div(ssnit_ded, 100), earned_salary), 2)
        else
          Decimal.new(0)
        end
      else
        Decimal.round(Decimal.mult(Decimal.div(ssnit_ded, 100), earned_salary), 2)
      end

    {ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2} =
      case emp_id === "E0053" do
        true ->
          if tax_year < 2020 do
            # 12.5 div 5
            ssnit_emp_contrib =
              Decimal.round(Decimal.mult(Decimal.from_float(2.5), ssnit_amount), 2)

            ssnit_total = Decimal.add(ssnit_amount, ssnit_emp_contrib)
            ssnit_tier_1 = ssnit_total
            ssnit_tier_2 = Decimal.new(0)
            {ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2}
          else
            ssnit_emp_contrib = Decimal.new(0)
            ssnit_total = Decimal.new(0)
            ssnit_tier_1 = Decimal.new(0)
            ssnit_tier_2 = Decimal.new(0)
            {ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2}
          end

        false ->
          # 13 div 5.5
          ssnit_emp_contrib =
            Decimal.round(Decimal.mult(Decimal.from_float(2.363636364), ssnit_amount), 2)

          ssnit_total = Decimal.add(ssnit_amount, ssnit_emp_contrib)

          ssnit_tier_1 =
            Decimal.round(Decimal.mult(Decimal.from_float(0.72972973), ssnit_total), 2)

          ssnit_tier_2 = Decimal.sub(ssnit_total, ssnit_tier_1)
          {ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2}
      end

    {ssnit_amount, ssnit_emp_contrib, ssnit_total, ssnit_tier_1, ssnit_tier_2}
  end

  defp gra_overtime_tax(o, earned_salary) do
    percent = Decimal.mult(Decimal.div(o, earned_salary), 100)

    case Decimal.compare(percent, @decimal_50) do
      :gt ->
        Decimal.round(Decimal.mult(o, Decimal.new("0.1")), 2)

      _ ->
        Decimal.round(Decimal.mult(o, Decimal.new("0.05")), 2)
    end
  end

  defp compute_tax(income, [[tax_bracket, rate, cum_tax] | t], prev_tax_bracket) do
    if Decimal.compare(income, tax_bracket) != :gt do
      Decimal.round(
        Decimal.add(
          Decimal.mult(
            Decimal.sub(income, prev_tax_bracket),
            rate
          ),
          cum_tax
        ),
        2
      )
    else
      compute_tax(income, t, tax_bracket)
    end
  end

  defp gra_income_tax(taxable_income, 2022) do
    tax_table = [
      # [Chargeable income, % rate, cumulative tax]
      [Decimal.new(365), Decimal.new(0), Decimal.new(0)],
      [Decimal.new(475), Decimal.new("0.050"), Decimal.new(0)],
      [Decimal.new(605), Decimal.new("0.100"), Decimal.new("5.5")],
      [Decimal.new(3605), Decimal.new("0.175"), Decimal.new("18.5")],
      [Decimal.new(20000), Decimal.new("0.250"), Decimal.new("543.5")],
      [%Decimal{coef: :inf}, Decimal.new("0.300"), Decimal.new("4642.25")]
    ]

    compute_tax(taxable_income, tax_table, Decimal.new(0))
  end

  defp gra_income_tax(taxable_income, 2020) do
    tax_table = [
      # [Chargeable income, % rate, cumulative tax]
      [Decimal.new(319), Decimal.new(0), Decimal.new(0)],
      [Decimal.new(419), Decimal.new("0.050"), Decimal.new(0)],
      [Decimal.new(539), Decimal.new("0.100"), Decimal.new(5)],
      [Decimal.new(3539), Decimal.new("0.175"), Decimal.new(17)],
      [Decimal.new(20000), Decimal.new("0.250"), Decimal.new(542)],
      [%Decimal{coef: :inf}, Decimal.new("0.300"), Decimal.new("4657.25")]
    ]

    compute_tax(taxable_income, tax_table, Decimal.new(0))
  end

  defp gra_income_tax(taxable_income, 2019) do
    tax_table = [
      [Decimal.new(288), Decimal.new(0), Decimal.new(0)],
      [Decimal.new(388), Decimal.new("0.050"), Decimal.new(0)],
      [Decimal.new(528), Decimal.new("0.100"), Decimal.new(5)],
      [Decimal.new(3528), Decimal.new("0.175"), Decimal.new(19)],
      [Decimal.new(20000), Decimal.new("0.250"), Decimal.new(544)],
      [%Decimal{coef: :inf}, Decimal.new("0.300"), Decimal.new(4662)]
    ]

    compute_tax(taxable_income, tax_table, Decimal.new(0))
  end

  defp gra_income_tax(taxable_income, _) do
    tax_table = [
      [Decimal.new(216), Decimal.new(0), Decimal.new(0)],
      [Decimal.new(331), Decimal.new("0.050"), Decimal.new(0)],
      [Decimal.new(431), Decimal.new("0.100"), Decimal.new("3.5")],
      [Decimal.new(3241), Decimal.new("0.175"), Decimal.new("13.5")],
      [Decimal.new(10000), Decimal.new("0.250"), Decimal.new("505.25")],
      [%Decimal{coef: :inf}, Decimal.new("0.350"), Decimal.new(2195)]
    ]

    compute_tax(taxable_income, tax_table, Decimal.new(0))
  end

  defp parse_ssnit_ded(_, "E0053"), do: Decimal.new(5)
  defp parse_ssnit_ded("5.50", _), do: Decimal.new("5.5")
  defp parse_ssnit_ded("0.00", _), do: Decimal.new(0)
  defp parse_ssnit_ded("N", _), do: Decimal.new(0)
  defp parse_ssnit_ded("Y", "E0053"), do: Decimal.new(5)
  defp parse_ssnit_ded("Y", _), do: Decimal.new("5.5")

  defp parse_dept("M001"), do: "ADMINSTRATION"
  defp parse_dept("M002"), do: "FACTORY"
  defp parse_dept(_), do: ""

  defp parse_sub_dept("S001"), do: "DRIVERS"
  defp parse_sub_dept("S002"), do: "MARKETING"
  defp parse_sub_dept("S003"), do: "SECURITY"
  defp parse_sub_dept("S004"), do: "STORES"
  defp parse_sub_dept("S005"), do: "WORKERS"
  defp parse_sub_dept("S006"), do: "QUALITY CONTROL"
  defp parse_sub_dept("S007"), do: "MAINTENANCE"
  defp parse_sub_dept("S008"), do: "OTHERS"
  defp parse_sub_dept(_), do: ""

  defp parse_terminated("M"), do: false
  defp parse_terminated("X"), do: true
  defp parse_terminated("D"), do: true

  defp parse_cash("C"), do: true
  defp parse_cash("B"), do: false

  defp parse_tuc_ded("2.00"), do: Decimal.new(2)
  defp parse_tuc_ded("0.00"), do: Decimal.new(0)
  defp parse_tuc_ded("Y"), do: Decimal.new(2)
  defp parse_tuc_ded("N"), do: Decimal.new(0)

  defp parse_date(""), do: nil
  defp parse_date(date), do: to_date(date)
end


remove redundant Supervisor.Spec import

remove redundant Supervisor.Spec import

defmodule Phoenix.Endpoint.Server do
  # The supervisor for the underlying handlers.
  @moduledoc false
  @handler Phoenix.Endpoint.CowboyHandler

  use Supervisor
  require Logger

  def start_link(otp_app, endpoint, opts \\ []) do
    Supervisor.start_link(__MODULE__, {otp_app, endpoint}, opts)
  end

  def init({otp_app, endpoint}) do
    children = []

    if config = endpoint.config(:http) do
      children =
        [@handler.child_spec(:http, endpoint, default(config, otp_app, 4000))|children]
    end

    if config = endpoint.config(:https) do
      {:ok, _} = Application.ensure_all_started(:ssl)
      children =
        [@handler.child_spec(:https, endpoint, default(config, otp_app, 4040))|children]
    end

    supervise(children, strategy: :one_for_one)
  end

  defp default(config, otp_app, port) do
    config =
      config
      |> Keyword.put_new(:otp_app, otp_app)
      |> Keyword.put_new(:port, port)

    Keyword.put(config, :port, to_port(config[:port]))
  end

  defp to_port(nil) do
    Logger.error "Server can't start because :port in config is nil, please use a valid port number"
    exit(:shutdown)
  end
  defp to_port(binary)  when is_binary(binary),   do: String.to_integer(binary)
  defp to_port(integer) when is_integer(integer), do: integer
  defp to_port({:system, env_var}), do: to_port(System.get_env(env_var))
end

defmodule Phoenix.Endpoint.Server do
  # The supervisor for the underlying handlers.
  @moduledoc false
  @handler Phoenix.Endpoint.CowboyHandler

  use Supervisor
  require Logger

  def start_link(otp_app, endpoint, opts \\ []) do
    Supervisor.start_link(__MODULE__, {otp_app, endpoint}, opts)
  end

  def init({otp_app, endpoint}) do
    import Supervisor.Spec

    children = []

    if config = endpoint.config(:http) do
      children =
        [@handler.child_spec(:http, endpoint, default(config, otp_app, 4000))|children]
    end

    if config = endpoint.config(:https) do
      {:ok, _} = Application.ensure_all_started(:ssl)
      children =
        [@handler.child_spec(:https, endpoint, default(config, otp_app, 4040))|children]
    end

    supervise(children, strategy: :one_for_one)
  end

  defp default(config, otp_app, port) do
    config =
      config
      |> Keyword.put_new(:otp_app, otp_app)
      |> Keyword.put_new(:port, port)

    Keyword.put(config, :port, to_port(config[:port]))
  end

  defp to_port(nil) do
    Logger.error "Server can't start because :port in config is nil, please use a valid port number"
    exit(:shutdown)
  end
  defp to_port(binary)  when is_binary(binary),   do: String.to_integer(binary)
  defp to_port(integer) when is_integer(integer), do: integer
  defp to_port({:system, env_var}), do: to_port(System.get_env(env_var))
end


Gracefully stop the server on EOF (Ctrl+D)

defmodule Serum.DevServer.Looper do
  @moduledoc """
  A looper which accepts user inputs as server commands and processes them.
  """

  import Serum.Util
  alias Serum.DevServer.Service

  @doc "Starts the infinite command prompt loop."
  @spec looper() :: no_return()
  def looper do
    IO.write("#{Service.port()}> ")

    case IO.gets("") do
      :eof ->
        run_command("quit", [Service.site_dir()])

      command when is_binary(command) ->
        run_command(String.trim(command), [Service.site_dir()])
        looper()
    end
  end

  @spec run_command(binary(), list()) :: :ok | no_return()
  def run_command(command, args)
  def run_command("", _), do: :ok

  def run_command("help", _) do
    """
    Available commands are:
      help   Displays this help message
      build  Rebuilds the project
      quit   Stops the server and quit
    """
    |> IO.write()
  end

  def run_command("build", _) do
    Service.rebuild()
  end

  def run_command("quit", [site_dir]) do
    IO.puts("Removing temporary directory \"#{site_dir}\"...")
    File.rm_rf!(site_dir)
    IO.puts("Shutting down...")
    System.halt(0)
  end

  def run_command(_, _) do
    warn("Type \"help\" for the list of available commands.")
  end
end

defmodule Serum.DevServer.Looper do
  @moduledoc """
  A looper which accepts user inputs as server commands and processes them.
  """

  import Serum.Util
  alias Serum.DevServer.Service

  @doc "Starts the infinite command prompt loop."
  @spec looper() :: no_return()
  def looper do
    IO.write("#{Service.port()}> ")
    command = "" |> IO.gets() |> String.trim()

    run_command(command, [Service.site_dir()])
    looper()
  end

  @spec run_command(binary(), list()) :: :ok | no_return()
  def run_command(command, args)
  def run_command("", _), do: :ok

  def run_command("help", _) do
    """
    Available commands are:
      help   Displays this help message
      build  Rebuilds the project
      quit   Stops the server and quit
    """
    |> IO.write()
  end

  def run_command("build", _) do
    Service.rebuild()
  end

  def run_command("quit", [site_dir]) do
    IO.puts("Removing temporary directory \"#{site_dir}\"...")
    File.rm_rf!(site_dir)
    IO.puts("Shutting down...")
    System.halt(0)
  end

  def run_command(_, _) do
    warn("Type \"help\" for the list of available commands.")
  end
end


use right variable

use right variable

defmodule Core.TypeformController do
  use Core.Web, :controller

  def submit_event(conn, %{"form_response" => %{"answers" => answers, "definition" => definition}}) do
    questions =
      definition["fields"]
      |> Enum.map(&field_name/1)

    responses =
      answers
      |> Enum.map(&get_answer/1)

    together = questions |> Enum.zip(responses) |> Enum.into(%{})

    names = String.split(together["host_name"], " ")
    first_name = List.first(names)
    last_name = if length(names) > 1 do
      List.last(names)
    else
      ""
    end

    person = %{
      email: together["host_email"],
      phone: together["host_phone"],
      first_name: first_name,
      last_name: last_name
    }

    IO.puts "Inspecting person"
    IO.inspect person

    # TODO - NB create person

    event = %{
      contact: %{
        name: together["host_name"],
        phone: together["host_phone"],
        email: together["host_email"]
      },
      rsvp_form: %{
        phone: "optional",
        address: "optional",
        accept_rsvps: true,
        gather_volunteers: true,
        allow_guests: true
      },
      venue: %{
        name: together["venue_name"],
        address: %{
          address1: "venue_address",
          city: "venue_city",
          state: "venue_state"
        }
      }
    }

    IO.puts "Inspecting event"
    IO.inspect event
  end

  defp field_name(%{"title" => "What's your name?"}), do: "host_name"
  defp field_name(%{"title" => "What's your email?"}), do: "host_email"
  defp field_name(%{"title" => "What's your phone number?"}), do: "host_phone"
  defp field_name(%{"title" => "When will it be?"}), do: "start_time"
  defp field_name(%{"title" => "When will it start?"}), do: "start_time"
  defp field_name(%{"title" => "When will it end?"}), do: "end_time"
  defp field_name(%{"title" => "What should we call it?"}), do: "event_name"
  defp field_name(%{"title" => "Give us a little description..."}), do: "intro"
  defp field_name(%{"title" => "What is the place called?"}), do: "venue_name"
  defp field_name(%{"title" => "What's the address?"}), do: "venue_address"
  defp field_name(%{"title" => "What city is it in?"}), do: "venue_city"
  defp field_name(%{"title" => "What state is it in?"}), do: "venue_state"
  defp field_name(%{"title" => "Can we share the address of the event on our map?"}), do: "sharing_permission"
  defp field_name(%{"title" => "What's your zip code?"}), do: "venue_zip"

  defp get_answer(%{"text" => val}), do: val
  defp get_answer(%{"email" => val}), do: val
  defp get_answer(%{"date" => val}), do: val
  defp get_answer(%{"choice" => %{"label" => val}}), do: val
  defp get_answer(%{"boolean" => val}), do: val
  defp get_answer(%{"number" => val}), do: val

end

defmodule Core.TypeformController do
  use Core.Web, :controller

  def submit_event(conn, %{"form_response" => %{"answers" => answers, "definition" => definition}}) do
    questions =
      definition["fields"]
      |> Enum.map(&field_name/1)

    responses =
      answers
      |> Enum.map(&get_answer/1)

    together = questions |> Enum.zip(answers) |> Enum.into(%{})

    names = String.split(together["host_name"], " ")
    first_name = List.first(names)
    last_name = if length(names) > 1 do
      List.last(names)
    else
      ""
    end

    person = %{
      email: together["host_email"],
      phone: together["host_phone"],
      first_name: first_name,
      last_name: last_name
    }

    IO.puts "Inspecting person"
    IO.inspect person

    # TODO - NB create person

    event = %{
      contact: %{
        name: together["host_name"],
        phone: together["host_phone"],
        email: together["host_email"]
      },
      rsvp_form: %{
        phone: "optional",
        address: "optional",
        accept_rsvps: true,
        gather_volunteers: true,
        allow_guests: true
      },
      venue: %{
        name: together["venue_name"],
        address: %{
          address1: "venue_address",
          city: "venue_city",
          state: "venue_state"
        }
      }
    }

    IO.puts "Inspecting event"
    IO.inspect event
  end

  defp field_name(%{"title" => "What's your name?"}), do: "host_name"
  defp field_name(%{"title" => "What's your email?"}), do: "host_email"
  defp field_name(%{"title" => "What's your phone number?"}), do: "host_phone"
  defp field_name(%{"title" => "When will it be?"}), do: "start_time"
  defp field_name(%{"title" => "When will it start?"}), do: "start_time"
  defp field_name(%{"title" => "When will it end?"}), do: "end_time"
  defp field_name(%{"title" => "What should we call it?"}), do: "event_name"
  defp field_name(%{"title" => "Give us a little description..."}), do: "intro"
  defp field_name(%{"title" => "What is the place called?"}), do: "venue_name"
  defp field_name(%{"title" => "What's the address?"}), do: "venue_address"
  defp field_name(%{"title" => "What city is it in?"}), do: "venue_city"
  defp field_name(%{"title" => "What state is it in?"}), do: "venue_state"
  defp field_name(%{"title" => "Can we share the address of the event on our map?"}), do: "sharing_permission"
  defp field_name(%{"title" => "What's your zip code?"}), do: "venue_zip"

  defp get_answer(%{"text" => val}), do: val
  defp get_answer(%{"email" => val}), do: val
  defp get_answer(%{"date" => val}), do: val
  defp get_answer(%{"choice" => %{"label" => val}}), do: val
  defp get_answer(%{"boolean" => val}), do: val
  defp get_answer(%{"number" => val}), do: val

end


fix for Bearer login

fix for Bearer login

defmodule Peepchat.Router do
  use Peepchat.Web, :router

  pipeline :api do
    plug :accepts, ["json", "json-api"]
  end

  pipeline :api_auth do
    plug :accepts, ["json", "json-api"]
    plug Guardian.Plug.VerifyHeader, realm: "Bearer"
    plug Guardian.Plug.LoadResource
  end

  scope "/api", Peepchat do
    pipe_through :api
    post "/register", RegistrationController, :create
    post "/token", SessionController, :create, as: :login
  end

  scope "/api", Peepchat do
    pipe_through :api_auth
    get "/user/current" ,UserController, :current
  end
end

defmodule Peepchat.Router do
  use Peepchat.Web, :router

  pipeline :api do
    plug :accepts, ["json", "json-api"]
  end

  pipeline :api_auth do
    plug :accepts, ["json", "json-api"]
    plug Guardian.Plug.VerifyHeader
    plug Guardian.Plug.LoadResource
  end

  scope "/api", Peepchat do
    pipe_through :api
    post "/register", RegistrationController, :create
    post "/token", SessionController, :create, as: :login
  end

  scope "/api", Peepchat do
    pipe_through :api_auth
    get "/user/current" ,UserController, :current
  end
end


Reset stability timer at 28 minutes, not 1.5 seconds lol


Bump agent

Bump agent

defmodule Appsignal.Agent do
  def version, do: "c55fb2c"

  def triples do
    %{
      "x86_64-darwin" => %{
        checksum: "fd287b7efa38d821331d75f4a8d4dd0585f1fc01993406e2c5ebf224d13ac178",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/c55fb2c/appsignal-x86_64-darwin-all-static.tar.gz"
      },
      "universal-darwin" => %{
        checksum: "fd287b7efa38d821331d75f4a8d4dd0585f1fc01993406e2c5ebf224d13ac178",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/c55fb2c/appsignal-x86_64-darwin-all-static.tar.gz"
      },
      "i686-linux" => %{
        checksum: "c6c0a54524d1b4483aee03677e32969ca896d1a5561e12d9cb3e45fb5c2deeb6",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/c55fb2c/appsignal-i686-linux-all-static.tar.gz"
      },
      "x86-linux" => %{
        checksum: "c6c0a54524d1b4483aee03677e32969ca896d1a5561e12d9cb3e45fb5c2deeb6",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/c55fb2c/appsignal-i686-linux-all-static.tar.gz"
      },
      "x86_64-linux" => %{
        checksum: "b60e7514677a0622dbd093b75a353a547661734202dbc76c36842848f90f0461",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/c55fb2c/appsignal-x86_64-linux-all-static.tar.gz"
      },
      "x86_64-linux-musl" => %{
        checksum: "69dd6789e1e13177e2e9827a6f1a93d7557cc66d3f4f58a843d46f6d7741eab9",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/c55fb2c/appsignal-x86_64-linux-musl-all-static.tar.gz"
      },
      "x86_64-freebsd" => %{
        checksum: "6a669fe6a894558c83c780f32fb886f4100e1775dcfd490523420c569f315416",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/c55fb2c/appsignal-x86_64-freebsd-all-static.tar.gz"
      },
      "amd64-freebsd" => %{
        checksum: "6a669fe6a894558c83c780f32fb886f4100e1775dcfd490523420c569f315416",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/c55fb2c/appsignal-x86_64-freebsd-all-static.tar.gz"
      },
    }
  end
end

defmodule Appsignal.Agent do
  def version, do: "f9d2b57"

  def triples do
    %{
      "x86_64-darwin" => %{
        checksum: "96b144b211525c83e29dc4c337f4addee3f168ffd82de7256f551d30c538445c",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/f9d2b57/appsignal-x86_64-darwin-all-static.tar.gz"
      },
      "universal-darwin" => %{
        checksum: "96b144b211525c83e29dc4c337f4addee3f168ffd82de7256f551d30c538445c",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/f9d2b57/appsignal-x86_64-darwin-all-static.tar.gz"
      },
      "i686-linux" => %{
        checksum: "0f58d7ab6c430c42d4b2b6ca31a029654a79022f4752f053364e9862efcce43f",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/f9d2b57/appsignal-i686-linux-all-static.tar.gz"
      },
      "x86-linux" => %{
        checksum: "0f58d7ab6c430c42d4b2b6ca31a029654a79022f4752f053364e9862efcce43f",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/f9d2b57/appsignal-i686-linux-all-static.tar.gz"
      },
      "x86_64-linux" => %{
        checksum: "131d8eac0bb9b0640f670834b136fbea581d9f96a31299e18ca4b7450a95df6d",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/f9d2b57/appsignal-x86_64-linux-all-static.tar.gz"
      },
      "x86_64-linux-musl" => %{
        checksum: "c8f5e866b7c9786fc24e198125e71239ef0cffe829dcda9a40c8aab5205db3d0",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/f9d2b57/appsignal-x86_64-linux-musl-all-static.tar.gz"
      },
      "x86_64-freebsd" => %{
        checksum: "9c30322a2685ac246380cc4dab6e24b2fd3d7a42fffaf12c901e2c2a9e7bc741",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/f9d2b57/appsignal-x86_64-freebsd-all-static.tar.gz"
      },
      "amd64-freebsd" => %{
        checksum: "9c30322a2685ac246380cc4dab6e24b2fd3d7a42fffaf12c901e2c2a9e7bc741",
        download_url: "https://appsignal-agent-releases.global.ssl.fastly.net/f9d2b57/appsignal-x86_64-freebsd-all-static.tar.gz"
      },
    }
  end
end


process: fix a code of supervisor

process: fix a code of supervisor

IO.puts "## Base"
send self(), {:hello, "world"}

IO.inspect(receive do
  {:hello, msg} -> msg
  {:world, _} -> "won't match"
end)
IO.puts ""

IO.puts "## Config Process"
defmodule KV do
  def start_link do
    {:ok, spawn_link(fn -> loop(%{}) end)}
  end

  defp loop(map) do
    receive do
      {:get, key, caller} ->
        send caller, Map.get(map, key)
        loop(map)
      {:put, key, value} ->
        loop(Map.put(map, key, value))
    end
  end
end

IO.inspect {:ok, pid} = KV.start_link
IO.inspect send pid, {:put, :hello, :world}
IO.inspect send pid, {:get, :hello, self()}

receive do
  value -> IO.inspect value
end
IO.puts ""

IO.puts "## OTP: GenServer"
defmodule KV2 do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, %{})
  end

  def get(pid, key) do
    GenServer.call(pid, {:get, key})
  end

  def put(pid, key, value) do
    GenServer.call(pid, {:put, key, value})
  end

  def handle_call({:get, key}, _from, state) do
    {:reply, Map.get(state, key), state}
  end

  def handle_call({:put, key, value}, _from, state) do
    {:reply, value, Map.put(state, key, value)}
  end
end
IO.inspect {:ok, pid} = KV2.start_link
IO.inspect KV2.get(pid, :a)
IO.inspect KV2.put(pid, :a, 1)
IO.inspect KV2.get(pid, :a)
IO.puts ""

IO.puts "## OTP: Agent"
defmodule KV3 do
  def start_link do
    Agent.start_link fn -> %{} end
  end

  def get(pid, key) do
    Agent.get pid, fn (map) -> Map.get(map, key) end
  end

  def put(pid, key, value) do
    Agent.update pid, fn (map) -> Map.put(map, key, value) end
  end
end

IO.inspect {:ok, pid} = KV3.start_link
IO.inspect KV3.get(pid, :a)
IO.inspect KV3.put(pid, :a, 1)
IO.inspect KV3.get(pid, :a)
IO.puts ""

IO.puts "## OTP: Task"
defmodule TaskModule do
  def parallel do
    [
      fn -> :timer.sleep(200); IO.puts 1 end,
      fn -> :timer.sleep(400); IO.puts 2 end,
      fn -> :timer.sleep(600); IO.puts 3 end,
      fn -> :timer.sleep(800); IO.puts 4 end
    ]
    |> Enum.map(&Task.async/1)
    |> Enum.map(&Task.await/1)
  end
end
TaskModule.parallel()
IO.puts ""

IO.puts "## Process.monitor"
pid = spawn fn ->
  :timer.sleep(100)
  raise RuntimeError
end
Process.monitor pid

receive do
  {:DOWN, ref, :process, pid, msg} ->
    IO.inspect ref
    IO.inspect pid
    IO.inspect msg
end
IO.puts ""

IO.puts "## OTP: Process.link"
pid = spawn fn ->
  pid = spawn fn ->
    :timer.sleep(500)
    raise RuntimeError
  end
  Process.link pid
  IO.puts "start to link with other process"
  :timer.sleep(2000)
  IO.puts "never display this message"
end
Process.monitor pid
IO.puts "start the linking process"
IO.inspect pid

receive do
  {:DOWN, ref, :process, pid, msg} ->
    IO.puts "killed the linking process"
    IO.inspect pid
    IO.inspect msg
end
IO.puts ""

IO.puts "## OTP: spawn_link"
pid = spawn fn ->
  pid = spawn_link fn ->
    :timer.sleep(500)
    raise RuntimeError
  end
  IO.puts "start to link with other process"
  :timer.sleep(2000)
  IO.puts "never display this message"
end
Process.monitor pid
IO.puts "start the linking process"
IO.inspect pid

receive do
  {:DOWN, ref, :process, pid, msg} ->
    IO.puts "killed the linking process"
    IO.inspect pid
    IO.inspect msg
end
IO.puts ""

IO.puts "## OTP: Supervisor"
defmodule Echo.Server do
  use GenServer

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

  def crash! do
    GenServer.cast(__MODULE__, :crash)
  end

  def handle_cast(:crash, _) do
    raise RuntimeError, message: "Opps!"
  end

  def echo(term) do
    GenServer.call(__MODULE__, {:echo, term})
  end

  def handle_call({:echo, str}, _, s) do
    {:reply, str, s}
  end
end

defmodule SupervisorTest do
  def run do
    import Supervisor.Spec, warn: false

    children = [worker(Echo.Server, [])]
    opts = [
      strategy: :one_for_one,
      name: Echo.Supervisor
    ]
    IO.inspect {:ok, pid} = Supervisor.start_link(children, opts)
    IO.inspect Echo.Server.echo "hey"
    IO.inspect Echo.Server.crash!
    :timer.sleep(100)
    IO.inspect Echo.Server.echo "foo"
  end
end
spawn fn ->
  SupervisorTest.run
end
:timer.sleep(300)

IO.puts "### ModuleBased Supervisor"
defmodule Echo.Supervisor do
  use Supervisor

  def start_link do
    Supervisor.start_link(__MODULE__, [])
  end

  def init([]) do
    children = [
      worker(Echo.Server, [])
    ]
    opts = [
      strategy: :one_for_one,
      name: Echo.Supervisor
    ]
    supervise(children, opts)
  end
end

defmodule ModuleBasedSupervisorTest do
  def run do
    IO.inspect {:ok, pid} = Echo.Supervisor.start_link()
    IO.inspect Echo.Server.echo "hey"
    IO.inspect Echo.Server.crash!
    :timer.sleep(100)
    IO.inspect Echo.Server.echo "foo"
  end
end
spawn fn ->
  ModuleBasedSupervisorTest.run()
end
:timer.sleep(300)
IO.puts ""

IO.puts "## Base"
send self(), {:hello, "world"}

IO.inspect(receive do
  {:hello, msg} -> msg
  {:world, _} -> "won't match"
end)
IO.puts ""

IO.puts "## Config Process"
defmodule KV do
  def start_link do
    {:ok, spawn_link(fn -> loop(%{}) end)}
  end

  defp loop(map) do
    receive do
      {:get, key, caller} ->
        send caller, Map.get(map, key)
        loop(map)
      {:put, key, value} ->
        loop(Map.put(map, key, value))
    end
  end
end

IO.inspect {:ok, pid} = KV.start_link
IO.inspect send pid, {:put, :hello, :world}
IO.inspect send pid, {:get, :hello, self()}

receive do
  value -> IO.inspect value
end
IO.puts ""

IO.puts "## OTP: GenServer"
defmodule KV2 do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, %{})
  end

  def get(pid, key) do
    GenServer.call(pid, {:get, key})
  end

  def put(pid, key, value) do
    GenServer.call(pid, {:put, key, value})
  end

  def handle_call({:get, key}, _from, state) do
    {:reply, Map.get(state, key), state}
  end

  def handle_call({:put, key, value}, _from, state) do
    {:reply, value, Map.put(state, key, value)}
  end
end
IO.inspect {:ok, pid} = KV2.start_link
IO.inspect KV2.get(pid, :a)
IO.inspect KV2.put(pid, :a, 1)
IO.inspect KV2.get(pid, :a)
IO.puts ""

IO.puts "## OTP: Agent"
defmodule KV3 do
  def start_link do
    Agent.start_link fn -> %{} end
  end

  def get(pid, key) do
    Agent.get pid, fn (map) -> Map.get(map, key) end
  end

  def put(pid, key, value) do
    Agent.update pid, fn (map) -> Map.put(map, key, value) end
  end
end

IO.inspect {:ok, pid} = KV3.start_link
IO.inspect KV3.get(pid, :a)
IO.inspect KV3.put(pid, :a, 1)
IO.inspect KV3.get(pid, :a)
IO.puts ""

IO.puts "## OTP: Task"
defmodule TaskModule do
  def parallel do
    [
      fn -> :timer.sleep(200); IO.puts 1 end,
      fn -> :timer.sleep(400); IO.puts 2 end,
      fn -> :timer.sleep(600); IO.puts 3 end,
      fn -> :timer.sleep(800); IO.puts 4 end
    ]
    |> Enum.map(&Task.async/1)
    |> Enum.map(&Task.await/1)
  end
end
TaskModule.parallel()
IO.puts ""

IO.puts "## Process.monitor"
pid = spawn fn ->
  :timer.sleep(100)
  raise RuntimeError
end
Process.monitor pid

receive do
  {:DOWN, ref, :process, pid, msg} ->
    IO.inspect ref
    IO.inspect pid
    IO.inspect msg
end
IO.puts ""

IO.puts "## OTP: Process.link"
pid = spawn fn ->
  pid = spawn fn ->
    :timer.sleep(500)
    raise RuntimeError
  end
  Process.link pid
  IO.puts "start to link with other process"
  :timer.sleep(2000)
  IO.puts "never display this message"
end
Process.monitor pid
IO.puts "start the linking process"
IO.inspect pid

receive do
  {:DOWN, ref, :process, pid, msg} ->
    IO.puts "killed the linking process"
    IO.inspect pid
    IO.inspect msg
end
IO.puts ""

IO.puts "## OTP: spawn_link"
pid = spawn fn ->
  pid = spawn_link fn ->
    :timer.sleep(500)
    raise RuntimeError
  end
  IO.puts "start to link with other process"
  :timer.sleep(2000)
  IO.puts "never display this message"
end
Process.monitor pid
IO.puts "start the linking process"
IO.inspect pid

receive do
  {:DOWN, ref, :process, pid, msg} ->
    IO.puts "killed the linking process"
    IO.inspect pid
    IO.inspect msg
end
IO.puts ""

IO.puts "## OTP: Supervisor"
defmodule Echo.Server do
  use GenServer

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

  def crash! do
    GenServer.cast(__MODULE__, :crash)
  end

  def handle_cast(:crash, _) do
    raise RuntimeError, message: "Opps!"
  end

  def echo(term) do
    GenServer.call(__MODULE__, {:echo, term})
  end

  def handle_call({:echo, str}, _, s) do
    {:reply, str, s}
  end
end

defmodule SupervisorTest do
  def run do
    import Supervisor.Spec, warn: false

    children = [worker(Echo.Server, [])]
    opts = [
      strategy: :one_for_one,
      name: Echo.Supervisor
    ]
    IO.inspect {:ok, pid} = Supervisor.start_link(children, opts)
    IO.inspect Echo.Server.echo "hey"
    IO.inspect Echo.Server.crash!
    :timer.sleep(1000)
    IO.inspect Echo.Server.echo "foo"
  end
end
# SupervisorTest.run

IO.puts "### ModuleBased Supervisor"
defmodule Echo.Supervisor do
  use Supervisor

  def start_link do
    Supervisor.start_link(__MODULE__, [])
  end

  def init([]) do
    children = [
      worker(Echo.Server, [])
    ]
    opts = [
      strategy: :one_for_one,
      name: Echo.Supervisor
    ]
    supervise(children, opts)
  end
end

defmodule ModuleBasedSupervisorTest do
  def run do
    IO.inspect {:ok, pid} = Echo.Supervisor.start_link()
    IO.inspect Echo.Server.echo "hey"
    IO.inspect Echo.Server.crash!
    :timer.sleep(1000)
    IO.inspect Echo.Server.echo "foo"
  end
end
ModuleBasedSupervisorTest.run()
IO.puts ""


Increment version [ci skip]

Increment version [ci skip]


Release 0.3.2

Release 0.3.2


v0.5.1


Requires newer version

Requires newer version


Fix documentation generation config

Fix documentation generation config


bump version

bump version


Version bumpup

Version bumpup


version bump

version bump


Add ex_doc deps

Add ex_doc deps


Upgrade to a newer version of elixir-oauth2

The local fork will be abandoned once this fix lands:

https://github.com/scrogson/oauth2/pull/89


Releases 0.2.1

Releases 0.2.1


Update gen_smtp to 0.15

Update gen_smtp to 0.15


URI.decode_query returns a map, not a HashDict

defmodule URI do
  @moduledoc """
  Utilities for working with and creating URIs.
  """

  defstruct scheme: nil, path: nil, query: nil,
            fragment: nil, authority: nil,
            userinfo: nil, host: nil, port: nil

  import Bitwise

  @ports %{
    "ftp"   => 21,
    "http"  => 80,
    "https" => 443,
    "ldap"  => 389,
    "sftp"  => 22,
    "tftp"  => 69,
  }

  Enum.each @ports, fn {scheme, port} ->
    def normalize_scheme(unquote(scheme)), do: unquote(scheme)
    def default_port(unquote(scheme)),     do: unquote(port)
  end

  @doc """
  Normalizes the scheme according to the spec by downcasing it.
  """
  def normalize_scheme(nil),     do: nil
  def normalize_scheme(scheme),  do: String.downcase(scheme)

  @doc """
  Returns the default port for a given scheme.

  If the scheme is unknown to URI, returns `nil`.
  Any scheme may be registered via `default_port/2`.

  ## Examples

      iex> URI.default_port("ftp")
      21

      iex> URI.default_port("ponzi")
      nil

  """
  def default_port(scheme) when is_binary(scheme) do
    {:ok, dict} = Application.fetch_env(:elixir, :uri)
    Map.get(dict, scheme)
  end

  @doc """
  Registers a scheme with a default port.

  It is recommended for this function to be invoked in your
  application start callback in case you want to register
  new URIs.
  """
  def default_port(scheme, port) when is_binary(scheme) and port > 0 do
    {:ok, dict} = Application.fetch_env(:elixir, :uri)
    Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persist: true)
  end

  @doc """
  Encodes an enumerable into a query string.

  Takes an enumerable (containing a sequence of two-item tuples)
  and returns a string of the form "key1=value1&key2=value2..." where
  keys and values are URL encoded as per `encode/1`.

  Keys and values can be any term that implements the `String.Chars`
  protocol, except lists which are explicitly forbidden.

  ## Examples

      iex> hd = %{"foo" => 1, "bar" => 2}
      iex> URI.encode_query(hd)
      "bar=2&foo=1"

  """
  def encode_query(l), do: Enum.map_join(l, "&", &pair/1)

  @doc """
  Decodes a query string into a dictionary (by default uses a map).

  Given a query string of the form "key1=value1&key2=value2...", produces a
  map with one entry for each key-value pair. Each key and value will be a
  binary. Keys and values will be percent-unescaped.

  Use `query_decoder/1` if you want to iterate over each value manually.

  ## Examples

      iex> URI.decode_query("foo=1&bar=2")
      %{"bar" => "2", "foo" => "1"}

  """
  def decode_query(q, dict \\ %{}) when is_binary(q) do
    Enum.reduce query_decoder(q), dict, fn({k, v}, acc) -> Dict.put(acc, k, v) end
  end

  @doc """
  Returns an iterator function over the query string that decodes
  the query string in steps.

  ## Examples

      iex> URI.query_decoder("foo=1&bar=2") |> Enum.map &(&1)
      [{"foo", "1"}, {"bar", "2"}]

  """
  def query_decoder(q) when is_binary(q) do
    Stream.unfold(q, &do_decoder/1)
  end

  defp do_decoder("") do
    nil
  end

  defp do_decoder(q) do
    {first, next} =
      case :binary.split(q, "&") do
        [first, rest] -> {first, rest}
        [first]       -> {first, ""}
      end

    current =
      case :binary.split(first, "=") do
        [ key, value ] -> {decode(key), decode(value)}
        [ key ]        -> {decode(key), nil}
      end

    {current, next}
  end

  defp pair({k, _}) when is_list(k) do
    raise ArgumentError, message: "encode_query/1 keys cannot be lists, got: #{inspect k}"
  end

  defp pair({_, v}) when is_list(v) do
    raise ArgumentError, message: "encode_query/1 values cannot be lists, got: #{inspect v}"
  end

  defp pair({k, v}) do
    encode(to_string(k)) <> "=" <> encode(to_string(v))
  end

  @doc """
  Percent-escape a URI.

  ## Example

      iex> URI.encode("http://elixir-lang.org/getting_started/2.html")
      "http%3A%2F%2Felixir-lang.org%2Fgetting_started%2F2.html"

  """
  def encode(s), do: for(<<c <- s>>, into: "", do: percent(c))

  defp percent(32), do: <<?+>>
  defp percent(?-), do: <<?->>
  defp percent(?_), do: <<?_>>
  defp percent(?.), do: <<?.>>

  defp percent(c)
      when c >= ?0 and c <= ?9
      when c >= ?a and c <= ?z
      when c >= ?A and c <= ?Z do
    <<c>>
  end

  defp percent(c), do: "%" <> hex(bsr(c, 4)) <> hex(band(c, 15))

  defp hex(n) when n <= 9, do: <<n + ?0>>
  defp hex(n), do: <<n + ?A - 10>>

  @doc """
  Percent-unescape a URI.

  ## Examples

      iex> URI.decode("http%3A%2F%2Felixir-lang.org")
      "http://elixir-lang.org"

  """
  def decode(uri) do
    decode(uri, uri)
  end

  def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
    << bsl(hex2dec(hex1, uri), 4) + hex2dec(hex2, uri) >> <> decode(tail, uri)
  end

  def decode(<<head, tail :: binary >>, uri) do
    <<check_plus(head)>> <> decode(tail, uri)
  end

  def decode(<<>>, _uri), do: <<>>

  defp hex2dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
  defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
  defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
  defp hex2dec(_n, uri) do
    raise ArgumentError, message: "malformed URI #{inspect uri}"
  end

  defp check_plus(?+), do: 32
  defp check_plus(c),  do: c

  @doc """
  Parses a URI into components.

  URIs have portions that are handled specially for the particular
  scheme of the URI. For example, http and https have different
  default ports. Such values can be accessed and registered via
  `URI.default_port/1` and `URI.default_port/2`.

  ## Examples

      iex> URI.parse("http://elixir-lang.org/")
      %URI{scheme: "http", path: "/", query: nil, fragment: nil,
           authority: "elixir-lang.org", userinfo: nil,
           host: "elixir-lang.org", port: 80}

  """
  def parse(s) when is_binary(s) do
    # From http://tools.ietf.org/html/rfc3986#appendix-B
    regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
    parts = nillify(Regex.run(regex, s))

    destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
    {userinfo, host, port} = split_authority(authority)

    if authority do
      authority = ""

      if userinfo, do: authority = authority <> userinfo <> "@"
      if host, do: authority = authority <> host
      if port, do: authority = authority <> ":" <> integer_to_binary(port)
    end

    scheme = normalize_scheme(scheme)

    if nil?(port) and not nil?(scheme) do
      port = default_port(scheme)
    end

    %URI{
      scheme: scheme, path: path, query: query,
      fragment: fragment, authority: authority,
      userinfo: userinfo, host: host, port: port
    }
  end

  # Split an authority into its userinfo, host and port parts.
  defp split_authority(s) do
    s = s || ""
    components = Regex.run ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s

    destructure [_, _, userinfo, host, _, port], nillify(components)
    port = if port, do: binary_to_integer(port)
    host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])

    {userinfo, host, port}
  end

  # Regex.run returns empty strings sometimes. We want
  # to replace those with nil for consistency.
  defp nillify(l) do
    for s <- l do
      if size(s) > 0, do: s, else: nil
    end
  end
end

defimpl String.Chars, for: URI do
  def to_string(uri) do
    scheme = uri.scheme

    if scheme && (port = URI.default_port(scheme)) do
      if uri.port == port, do: uri = %{uri | port: nil}
    end

    result = ""

    if uri.scheme,   do: result = result <> uri.scheme <> "://"
    if uri.userinfo, do: result = result <> uri.userinfo <> "@"
    if uri.host,     do: result = result <> uri.host
    if uri.port,     do: result = result <> ":" <> integer_to_binary(uri.port)
    if uri.path,     do: result = result <> uri.path
    if uri.query,    do: result = result <> "?" <> uri.query
    if uri.fragment, do: result = result <> "#" <> uri.fragment

    result
  end
end

defmodule URI do
  @moduledoc """
  Utilities for working with and creating URIs.
  """

  defstruct scheme: nil, path: nil, query: nil,
            fragment: nil, authority: nil,
            userinfo: nil, host: nil, port: nil

  import Bitwise

  @ports %{
    "ftp"   => 21,
    "http"  => 80,
    "https" => 443,
    "ldap"  => 389,
    "sftp"  => 22,
    "tftp"  => 69,
  }

  Enum.each @ports, fn {scheme, port} ->
    def normalize_scheme(unquote(scheme)), do: unquote(scheme)
    def default_port(unquote(scheme)),     do: unquote(port)
  end

  @doc """
  Normalizes the scheme according to the spec by downcasing it.
  """
  def normalize_scheme(nil),     do: nil
  def normalize_scheme(scheme),  do: String.downcase(scheme)

  @doc """
  Returns the default port for a given scheme.

  If the scheme is unknown to URI, returns `nil`.
  Any scheme may be registered via `default_port/2`.

  ## Examples

      iex> URI.default_port("ftp")
      21

      iex> URI.default_port("ponzi")
      nil

  """
  def default_port(scheme) when is_binary(scheme) do
    {:ok, dict} = Application.fetch_env(:elixir, :uri)
    Map.get(dict, scheme)
  end

  @doc """
  Registers a scheme with a default port.

  It is recommended for this function to be invoked in your
  application start callback in case you want to register
  new URIs.
  """
  def default_port(scheme, port) when is_binary(scheme) and port > 0 do
    {:ok, dict} = Application.fetch_env(:elixir, :uri)
    Application.put_env(:elixir, :uri, Map.put(dict, scheme, port), persist: true)
  end

  @doc """
  Encodes an enumerable into a query string.

  Takes an enumerable (containing a sequence of two-item tuples)
  and returns a string of the form "key1=value1&key2=value2..." where
  keys and values are URL encoded as per `encode/1`.

  Keys and values can be any term that implements the `String.Chars`
  protocol, except lists which are explicitly forbidden.

  ## Examples

      iex> hd = %{"foo" => 1, "bar" => 2}
      iex> URI.encode_query(hd)
      "bar=2&foo=1"

  """
  def encode_query(l), do: Enum.map_join(l, "&", &pair/1)

  @doc """
  Decodes a query string into a dictionary (by default uses a map).

  Given a query string of the form "key1=value1&key2=value2...", produces a
  `HashDict` with one entry for each key-value pair. Each key and value will be a
  binary. Keys and values will be percent-unescaped.

  Use `query_decoder/1` if you want to iterate over each value manually.

  ## Examples

      iex> URI.decode_query("foo=1&bar=2")
      %{"bar" => "2", "foo" => "1"}

  """
  def decode_query(q, dict \\ %{}) when is_binary(q) do
    Enum.reduce query_decoder(q), dict, fn({k, v}, acc) -> Dict.put(acc, k, v) end
  end

  @doc """
  Returns an iterator function over the query string that decodes
  the query string in steps.

  ## Examples

      iex> URI.query_decoder("foo=1&bar=2") |> Enum.map &(&1)
      [{"foo", "1"}, {"bar", "2"}]

  """
  def query_decoder(q) when is_binary(q) do
    Stream.unfold(q, &do_decoder/1)
  end

  defp do_decoder("") do
    nil
  end

  defp do_decoder(q) do
    {first, next} =
      case :binary.split(q, "&") do
        [first, rest] -> {first, rest}
        [first]       -> {first, ""}
      end

    current =
      case :binary.split(first, "=") do
        [ key, value ] -> {decode(key), decode(value)}
        [ key ]        -> {decode(key), nil}
      end

    {current, next}
  end

  defp pair({k, _}) when is_list(k) do
    raise ArgumentError, message: "encode_query/1 keys cannot be lists, got: #{inspect k}"
  end

  defp pair({_, v}) when is_list(v) do
    raise ArgumentError, message: "encode_query/1 values cannot be lists, got: #{inspect v}"
  end

  defp pair({k, v}) do
    encode(to_string(k)) <> "=" <> encode(to_string(v))
  end

  @doc """
  Percent-escape a URI.

  ## Example

      iex> URI.encode("http://elixir-lang.org/getting_started/2.html")
      "http%3A%2F%2Felixir-lang.org%2Fgetting_started%2F2.html"

  """
  def encode(s), do: for(<<c <- s>>, into: "", do: percent(c))

  defp percent(32), do: <<?+>>
  defp percent(?-), do: <<?->>
  defp percent(?_), do: <<?_>>
  defp percent(?.), do: <<?.>>

  defp percent(c)
      when c >= ?0 and c <= ?9
      when c >= ?a and c <= ?z
      when c >= ?A and c <= ?Z do
    <<c>>
  end

  defp percent(c), do: "%" <> hex(bsr(c, 4)) <> hex(band(c, 15))

  defp hex(n) when n <= 9, do: <<n + ?0>>
  defp hex(n), do: <<n + ?A - 10>>

  @doc """
  Percent-unescape a URI.

  ## Examples

      iex> URI.decode("http%3A%2F%2Felixir-lang.org")
      "http://elixir-lang.org"

  """
  def decode(uri) do
    decode(uri, uri)
  end

  def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
    << bsl(hex2dec(hex1, uri), 4) + hex2dec(hex2, uri) >> <> decode(tail, uri)
  end

  def decode(<<head, tail :: binary >>, uri) do
    <<check_plus(head)>> <> decode(tail, uri)
  end

  def decode(<<>>, _uri), do: <<>>

  defp hex2dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
  defp hex2dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
  defp hex2dec(n, _uri) when n in ?0..?9, do: n - ?0
  defp hex2dec(_n, uri) do
    raise ArgumentError, message: "malformed URI #{inspect uri}"
  end

  defp check_plus(?+), do: 32
  defp check_plus(c),  do: c

  @doc """
  Parses a URI into components.

  URIs have portions that are handled specially for the particular
  scheme of the URI. For example, http and https have different
  default ports. Such values can be accessed and registered via
  `URI.default_port/1` and `URI.default_port/2`.

  ## Examples

      iex> URI.parse("http://elixir-lang.org/")
      %URI{scheme: "http", path: "/", query: nil, fragment: nil,
           authority: "elixir-lang.org", userinfo: nil,
           host: "elixir-lang.org", port: 80}

  """
  def parse(s) when is_binary(s) do
    # From http://tools.ietf.org/html/rfc3986#appendix-B
    regex = ~r/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
    parts = nillify(Regex.run(regex, s))

    destructure [_, _, scheme, _, authority, path, _, query, _, fragment], parts
    {userinfo, host, port} = split_authority(authority)

    if authority do
      authority = ""

      if userinfo, do: authority = authority <> userinfo <> "@"
      if host, do: authority = authority <> host
      if port, do: authority = authority <> ":" <> integer_to_binary(port)
    end

    scheme = normalize_scheme(scheme)

    if nil?(port) and not nil?(scheme) do
      port = default_port(scheme)
    end

    %URI{
      scheme: scheme, path: path, query: query,
      fragment: fragment, authority: authority,
      userinfo: userinfo, host: host, port: port
    }
  end

  # Split an authority into its userinfo, host and port parts.
  defp split_authority(s) do
    s = s || ""
    components = Regex.run ~r/(^(.*)@)?(\[[a-zA-Z0-9:.]*\]|[^:]*)(:(\d*))?/, s

    destructure [_, _, userinfo, host, _, port], nillify(components)
    port = if port, do: binary_to_integer(port)
    host = if host, do: host |> String.lstrip(?[) |> String.rstrip(?])

    {userinfo, host, port}
  end

  # Regex.run returns empty strings sometimes. We want
  # to replace those with nil for consistency.
  defp nillify(l) do
    for s <- l do
      if size(s) > 0, do: s, else: nil
    end
  end
end

defimpl String.Chars, for: URI do
  def to_string(uri) do
    scheme = uri.scheme

    if scheme && (port = URI.default_port(scheme)) do
      if uri.port == port, do: uri = %{uri | port: nil}
    end

    result = ""

    if uri.scheme,   do: result = result <> uri.scheme <> "://"
    if uri.userinfo, do: result = result <> uri.userinfo <> "@"
    if uri.host,     do: result = result <> uri.host
    if uri.port,     do: result = result <> ":" <> integer_to_binary(uri.port)
    if uri.path,     do: result = result <> uri.path
    if uri.query,    do: result = result <> "?" <> uri.query
    if uri.fragment, do: result = result <> "#" <> uri.fragment

    result
  end
end