defmodule Noizu.Entity.Path do
@derive Noizu.EntityReference.Protocol
defstruct path: nil,
materialized_path: nil,
matrix: nil,
depth: nil
@identity_matrix %{
a11: 1,
a12: 0,
a21: 0,
a22: 1
}
use Noizu.Entity.Field.Behaviour
def ecto_gen_string(name) do
{:ok, ["#{name}_depth:integer", "#{name}_a11:integer", "#{name}_a12:integer", "#{name}_a21:integer", "#{name}_a22:integer"]}
end
def parent_path(nil), do: nil
def parent_path(%{__struct__: __MODULE__} = this) do
new(Enum.slice(this.path, 0..-2))
end
def path_string(nil), do: nil
def path_string(%{__struct__: __MODULE__} = this) do
Enum.join(this.path, ".")
end
# ---------------------------
#
# ---------------------------
def position_matrix(position) when is_integer(position) do
i = position + 1
%{
a11: i,
a12: -1,
a21: 1,
a22: 0
}
end
# ---------------------------
#
# ---------------------------
def multiply_matrix(%{a11: a11, a12: a12, a21: a21, a22: a22}, %{
a11: b11,
a12: b12,
a21: b21,
a22: b22
}) do
%{
a11: a11 * b11 + a12 * b21,
a12: a11 * b12 + a12 * b22,
a21: a21 * b11 + a22 * b21,
a22: a21 * b12 + a22 * b22
}
end
# ---------------------------
#
# ---------------------------
def leaf_node(%{a11: a11, a12: a12, a21: _a21, a22: _a22}) do
Integer.floor_div(a11, -a12)
end
# ---------------------------
#
# ---------------------------
def convert_path_to_matrix([]), do: @identity_matrix
def convert_path_to_matrix(path) when is_list(path) do
Enum.reduce(
path,
@identity_matrix,
fn position, path ->
multiply_matrix(path, position_matrix(position))
end
)
end
# ---------------------------
# child
# ---------------------------
def child(path, position) do
np = path.path ++ [position]
%__MODULE__{
depth: path.depth + 1,
path: np,
materialized_path: convert_path_to_tuple(np),
matrix: multiply_matrix(path.matrix, position_matrix(position))
}
end
# ---------------------------
#
# ---------------------------
def convert_matrix_to_path(m) do
convert_matrix_to_path(m, [])
end
def convert_matrix_to_path(%{a11: 1, a12: 0, a21: 0, a22: 1}, acc) do
Enum.reverse(acc)
end
def convert_matrix_to_path(m, acc) do
if m.a22 == 0 do
Enum.reverse(acc ++ [m.a11 - 1])
else
if length(acc) < 1024 do
l = leaf_node(m)
a11 = -m.a12
a21 = -m.a22
a12 = m.a11 - a11 * (l + 1)
a22 = m.a21 - a21 * (l + 1)
convert_matrix_to_path(%{a11: a11, a12: a12, a21: a21, a22: a22}, acc ++ [l])
else
{:error, acc}
end
end
end
# ---------------------------
#
# ---------------------------
def convert_tuple_to_path(path) when is_tuple(path) do
convert_tuple_to_path(path, [])
end
def convert_tuple_to_path({}, acc), do: acc
def convert_tuple_to_path({a}, acc), do: acc ++ [a]
def convert_tuple_to_path({a, {}}, acc), do: acc ++ [a]
def convert_tuple_to_path({a, b}, acc), do: convert_tuple_to_path(b, acc ++ [a])
# ---------------------------
#
# ---------------------------
def convert_path_to_tuple(path) when is_list(path) do
path
|> Enum.reverse()
|> Enum.reduce({}, &{&1, &2})
end
# ---------------------------
#
# ---------------------------
def new(%{path_a11: a11, path_a12: a12, path_a21: a21, path_a22: a22}) do
new(%{a11: a11, a12: a12, a21: a21, a22: a22})
end
def new(%{a11: a11, a12: a12, a21: a21, a22: a22} = _m) when a12 > 0 and a22 > 0 do
new(%{a11: a11, a12: -a12, a21: a21, a22: -a22})
end
def new(%{a11: _a11, a12: _a12, a21: _a21, a22: _a22} = m) do
new(convert_matrix_to_path(m))
end
def new(path) when is_tuple(path) do
new(convert_tuple_to_path(path))
end
def new(path) when is_list(path) do
%__MODULE__{
depth: length(path),
path: path,
materialized_path: convert_path_to_tuple(path),
matrix: convert_path_to_matrix(path)
}
end
def new("") do
%__MODULE__{
depth: 0,
path: [],
materialized_path: {},
matrix: @identity_matrix
}
end
def new(path) when is_bitstring(path) do
path
|> String.split(".")
|> Enum.map(&String.to_integer(&1))
|> new()
end
end
defmodule Noizu.Entity.Path.TypeHelper do
require Noizu.Entity.Meta.Persistence
require Noizu.Entity.Meta.Field
def persist(_, _, _, _, _), do: {:error, :not_supported}
def as_record(_, _, _, _), do: {:error, :not_supported}
def as_entity(_, _, _, _), do: {:error, :not_supported}
def as_entity(_, _, _, _, _), do: {:error, :not_supported}
def delete_record(_, _, _, _), do: {:error, :not_supported}
def from_record(_, _, _, _), do: {:error, :not_supported}
def from_record(_, _, _, _, _), do: {:error, :not_supported}
def field_as_record(
field,
Noizu.Entity.Meta.Field.field_settings(name: name, store: field_store),
Noizu.Entity.Meta.Persistence.persistence_settings(store: store, table: table),
_context,
_options
) do
name = field_store[table][:name] || field_store[store][:name] || name
[
{:ok, {:"#{name}_depth", field.depth}},
{:ok, {:"#{name}_a11", field.matrix.a11}},
{:ok, {:"#{name}_a12", field.matrix.a12}},
{:ok, {:"#{name}_a21", field.matrix.a21}},
{:ok, {:"#{name}_a22", field.matrix.a22}}
]
end
def field_from_record(
_,
record,
Noizu.Entity.Meta.Field.field_settings(name: name, store: field_store),
Noizu.Entity.Meta.Persistence.persistence_settings(store: store, table: table),
_context,
_options
) do
as_name = field_store[table][:name] || field_store[store][:name] || name
depth = Map.get(record, :"#{as_name}_depth")
a11 = Map.get(record, :"#{as_name}_a11")
a12 = Map.get(record, :"#{as_name}_a12")
a21 = Map.get(record, :"#{as_name}_a21")
a22 = Map.get(record, :"#{as_name}_a22")
entity = Noizu.Entity.Path.new(%{a11: a11, a12: a12, a21: a21, a22: a22, depth: depth})
(entity && {:ok, {name, entity}}) || {:ok, {name, nil}}
end
end
defimpl Noizu.Entity.Store.Ecto.EntityProtocol, for: [Noizu.Entity.Path] do
defdelegate persist(entity, type, settings, context, options), to: Noizu.Entity.Path.TypeHelper
defdelegate as_record(entity, settings, context, options), to: Noizu.Entity.Path.TypeHelper
defdelegate as_entity(entity, settings, context, options), to: Noizu.Entity.Path.TypeHelper
defdelegate as_entity(entity, record, settings, context, options), to: Noizu.Entity.Path.TypeHelper
defdelegate delete_record(entity, settings, context, options), to: Noizu.Entity.Path.TypeHelper
defdelegate from_record(record, settings, context, options), to: Noizu.Entity.Path.TypeHelper
defdelegate from_record(entity, record, settings, context, options), to: Noizu.Entity.Path.TypeHelper
end
defimpl Noizu.Entity.Store.Ecto.Entity.FieldProtocol, for: [Noizu.Entity.Path] do
defdelegate field_from_record(
field,
record,
field_settings,
persistence_settings,
context,
options
),
to: Noizu.Entity.Path.TypeHelper
defdelegate field_as_record(field, field_settings, persistence_settings, context, options),
to: Noizu.Entity.Path.TypeHelper
end
defimpl Noizu.Entity.Store.Dummy.EntityProtocol, for: [Noizu.Entity.Path] do
defdelegate persist(entity, type, settings, context, options), to: Noizu.Entity.Path.TypeHelper
defdelegate as_record(entity, settings, context, options), to: Noizu.Entity.Path.TypeHelper
defdelegate as_entity(entity, settings, context, options), to: Noizu.Entity.Path.TypeHelper
defdelegate as_entity(entity, record, settings, context, options), to: Noizu.Entity.Path.TypeHelper
defdelegate delete_record(entity, settings, context, options), to: Noizu.Entity.Path.TypeHelper
defdelegate from_record(record, settings, context, options), to: Noizu.Entity.Path.TypeHelper
defdelegate from_record(entity, record, settings, context, options), to: Noizu.Entity.Path.TypeHelper
end
defimpl Noizu.Entity.Store.Dummy.Entity.FieldProtocol, for: [Noizu.Entity.Path] do
defdelegate field_from_record(
field,
record,
field_settings,
persistence_settings,
context,
options
),
to: Noizu.Entity.Path.TypeHelper
defdelegate field_as_record(field, field_settings, persistence_settings, context, options),
to: Noizu.Entity.Path.TypeHelper
end