# Memento Pattern

## Problem

A text editor needs save and undo functionality. Directly exposing the editor's internal state to external code for snapshotting breaks encapsulation. If the state structure changes (e.g., adding cursor position), all snapshot code must be updated. You need a way to capture and restore state without violating encapsulation.

## Solution

The editor (Originator) creates **Memento** objects — opaque snapshots of its state. A **Caretaker** stores these mementos in a history stack. To undo, the caretaker retrieves a memento and passes it back to the editor for restoration. The caretaker never inspects or modifies the memento's contents.

**Key participants:**
- **Originator** — the Editor that creates and restores from mementos
- **Memento** — an immutable snapshot of the editor's state
- **Caretaker** — manages the history stack of mementos

## When to Use

- You need to save and restore object state (undo, checkpoints, rollback)
- Direct access to internal state would violate encapsulation
- A history of states must be maintained (not just the previous one)
- You need transactional behavior — commit or rollback

## When to Avoid

- The state is very large — each snapshot consumes significant memory
- Snapshots are rarely used — the overhead of creating them isn't justified
- The state is simple enough to just copy a few fields directly
- You need to modify past states — mementos should be immutable

## Pseudocode

```
CLASS Memento:
    content: string  // immutable snapshot
    cursor: int

CLASS Editor:
    content, cursor

    method save() -> Memento:
        RETURN Memento(content, cursor)

    method restore(memento):
        content = memento.content
        cursor = memento.cursor

CLASS History:
    stack = []

    method push(memento):
        stack.push(memento)

    method pop() -> Memento:
        RETURN stack.pop()
```

## Python

```python
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime


@dataclass(frozen=True)
class Memento:
    """Immutable snapshot of editor state."""
    content: str
    cursor: int
    timestamp: str


class Editor:
    def __init__(self) -> None:
        self.content = ""
        self.cursor = 0

    def type_text(self, text: str) -> None:
        self.content = (
            self.content[:self.cursor] + text + self.content[self.cursor:]
        )
        self.cursor += len(text)

    def delete(self, count: int) -> None:
        start = max(0, self.cursor - count)
        self.content = self.content[:start] + self.content[self.cursor:]
        self.cursor = start

    def move_cursor(self, position: int) -> None:
        self.cursor = max(0, min(position, len(self.content)))

    def save(self) -> Memento:
        return Memento(
            content=self.content,
            cursor=self.cursor,
            timestamp=datetime.now().strftime("%H:%M:%S"),
        )

    def restore(self, memento: Memento) -> None:
        self.content = memento.content
        self.cursor = memento.cursor

    def __str__(self) -> str:
        display = self.content[:self.cursor] + "|" + self.content[self.cursor:]
        return f"'{display}' (cursor={self.cursor})"


class History:
    def __init__(self) -> None:
        self.snapshots: list[Memento] = []

    def push(self, memento: Memento) -> None:
        self.snapshots.append(memento)

    def pop(self) -> Memento | None:
        if not self.snapshots:
            return None
        return self.snapshots.pop()

    @property
    def size(self) -> int:
        return len(self.snapshots)


def main() -> None:
    editor = Editor()
    history = History()

    # Type and save
    editor.type_text("Hello")
    history.push(editor.save())
    print(f"After 'Hello':        {editor}")

    editor.type_text(" World")
    history.push(editor.save())
    print(f"After ' World':       {editor}")

    editor.type_text("!")
    history.push(editor.save())
    print(f"After '!':            {editor}")

    # Move cursor and insert
    editor.move_cursor(5)
    editor.type_text(" Beautiful")
    history.push(editor.save())
    print(f"After insert at 5:    {editor}")

    # Undo operations
    print(f"\n--- Undo (history has {history.size} snapshots) ---")
    memento = history.pop()
    if memento:
        # Pop current state, restore previous
        memento = history.pop()
        if memento:
            editor.restore(memento)
            print(f"After undo:           {editor}")

    memento = history.pop()
    if memento:
        editor.restore(memento)
        print(f"After undo:           {editor}")

    memento = history.pop()
    if memento:
        editor.restore(memento)
        print(f"After undo:           {editor}")

    print(f"\nHistory empty: {history.size == 0}")


if __name__ == "__main__":
    main()
```

**Output:**
```
After 'Hello':        'Hello|' (cursor=5)
After ' World':       'Hello World|' (cursor=11)
After '!':            'Hello World!|' (cursor=12)
After insert at 5:    'Hello Beautiful| World!' (cursor=15)

--- Undo (history has 4 snapshots) ---
After undo:           'Hello World!|' (cursor=12)
After undo:           'Hello World|' (cursor=11)
After undo:           'Hello|' (cursor=5)

History empty: True
```

## Go

