defmodule ESTree.Tools.ESTreeJSONTransformer do
  @moduledoc """
  Converts ESTree JSON into the structs in the ESTree library
  """


  @spec convert(%{}) :: ESTree.Node.t
  def convert(json)

  def convert(%{"type" => "ArrayExpression"} = json) do
    %ESTree.ArrayExpression{
      elements: convert_property(json, "elements", [])
    }
  end

  def convert(%{"type" => "ArrayPattern"} = json) do
    %ESTree.ArrayPattern{
      elements: convert_property(json, "elements", [])
    }
  end

  def convert(%{"type" => "ArrowFunctionExpression"} = json) do
    %ESTree.ArrowFunctionExpression{
      generator: convert_property(json, "generator", false),
      expression: convert_property(json, "expression", false),
      params: convert_property(json, "params", []),
      body: convert_property(json, "body", [])
    }
  end

  def convert(%{"type" => "AssignmentExpression"} = json) do
    %ESTree.AssignmentExpression{
      operator: convert_property_to_atom(json, "operator", "="),
      left: convert_property(json, "left"),
      right: convert_property(json, "right")
    }
  end

  def convert(%{"type" => "AssignmentPattern"} = json) do
    %ESTree.AssignmentPattern{
      left: convert_property(json, "left"),
      right: convert_property(json, "right")
    }
  end

  def convert(%{"type" => "AwaitExpression"} = json) do
    %ESTree.AwaitExpression{
      argument: convert_property(json, "argument")
    }
  end

  def convert(%{"type" => "BinaryExpression"} = json) do
    %ESTree.BinaryExpression{
      operator: convert_property_to_atom(json, "operator", "="),
      left: convert_property(json, "left"),
      right: convert_property(json, "right")
    }
  end

  def convert(%{"type" => "BlockStatement"} = json) do
    %ESTree.BlockStatement{
      body: convert_property(json, "body", [])
    }
  end

  def convert(%{"type" => "BreakStatement"} = json) do
    %ESTree.BreakStatement{
      label: convert_property(json, "label")
    }
  end

  def convert(%{"type" => "CallExpression"} = json) do
    %ESTree.CallExpression{
      callee: convert_property(json, "callee", false),
      arguments: convert_property(json, "arguments", [])
    }
  end

  def convert(%{"type" => "CatchClause"} = json) do
    %ESTree.CatchClause{
      param: convert_property(json, "param"),
      body: convert_property(json, "body")
    }
  end

  def convert(%{"type" => "ClassBody"} = json) do
    %ESTree.ClassBody{
      body: convert_property(json, "body", [])
    }
  end

  def convert(%{"type" => "ClassDeclaration"} = json) do
    %ESTree.ClassDeclaration{
      id: convert_property(json, "id"),
      body: convert_property(json, "body"),
      superClass: convert_property(json, "superClass")
    }
  end

  def convert(%{"type" => "ClassExpression"} = json) do
    %ESTree.ClassExpression{
      body: convert_property(json, "body"),
      superClass: convert_property(json, "superClass")
    }
  end

  def convert(%{"type" => "ConditionalStatement"} = json) do
    %ESTree.ConditionalStatement{
      test: convert_property(json, "test"),
      alternate: convert_property(json, "alternate"),
      consequent: convert_property(json, "consequent")
    }
  end

  def convert(%{"type" => "ContinueStatement"} = json) do
    %ESTree.ContinueStatement{
      label: convert_property(json, "label")
    }
  end

  def convert(%{"type" => "DebuggerStatement"}) do
    %ESTree.DebuggerStatement{}
  end

  def convert(%{"type" => "DoWhileStatement"} = json) do
    %ESTree.DoWhileStatement{
      test: convert_property(json, "test"),
      body: convert_property(json, "body")
    }
  end

  def convert(%{"type" => "EmptyExpression"}) do
    %ESTree.EmptyExpression{}
  end

  def convert(%{"type" => "EmptyStatement"}) do
    %ESTree.EmptyStatement{}
  end

  def convert(%{"type" => "ExportAllDeclaration"} = json) do
    %ESTree.ExportAllDeclaration{
      source: convert_property(json, "source")
    }
  end

  def convert(%{"type" => "ExportDefaultDeclaration"} = json) do
    %ESTree.ExportDefaultDeclaration{
      declaration: convert_property(json, "declaration")
    }
  end

  def convert(%{"type" => "ExportNamedDeclaration"} = json) do
    %ESTree.ExportNamedDeclaration{
      declaration: convert_property(json, "declaration"),
      specifiers: convert_property(json, "specifiers", []),
      source: convert_property(json, "source")
    }
  end

  def convert(%{"type" => "ExportSpecifier"} = json) do
    %ESTree.ExportSpecifier{
      local: convert_property(json, "local"),
      exported: convert_property(json, "exported")
    }
  end

  def convert(%{"type" => "ExpressionStatement"} = json) do
    %ESTree.ExpressionStatement{
      expression: convert_property(json, "expression")
    }
  end

  def convert(%{"type" => "ForInStatement"} = json) do
    %ESTree.ForInStatement{
      left: convert_property(json, "left"),
      right: convert_property(json, "right"),
      body: convert_property(json, "body")
    }
  end

  def convert(%{"type" => "ForOfStatement"} = json) do
    %ESTree.ForOfStatement{
      left: convert_property(json, "left"),
      right: convert_property(json, "right"),
      body: convert_property(json, "body")
    }
  end

  def convert(%{"type" => "ForStatement"} = json) do
    %ESTree.ForStatement{
      init: convert_property(json, "init"),
      test: convert_property(json, "test"),
      update: convert_property(json, "update"),
      body: convert_property(json, "body")
    }
  end

  def convert(%{"type" => "FunctionDeclaration"} = json) do
    %ESTree.FunctionDeclaration{
      id: convert_property(json, "id"),
      params: convert_property(json, "params", []),
      defaults: convert_property(json, "defaults", []),
      body: convert_property(json, "body"),
      generator: convert_property(json, "generator", false),
      expression: convert_property(json, "expression", false),
      async: convert_property(json, "async", false)
    }
  end

  def convert(%{"type" => "FunctionExpression"} = json) do
    %ESTree.FunctionExpression{
      params: convert_property(json, "params", []),
      defaults: convert_property(json, "defaults", []),
      body: convert_property(json, "body"),
      generator: convert_property(json, "generator", false),
      expression: convert_property(json, "expression", false),
      async: convert_property(json, "async", false)
    }
  end

  def convert(%{"type" => "Identifier"} = json) do
    %ESTree.Identifier{
      name: convert_property(json, "name")
    }
  end

  def convert(%{"type" => "IfStatement"} = json) do
    %ESTree.IfStatement{
      test: convert_property(json, "test"),
      consequent: convert_property(json, "consequent"),
      alternate: convert_property(json, "alternate")
    }
  end

  def convert(%{"type" => "ImportDeclaration"} = json) do
    %ESTree.ImportDeclaration{
      specifiers: convert_property(json, "specifiers", []),
      source: convert_property(json, "source")
    }
  end

  def convert(%{"type" => "ImportDefaultSpecifier"} = json) do
    %ESTree.ImportDefaultSpecifier{
      local: convert_property(json, "local")
    }
  end

  def convert(%{"type" => "ImportNamespaceSpecifier"} = json) do
    %ESTree.ImportNamespaceSpecifier{
      local: convert_property(json, "local")
    }
  end

  def convert(%{"type" => "ImportSpecifier"} = json) do
    %ESTree.ImportSpecifier{
      local: convert_property(json, "local"),
      imported: convert_property(json, "imported")
    }
  end

  def convert(%{"type" => "JSXAttribute"} = json) do
    %ESTree.JSXAttribute{
      name: convert_property(json, "name"),
      value: convert_property(json, "value")
    }
  end

  def convert(%{"type" => "JSXClosingElement"} = json) do
    %ESTree.JSXClosingElement{
      name: convert_property(json, "name")
    }
  end

  def convert(%{"type" => "JSXElement"} = json) do
    %ESTree.JSXElement{
      openingElement: convert_property(json, "openingElement"),
      children: convert_property(json, "children", []),
      closingElement: convert_property(json, "closingElement")
    }
  end

  def convert(%{"type" => "JSXEmptyExpression"}) do
    %ESTree.JSXEmptyExpression{}
  end

  def convert(%{"type" => "JSXExpressionContainer"} = json) do
    %ESTree.JSXExpressionContainer{
      expression: convert_property(json, "expression")
    }
  end

  def convert(%{"type" => "JSXIdentifier"} = json) do
    %ESTree.JSXIdentifier{
      name: convert_property(json, "name")
    }
  end

  def convert(%{"type" => "JSXMemberExpression"} = json) do
    %ESTree.JSXMemberExpression{
      object: convert_property(json, "object"),
      property: convert_property(json, "property")
    }
  end

  def convert(%{"type" => "JSXNamespacedName"} = json) do
    %ESTree.JSXNamespacedName{
      namespace: convert_property(json, "namespace"),
      name: convert_property(json, "name")
    }
  end

  def convert(%{"type" => "JSXOpeningElement"} = json) do
    %ESTree.JSXOpeningElement{
      name: convert_property(json, "name"),
      attributes: convert_property(json, "attributes", []),
      selfClosing: convert_property(json, "selfClosing", false)
    }
  end

  def convert(%{"type" => "JSXSpreadAttribute"} = json) do
    %ESTree.JSXSpreadAttribute{
      argument: convert_property(json, "argument")
    }
  end

  def convert(%{"type" => "LabeledStatement"} = json) do
    %ESTree.LabeledStatement{
      label: convert_property(json, "label"),
      body: convert_property(json, "body")
    }
  end

  def convert(%{"type" => "Literal"} = json) do
    %ESTree.Literal{
      value: convert_property(json, "value")
    }
  end

  def convert(%{"type" => "LogicalExpression"} = json) do
    %ESTree.LogicalExpression{
      operator: convert_property_to_atom(json, "operator", ""),
      left: convert_property(json, "left"),
      right: convert_property(json, "right")
    }
  end

  def convert(%{"type" => "MemberExpression"} = json) do
    %ESTree.MemberExpression{
      object: convert_property(json, "object"),
      property: convert_property(json, "property"),
      computed: convert_property(json, "computed", false)
    }
  end

  def convert(%{"type" => "MetaProperty"} = json) do
    %ESTree.MetaProperty{
      meta: convert_property(json, "meta"),
      property: convert_property(json, "property")
    }
  end

  def convert(%{"type" => "MethodDefinition"} = json) do
    %ESTree.MethodDefinition{
      key: convert_property(json, "key"),
      value: convert_property(json, "value"),
      kind: convert_property_to_atom(json, "kind", ""),
      computed: convert_property(json, "computed", false),
      static: convert_property(json, "static", false)
    }
  end

  def convert(%{"type" => "NewExpression"} = json) do
    %ESTree.NewExpression{
      callee: convert_property(json, "callee", false),
      arguments: convert_property(json, "arguments", [])
    }
  end

  def convert(%{"type" => "ObjectExpression"} = json) do
    %ESTree.ObjectExpression{
      properties: convert_property(json, "properties", [])
    }
  end

  def convert(%{"type" => "ObjectPattern"} = json) do
    %ESTree.ObjectPattern{
      properties: convert_property(json, "properties", [])
    }
  end

  def convert(%{"type" => "Program"} = json) do
    %ESTree.Program{
      body: convert_property(json, "body", []),
      sourceType: convert_property(json, "sourceType", "module")
    }
  end

  def convert(%{"type" => "Property"} = json) do
    %ESTree.Property{
      key: convert_property(json, "key"),
      value: convert_property(json, "value"),
      kind: convert_property_to_atom(json, "kind", ""),
      method: convert_property(json, "method", false),
      shorthand: convert_property(json, "shorthand", false),
      computed: convert_property(json, "computed", false)
    }
  end

  def convert(%{"type" => "RestElement"} = json) do
    %ESTree.RestElement{
      argument: convert_property(json, "argument")
    }
  end

  def convert(%{"type" => "ReturnStatement"} = json) do
    %ESTree.ReturnStatement{
      argument: convert_property(json, "argument")
    }
  end

  def convert(%{"type" => "SequenceExpression"} = json) do
    %ESTree.SequenceExpression{
      expressions: convert_property(json, "expressions", [])
    }
  end

  def convert(%{"type" => "SpreadElement"} = json) do
    %ESTree.SpreadElement{
      argument: convert_property(json, "argument")
    }
  end

  def convert(%{"type" => "Super"}) do
    %ESTree.Super{}
  end

  def convert(%{"type" => "SwitchCase"} = json) do
    %ESTree.SwitchCase{
      test: convert_property(json, "test"),
      consequent: convert_property(json, "consequent")
    }
  end

  def convert(%{"type" => "SwitchStatement"} = json) do
    %ESTree.SwitchStatement{
      discriminant: convert_property(json, "discriminant"),
      cases: convert_property(json, "cases", [])
    }
  end

  def convert(%{"type" => "TaggedTemplateExpression"} = json) do
    %ESTree.TaggedTemplateExpression{
      tag: convert_property(json, "tag"),
      quasi: convert_property(json, "quasi")
    }
  end

  def convert(%{"type" => "TemplateElement"} = json) do
    value = case Map.get(json, "value") do
      map when is_map(map) ->
        %{
          cooked: convert_property(map, "cooked"),
          raw: convert_property(map, "raw")
        }
      other ->
        other
    end

    %ESTree.TemplateElement{
      value: value,
      tail: convert_property(json, "tail", false)
    }
  end

  def convert(%{"type" => "TemplateLiteral"} = json) do
    %ESTree.TemplateLiteral{
      quasis: convert_property(json, "quasis", []),
      expressions: convert_property(json, "expressions", [])
    }
  end

  def convert(%{"type" => "ThisExpression"}) do
    %ESTree.ThisExpression{}
  end

  def convert(%{"type" => "ThrowStatement"} = json) do
    %ESTree.ThrowStatement{
      argument: convert_property(json, "argument")
    }
  end

  def convert(%{"type" => "TryStatement"} = json) do
    %ESTree.TryStatement{
      block: convert_property(json, "block"),
      handler: convert_property(json, "handler"),
      finalizer: convert_property(json, "finalizer")
    }
  end

  def convert(%{"type" => "UnaryExpression"} = json) do
    %ESTree.UnaryExpression{
      operator: convert_property_to_atom(json, "operator", ""),
      prefix: convert_property(json, "prefix", false),
      argument: convert_property(json, "argument")
    }
  end

  def convert(%{"type" => "UpdateExpression"} = json) do
    %ESTree.UpdateExpression{
      operator: convert_property_to_atom(json, "operator", ""),
      prefix: convert_property(json, "prefix", false),
      argument: convert_property(json, "argument")
    }
  end

  def convert(%{"type" => "VariableDeclaration"} = json) do
    %ESTree.VariableDeclaration{
      kind: convert_property_to_atom(json, "kind", "var"),
      declarations: convert_property(json, "declarations", [])
    }
  end

  def convert(%{"type" => "VariableDeclarator"} = json) do
    %ESTree.VariableDeclarator{
      id: convert_property(json, "id"),
      init: convert_property(json, "init")
    }
  end

  def convert(%{"type" => "WhileStatement"} = json) do
    %ESTree.WhileStatement{
      test: convert_property(json, "test"),
      body: convert_property(json, "body")
    }
  end

  def convert(%{"type" => "WithStatement"} = json) do
    %ESTree.WithStatement{
      object: convert_property(json, "object"),
      body: convert_property(json, "body")
    }
  end

  def convert(%{"type" => "YieldExpression"} = json) do
    %ESTree.YieldExpression{
      argument: convert_property(json, "argument"),
      delegate: convert_property(json, "delegate", false)
    }
  end

  defp convert_property_to_atom(json, property, default) do
    json
    |> Map.get(property, default)
    |> String.to_atom()
  end

  defp convert_property(json, property, default \\ nil) do
    case Map.get(json, property, default) do
      nil ->
        nil
      list when is_list(list) ->
        Enum.map(list, &convert/1)
      map when is_map(map) ->
        convert(map)
      val ->
        val
    end
  end
end