lib/weaviate_graph_ql/where.ex

defmodule Noizu.Weaviate.GraphQL.Where do

  @value_types [
    :value_int,
    :value_boolean,
    :value_string,
    :value_text,
    :value_number,
    :value_date
  ]

  @value_type_lookup %{
    int: :value_int,
    bool: :value_boolean,
    string: :value_string,
    text: :value_text,
    number: :value_number,
    date: :value_date
  }

  defstruct [
    operator: nil
  ]

  defimpl Jason.Encoder do
    defp nest(string, prefix) do
      prepared = String.trim(string)
                 |> String.split("\n")
                 |> Enum.join("\n#{prefix}")
      prepared
    end

    def encode(this, opts) do
      Jason.encode!(this.operator) |> String.trim()
    end
  end

  defmodule And do
    defstruct [
      operands: nil
    ]


    defimpl Jason.Encoder do
      defp nest(string, prefix) do
        prepared = String.trim(string)
                   |> String.split("\n")
                   |> Enum.join("\n#{prefix}")
        prepared
      end

      def encode(this, opts) do
        contents =
          this.operands
          |> Enum.map(fn
            {k} -> "#{nest(Jason.encode!(k), "  ")}"
          end)
          |> Enum.join(",\n")
        """
        {
          operator: And,
          operands: [
            #{nest(contents, "    ")}
          ]
        }
        """ |> String.trim()
      end
    end

  end

  defmodule Or do
    defstruct [
      operands: nil
    ]

    defimpl Jason.Encoder do
      defp nest(string, prefix) do
        prepared = String.trim(string)
                   |> String.split("\n")
                   |> Enum.join("\n#{prefix}")
        prepared
      end

      def encode(this, opts) do
        contents =
          this.operands
          |> Enum.map(fn
            {k} -> "#{nest(Jason.encode!(k), "  ")}"
          end)
          |> Enum.join(",\n")
        """
        {
          operator: Or,
          operands: [
            #{nest(contents, "    ")}
          ]
        }
        """ |> String.trim()
      end
    end
  end

  defmodule WithinGeoRange do
    defstruct [
      coordinates: nil,
      distance: nil,
      path: nil
    ]


    defimpl Jason.Encoder do
      defp nest(string, prefix) do
        prepared = String.trim(string)
                   |> String.split("\n")
                   |> Enum.join("\n#{prefix}")
        prepared
      end

      def encode(this, opts) do
        contents =
          this.operands
          |> Enum.map(fn
            {k} -> "#{nest(Jason.encode!(k), "  ")}"
          end)
          |> Enum.join(",\n")
        """
        {
          operator: WithinGeoRange,
          valueGeoRange: {
            latitude: #{this.coordinates.latitude},
            longitude: #{this.coordinates.longitude},
          }
          distance: #{Jason.encode(this.distance) |> nest("  ")}
          path: #{inspect this.path}
        }
        """ |> String.trim()
      end
    end

  end

  defmodule Condition do
    defstruct [
      path: nil,
      operator: nil,
      value: nil,
      value_type: nil
    ]


    defimpl Jason.Encoder do
      defp nest(string, prefix) do
        prepared = String.trim(string)
                   |> String.split("\n")
                   |> Enum.join("\n#{prefix}")
        prepared
      end

      def encode(this, opts) do
        value_type = case this.value_type do
          :value_int -> "valueInt"
          :value_boolean -> "valueBoolean"
          :value_string -> "valueString"
          :value_text -> "valueText"
          :value_number -> "valueNumber"
          :value_date -> "valueDate"
        end
        """
        {
          operator: #{this.operator},
          #{value_type}: #{inspect(this.value)},
          path: #{inspect this.path}
        }
        """ |> String.trim()
      end
    end

  end

  def extract_path(path) do
    cond do
      is_bitstring(path) -> [path]
      is_list(path) -> path
    end
  end
  def extract_value_type(value) do
    cond do
      is_integer(value) -> :value_int
      is_bitstring(value) -> :value_string
      is_float(value) -> :value_number
      value in [true, false] -> :value_boolean
      is_struct(value) && value.__struct__ == DateTime -> :value_date
      is_struct(value) && value.__struct__ == Date -> :value_date
    end
  end
  def type_lookup(value_type) do
    @value_type_lookup[value_type]
  end


  def where(container, filter) do
    container.__struct__.where(container, %__MODULE__{operator: filter})
  end

  def and_operator(a,b) do
    %Noizu.Weaviate.GraphQL.Where.And{
      operands: [a,b]
    }
  end

  def or_operator(a,b) do
    %Noizu.Weaviate.GraphQL.Where.Or{
      operands: [a,b]
    }
  end

  def near(path, %{longitude: longitude, latitude: latitude}, distance) do
    %Noizu.Weaviate.GraphQL.Where.WithinGeoRange{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      coordinates: %{longitude: longitude, latitude: latitude},
      distance: distance
    }
  end

  def equal(path, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :Equal,
      value: value,
      value_type: Noizu.Weaviate.GraphQL.Where.extract_value_type(value)
    }
  end
  def equal(path, value_type, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :Equal,
      value: value,
      value_type: Noizu.Weaviate.GraphQL.Where.type_lookup(value_type)
    }
  end


  def not_equal(path, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :NotEqual,
      value: value,
      value_type: Noizu.Weaviate.GraphQL.Where.extract_value_type(value)
    }
  end
  def not_equal(path, value_type, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :NotEqual,
      value: value,
      value_type: Noizu.Weaviate.GraphQL.Where.type_lookup(value_type)
    }
  end

  def greater_than(path, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :GreaterThan,
      value: value,
      value_type: Noizu.Weaviate.GraphQL.Where.extract_value_type(value)
    }
  end
  def greater_than(path, value_type, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :GreaterThan,
      value: value,
      value_type: Noizu.Weaviate.GraphQL.Where.type_lookup(value_type)
    }
  end


  def greater_than_eq(path, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :GreaterThanEqual,
      value: value,
      value_type: Noizu.Weaviate.GraphQL.Where.extract_value_type(value)
    }
  end
  def greater_than_eq(path, value_type, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :GreaterThanEqual,
      value: value,
      value_type: Noizu.Weaviate.GraphQL.Where.type_lookup(value_type)
    }
  end


  def less_than(path, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :LessThan,
      value: value,
      value_type: Noizu.Weaviate.GraphQL.Where.extract_value_type(value)
    }
  end
  def less_than(path, value_type, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :LessThan,
      value: value,
      value_type: Noizu.Weaviate.GraphQL.Where.type_lookup(value_type)
    }
  end


  def less_than_eq(path, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :LessThanEqual,
      value: value,
      value_type: Noizu.Weaviate.GraphQL.Where.extract_value_type(value)
    }
  end
  def less_than_eq(path, value_type, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :LessThanEqual,
      value: value,
      value_type: Noizu.Weaviate.GraphQL.Where.type_lookup(value_type)
    }
  end


  def like(path, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :Like,
      value: value,
      value_type: :value_text
    }
  end
  def like(path, value_type, value) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :Like,
      value: value,
      value_type: Noizu.Weaviate.GraphQL.Where.type_lookup(value_type)
    }
  end



  def is_null(path) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :IsNull,
      value: true,
      value_type: :value_boolean
    }
  end

  def is_not_null(path) do
    %Noizu.Weaviate.GraphQL.Where.Condition{
      path: Noizu.Weaviate.GraphQL.Where.extract_path(path),
      operator: :IsNull,
      value: false,
      value_type: :value_boolean
    }
  end


end