package main

import (
	"errors"
	"fmt"
)

type Shard struct {
	Generation    int
	Writable      bool
	Value, Offset int
}

func (s *Shard) Write(generation, value int) error {
	if !s.Writable || generation != s.Generation {
		return errors.New("stale route")
	}
	s.Value = value
	s.Offset++
	return nil
}

type Migration struct {
	Source, Target Shard
	Phase          string
}

func NewMigration() *Migration {
	return &Migration{Source: Shard{Generation: 7, Writable: true, Value: 10, Offset: 1}, Phase: "copy"}
}
func (m *Migration) Snapshot() {
	m.Target = Shard{Generation: 8, Value: m.Source.Value, Offset: m.Source.Offset}
	m.Phase = "catchup"
}
func (m *Migration) Freeze()  { m.Source.Writable = false; m.Phase = "frozen" }
func (m *Migration) CatchUp() { m.Target.Value = m.Source.Value; m.Target.Offset = m.Source.Offset }
func (m *Migration) Cutover() error {
	if m.Phase != "frozen" || m.Source.Writable || m.Target.Offset != m.Source.Offset || m.Target.Value != m.Source.Value {
		return errors.New("migration not ready")
	}
	m.Target.Writable = true
	m.Phase = "serving"
	return nil
}
func main() {
	m := NewMigration()
	m.Snapshot()
	must(m.Source.Write(7, 11))
	fmt.Println("source/target before catchup:", m.Source.Value, m.Target.Value)
	fmt.Println("early cutover:", m.Cutover())
	m.Freeze()
	m.CatchUp()
	must(m.Cutover())
	fmt.Println("old source:", m.Source.Write(7, 99))
	fmt.Println("old generation to target:", m.Target.Write(7, 99))
	must(m.Target.Write(8, 12))
	fmt.Println("new owner:", m.Target.Value, "generation:", m.Target.Generation)
}
func must(e error) {
	if e != nil {
		panic(e)
	}
}
