# Getting Started

## Installation

Add `ex_ring_ring` to your dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:ex_ring_ring, "~> 0.1.0"}
  ]
end
```

Then fetch the dependency:

```bash
mix deps.get
```

## Basic Usage

### Creating Nodes

Nodes are the members of your hash ring. Create them with `ExRingRing.list_to_nodes/1`:

```elixir
# List of keys (data defaults to key, weight defaults to 1)
nodes = ExRingRing.list_to_nodes([:a, :b, :c])

# Key-data pairs
nodes = ExRingRing.list_to_nodes([
  {:node1, "192.168.1.1"},
  {:node2, "192.168.1.2"}
])

# With weights (higher weight = more virtual nodes = more load)
nodes = ExRingRing.list_to_nodes([
  {:node1, "192.168.1.1", [weight: 2]},
  {:node2, "192.168.1.2", [weight: 1]}
])
```

### Creating a Ring

```elixir
# From pre-built nodes
nodes = ExRingRing.list_to_nodes([:a, :b, :c])
ring = ExRingRing.make(nodes)

# Directly from keys (convenience)
ring = ExRingRing.make_from_keys([:a, :b, :c])
```

### Finding Nodes

Once you have a ring, find which node owns a given key:

```elixir
ring = ExRingRing.make_from_keys([:a, :b, :c, :d, :e])

# Find the primary node for an item
{:ok, node} = ExRingRing.find_node(ring, "user_42")
IO.puts(node.key)  # => :b (deterministic)

# :error when ring is empty
empty_ring = ExRingRing.make([])
ExRingRing.find_node(empty_ring, "anything")  # => :error
```

### Replication

Collect N distinct nodes for an item (primary + replicas):

```elixir
ring = ExRingRing.make_from_keys([:a, :b, :c, :d, :e])

# First 3 unique nodes for this item
nodes = ExRingRing.collect_nodes(ring, "my_data", 3)
# => [%Node{key: :e}, %Node{key: :a}, %Node{key: :d}]

# Collects all nodes when count exceeds ring size
ExRingRing.collect_nodes(ring, "my_data", 10)
# => [%Node{key: :e}, %Node{key: :a}, %Node{key: :d}, ...]  # all 5 nodes
```

### Adding and Removing Nodes

```elixir
ring = ExRingRing.make_from_keys([:a, :b, :c])

# Add a single node
ring = ExRingRing.add_node(ring, ExRingRing.Domain.Entities.Node.make(:d))

# Add multiple nodes at once
ring = ExRingRing.add_nodes(ring, ExRingRing.list_to_nodes([:e, :f]))

# Remove by key
ring = ExRingRing.remove_node(ring, :a)

# Remove multiple at once
ring = ExRingRing.remove_nodes(ring, [:b, :c])
```

> **Note**: Adding/removing nodes preserves consistent hashing — only `1/N` of keys
> remap on average, where N is the number of nodes.

### Checking Ring State

```elixir
ExRingRing.is_ring(ring)            # => true
ExRingRing.get_node_count(ring)     # => 3
ExRingRing.get_nodes(ring)          # => [%Node{...}, ...]
```

## Complete Example

```elixir
# Simulate a distributed cache with 5 nodes
nodes = ExRingRing.list_to_nodes([:cache_1, :cache_2, :cache_3, :cache_4, :cache_5])
ring = ExRingRing.make(nodes)

keys = Enum.map(1..100, &"session_#{&1}")

# Find where each session key lives
Enum.each(keys, fn key ->
  {:ok, node} = ExRingRing.find_node(ring, key)
  IO.puts("#{key} -> #{node.key}")
end)

# Simulate a node failure
ring = ExRingRing.remove_node(ring, :cache_3)

# Most keys still map to the same node — only ~20% remap
```
