defmodule ReaxtError do
  defexception [:message,:args,:js_render,:js_stack]
  def exception({:handler_error,module,submodule,args,error,stack}) do
    params=%{
      module: module,
      submodule: submodule,
      args: args
    }
    %ReaxtError{message: "JS Handler Exception for #{inspect params}: #{error}", args: params, js_stack: (stack && parse_stack(stack))}
  end
  def exception({:render_error,params,error,stack,js_render}) do
    %ReaxtError{message: "JS Render Exception : #{error}", args: params, js_render: js_render, js_stack: (stack && parse_stack(stack))}
  end
  defp parse_stack(stack) do
    Regex.scan(~r/at (.*) \((.*):([0-9]*):[0-9]*\)/,stack)
    |> Enum.map(fn [_,function,url,line]->
      if String.contains?(url,"/priv") and !(function in ["Port.next_term","Socket.read_term"]) do
        {line,_} = Integer.parse(line)
        [_,after_priv] = String.split(url,"/priv/",parts: 2)
        {JS,:"#{function}",0,file: '#{WebPack.Util.web_priv}/#{after_priv}', line: line}
      end
    end)
    |> Enum.filter(&!is_nil(&1))
  end
end
defmodule Reaxt do
  alias :poolboy, as: Pool
  require Logger

  def render_result(chunk,module,data,timeout) when not is_tuple(module), do:
    render_result(chunk,{module,nil},data,timeout)
  def render_result(chunk,{module,submodule},data,timeout) do
    Pool.transaction(:"react_#{chunk}_pool",fn worker->
      GenServer.call(worker,{:render,module,submodule,data,timeout},timeout+100)
    end)
  end

  def render!(module,data,timeout \\ 5_000, chunk \\ :server) do
    case render_result(chunk,module,data,timeout) do
      {:ok,res}->res
      {:error,err}->
        try do raise(ReaxtError,err)
        rescue ex->
          [_|stack] = __STACKTRACE__
          reraise ex, ((ex.js_stack || []) ++ stack)
        end
    end
  end

  def render(module,data, timeout \\ 5_000) do
    try do
      render!(module,data,timeout)
    rescue
      ex->
        case ex do
          %{js_render: js_render} when is_binary(js_render)->
            Logger.error(Exception.message(ex))
            %{css: "",html: "", js_render: js_render}
          _ ->
            reraise ex, __STACKTRACE__
        end
    end
  end

  def reload do
    WebPack.Util.build_stats
    Supervisor.terminate_child(Reaxt.App, Reaxt.App.PoolsSup)
    Supervisor.restart_child(Reaxt.App, Reaxt.App.PoolsSup)
  end

  def start_link(server_path) do
    init = Poison.encode!(Application.get_env(:reaxt,:global_config,nil))
    Exos.Proc.start_link("node #{server_path}",init,[cd: '#{WebPack.Util.web_priv}'])
  end

  defmodule App do
    use Application
    def start(_,_) do
      result = Supervisor.start_link(
        [App.PoolsSup] ++ List.wrap(if Application.get_env(:reaxt,:hot) do [
          WebPack.Events,
          WebPack.EventManager,
          WebPack.Compiler,
          #{WebPack.StartBlocker,:infinity} # choice : wait for build or "mix webpack.compile" before launch
        ] end), name: __MODULE__, strategy: :one_for_one)
      WebPack.Util.build_stats
      result
    end
    defmodule PoolsSup do
      use Supervisor
      def start_link(arg) do Supervisor.start_link(__MODULE__,arg, name: __MODULE__) end
      def init(_) do
        pool_size = Application.get_env(:reaxt,:pool_size)
        pool_overflow = Application.get_env(:reaxt,:pool_max_overflow)
        server_dir = "#{WebPack.Util.web_priv}/#{Application.get_env(:reaxt,:server_dir)}"
        server_files = Path.wildcard("#{server_dir}/*.js")
        if server_files == [] do
          Logger.error("#server JS not yet compiled in #{server_dir}, compile it before with `mix webpack.compile`")
          throw {:error,:serverjs_not_compiled}
        else
          Supervisor.init(
            for server<-server_files do
              pool = :"react_#{server |> Path.basename(".js") |> String.replace(~r/[0-9][a-z][A-Z]/,"_")}_pool"
              Pool.child_spec(pool,[worker_module: Reaxt,size: pool_size, max_overflow: pool_overflow, name: {:local,pool}], server)
            end, strategy: :one_for_one)
        end
      end
    end
  end
end
