package react

import "strconv"

const testVersion = 4

// MyReactor implements Reactor
type MyReactor struct {
	lastId int
	cells  []Cell
}

// New creates a new Reactor
func New() *MyReactor {
	r := &MyReactor{}
	return r
}

// CreateInput builds an input cell and adds it to the reactor
func (r *MyReactor) CreateInput(i int) InputCell {
	r.lastId++
	ic := MyCell{val: i, id: r.lastId}
	ic.previousVal = ic.Value()
	return &ic
}

// CreateCompute1 Takes a cell and a function and returns a compute cell
// which has a value based on running the function on the cells value.
func (r *MyReactor) CreateCompute1(c Cell, f func(int) int) ComputeCell {
	r.lastId++
	cc := &MyCell{id: r.lastId, isComputed: true}
	cc.compVal = func() int { return f(c.Value()) }
	cc.previousVal = cc.Value()
	c.(*MyCell).addDependent(cc)
	return cc
}

// CreateCompute2 Takes two cells and a function and returns a compute cell
// which has a value based on running the function on the cells values.
func (r *MyReactor) CreateCompute2(c1, c2 Cell, f func(int, int) int) ComputeCell {
	r.lastId++
	cc := &MyCell{id: r.lastId, isComputed: true}
	cc.compVal = func() int { return f(c1.Value(), c2.Value()) }
	cc.previousVal = cc.Value()
	c1.(*MyCell).addDependent(cc)
	c2.(*MyCell).addDependent(cc)
	return cc
}

// MyCell implements the all Cell interfaces
type MyCell struct {
	id             int
	isComputed     bool
	val            int
	compVal        func() int
	lastCallbackId int
	callbacks      map[int]func(int)
	dependents     []Cell
	previousVal    int
}

// Value returns the value of the cell
func (c MyCell) Value() int {
	if c.isComputed {
		return c.compVal()
	}
	return c.val
}

// SetValue sets the value on the cell
func (c *MyCell) SetValue(i int) {
	if i == c.val || c.isComputed {
		// No change or this is a computed cell, just return
		return
	}
	c.previousVal = c.val
	c.val = i
	c.updated()
}

func i(v int) string {
	return strconv.Itoa(v)
}

func (c *MyCell) updated() {
	if c.Value() == c.previousVal {
		// Value didn't change, done.
		return
	}
	c.previousVal = c.Value()
	// Triggers callbacks
	for _, v := range c.callbacks {
		v(c.Value())
	}
	// Hit 'updated' on all dependents
	for _, v := range c.dependents {
		v.(*MyCell).updated()
	}
}

func (c *MyCell) addDependent(nc Cell) {
	c.dependents = append(c.dependents, nc)
}

func (c *MyCell) AddCallback(cb func(int)) CallbackHandle {
	if c.lastCallbackId == 0 {
		c.callbacks = make(map[int]func(int))
	}
	c.lastCallbackId++
	c.callbacks[c.lastCallbackId] = cb
	return c.lastCallbackId
}

func (c *MyCell) RemoveCallback(cbh CallbackHandle) {
	delete(c.callbacks, cbh.(int))
}
