package presence

import (
	"context"

	"goa.design/clue/log"

	"douglasthrift.net/presence/ifttt"
	"douglasthrift.net/presence/neighbors"
)

type (
	Detector interface {
		Detect(ctx context.Context) error
		Config(config *Config)
		Client(client ifttt.Client)
	}

	detector struct {
		config     *Config
		arp        neighbors.ARP
		interfaces neighbors.Interfaces
		state      neighbors.State
		states     neighbors.HardwareAddrStates
		client     ifttt.Client
	}
)

func NewDetector(config *Config, arp neighbors.ARP, client ifttt.Client) Detector {
	d := &detector{
		arp:    arp,
		state:  neighbors.NewState(),
		states: make(neighbors.HardwareAddrStates, len(config.MACAddresses)),
		client: client,
	}
	d.Config(config)
	return d
}

func (d *detector) Detect(ctx context.Context) error {
	log.Print(ctx, log.KV{K: "msg", V: "detecting presence"}, log.KV{K: "present", V: d.state.Present()})
	err := d.arp.Present(ctx, d.interfaces, d.state, d.states)
	if err != nil {
		return err
	}

	for _, a := range d.config.MACAddresses {
		state := d.states[a]
		log.Print(ctx, log.KV{K: "msg", V: a}, log.KV{K: "present", V: state.Present()}, log.KV{K: "changed", V: state.Changed()})
	}

	log.Print(ctx, log.KV{K: "msg", V: "detected presence"}, log.KV{K: "present", V: d.state.Present()}, log.KV{K: "changed", V: d.state.Changed()})
	if d.state.Changed() {
		event, values, err := d.client.Trigger(ctx, d.state.Present())
		if err != nil {
			d.state.Reset()
			return err
		}
		log.Print(ctx, log.KV{K: "msg", V: "triggered IFTTT"}, log.KV{K: "event", V: event},
			log.KV{K: "value1", V: values.Value1},
			log.KV{K: "value2", V: values.Value2},
			log.KV{K: "value3", V: values.Value3})
	}

	return nil
}

func (d *detector) Config(config *Config) {
	d.config = config
	d.interfaces = make(neighbors.Interfaces, len(config.Interfaces))
	for _, i := range config.Interfaces {
		d.interfaces[i] = true
	}

	states := make(map[string]bool, len(d.states))
	for a := range d.states {
		states[a] = true
	}
	for _, a := range config.MACAddresses {
		if states[a] {
			states[a] = false
		} else {
			d.states[a] = neighbors.NewState()
		}
	}
	for a, ok := range states {
		if ok {
			delete(d.states, a)
		}
	}
}

func (d *detector) Client(client ifttt.Client) {
	d.client = client
}