```go
package main

import (
	"fmt"
	"time"
)

type Memento struct {
	content   string
	cursor    int
	timestamp string
}

type Editor struct {
	content string
	cursor  int
}

func (e *Editor) TypeText(text string) {
	e.content = e.content[:e.cursor] + text + e.content[e.cursor:]
	e.cursor += len(text)
}

func (e *Editor) Delete(count int) {
	start := e.cursor - count
	if start < 0 {
		start = 0
	}
	e.content = e.content[:start] + e.content[e.cursor:]
	e.cursor = start
}

func (e *Editor) MoveCursor(pos int) {
	if pos < 0 {
		pos = 0
	}
	if pos > len(e.content) {
		pos = len(e.content)
	}
	e.cursor = pos
}

func (e *Editor) Save() Memento {
	return Memento{
		content:   e.content,
		cursor:    e.cursor,
		timestamp: time.Now().Format("15:04:05"),
	}
}

func (e *Editor) Restore(m Memento) {
	e.content = m.content
	e.cursor = m.cursor
}

func (e *Editor) String() string {
	return fmt.Sprintf("'%s|%s' (cursor=%d)",
		e.content[:e.cursor], e.content[e.cursor:], e.cursor)
}

type History struct {
	snapshots []Memento
}

func (h *History) Push(m Memento) {
	h.snapshots = append(h.snapshots, m)
}

func (h *History) Pop() (Memento, bool) {
	if len(h.snapshots) == 0 {
		return Memento{}, false
	}
	m := h.snapshots[len(h.snapshots)-1]
	h.snapshots = h.snapshots[:len(h.snapshots)-1]
	return m, true
}

func (h *History) Size() int {
	return len(h.snapshots)
}

func main() {
	editor := &Editor{}
	history := &History{}

	editor.TypeText("Hello")
	history.Push(editor.Save())
	fmt.Printf("After 'Hello':        %s\n", editor)

	editor.TypeText(" World")
	history.Push(editor.Save())
	fmt.Printf("After ' World':       %s\n", editor)

	editor.TypeText("!")
	history.Push(editor.Save())
	fmt.Printf("After '!':            %s\n", editor)

	editor.MoveCursor(5)
	editor.TypeText(" Beautiful")
	history.Push(editor.Save())
	fmt.Printf("After insert at 5:    %s\n", editor)

	fmt.Printf("\n--- Undo (history has %d snapshots) ---\n", history.Size())

	// Pop current state, restore previous
	history.Pop()
	if m, ok := history.Pop(); ok {
		editor.Restore(m)
		fmt.Printf("After undo:           %s\n", editor)
	}
	if m, ok := history.Pop(); ok {
		editor.Restore(m)
		fmt.Printf("After undo:           %s\n", editor)
	}
	if m, ok := history.Pop(); ok {
		editor.Restore(m)
		fmt.Printf("After undo:           %s\n", editor)
	}

	fmt.Printf("\nHistory empty: %v\n", history.Size() == 0)
}
```

**Output:**
```
After 'Hello':        'Hello|' (cursor=5)
After ' World':       'Hello World|' (cursor=11)
After '!':            'Hello World!|' (cursor=12)
After insert at 5:    'Hello Beautiful| World!' (cursor=15)

--- Undo (history has 4 snapshots) ---
After undo:           'Hello World!|' (cursor=12)
After undo:           'Hello World|' (cursor=11)
After undo:           'Hello|' (cursor=5)

History empty: true
```

## JavaScript

```javascript
class Memento {
  constructor(content, cursor) {
    this.content = content;
    this.cursor = cursor;
    this.timestamp = new Date().toLocaleTimeString();
    Object.freeze(this); // immutable
  }
}

class Editor {
  constructor() {
    this.content = "";
    this.cursor = 0;
  }

  typeText(text) {
    this.content =
      this.content.slice(0, this.cursor) + text + this.content.slice(this.cursor);
    this.cursor += text.length;
  }

  delete(count) {
    const start = Math.max(0, this.cursor - count);
    this.content = this.content.slice(0, start) + this.content.slice(this.cursor);
    this.cursor = start;
  }

  moveCursor(pos) {
    this.cursor = Math.max(0, Math.min(pos, this.content.length));
  }

  save() {
    return new Memento(this.content, this.cursor);
  }

  restore(memento) {
    this.content = memento.content;
    this.cursor = memento.cursor;
  }

  toString() {
    return `'${this.content.slice(0, this.cursor)}|${this.content.slice(this.cursor)}' (cursor=${this.cursor})`;
  }
}

class History {
  constructor() {
    this.snapshots = [];
  }

  push(memento) {
    this.snapshots.push(memento);
  }

  pop() {
    return this.snapshots.pop() || null;
  }

  get size() {
    return this.snapshots.length;
  }
}

// --- Demo ---
const editor = new Editor();
const history = new History();

editor.typeText("Hello");
history.push(editor.save());
console.log(`After 'Hello':        ${editor}`);

editor.typeText(" World");
history.push(editor.save());
console.log(`After ' World':       ${editor}`);

editor.typeText("!");
history.push(editor.save());
console.log(`After '!':            ${editor}`);

editor.moveCursor(5);
editor.typeText(" Beautiful");
history.push(editor.save());
console.log(`After insert at 5:    ${editor}`);

console.log(`\n--- Undo (history has ${history.size} snapshots) ---`);

// Pop current, restore previous
history.pop();
let memento = history.pop();
if (memento) {
  editor.restore(memento);
  console.log(`After undo:           ${editor}`);
}
memento = history.pop();
if (memento) {
  editor.restore(memento);
  console.log(`After undo:           ${editor}`);
}
memento = history.pop();
if (memento) {
  editor.restore(memento);
  console.log(`After undo:           ${editor}`);
}

console.log(`\nHistory empty: ${history.size === 0}`);
```

**Output:**
```
After 'Hello':        'Hello|' (cursor=5)
After ' World':       'Hello World|' (cursor=11)
After '!':            'Hello World!|' (cursor=12)
After insert at 5:    'Hello Beautiful| World!' (cursor=15)

--- Undo (history has 4 snapshots) ---
After undo:           'Hello World!|' (cursor=12)
After undo:           'Hello World|' (cursor=11)
After undo:           'Hello|' (cursor=5)

History empty: true
```

## Related Patterns

- **Command** — commands are often stored alongside mementos to support undo/redo; command performs the action, memento records the state.
- **Event Sourcing** — an architectural version of memento: all state changes are stored as an ordered log of events.
- **Prototype** — prototype cloning can serve as a lightweight alternative to memento for simple state snapshots.
- **State** — state pattern manages transitions between states; memento can capture each state for later restoration.
