package network

import (
	"bufio"
	"encoding/binary"
	"io"

	//"math"
	"net"
	"time"

	"github.com/Arceliar/phony"

	"github.com/Arceliar/ironwood/types"
)

// TODO? copy relevant config info to structs here, to avoid needing to dereference pointers all the way back to the core

type peerPort uint64

type peers struct {
	phony.Inbox // Used to create/remove peers
	core        *core
	ports       map[peerPort]struct{}
	peers       map[publicKey]map[*peer]struct{}
	order       uint64 // global counter for (*peer).order
}

func (ps *peers) init(c *core) {
	ps.core = c
	ps.ports = make(map[peerPort]struct{})
	ps.peers = make(map[publicKey]map[*peer]struct{})
}

func (ps *peers) addPeer(key publicKey, conn net.Conn, prio uint8) (*peer, error) {
	var p *peer
	var err error
	ps.core.pconn.closeMutex.Lock()
	defer ps.core.pconn.closeMutex.Unlock()
	select {
	case <-ps.core.pconn.closed:
		return nil, types.ErrClosed
	default:
	}
	phony.Block(ps, func() {
		var port peerPort
		if keyPeers, isIn := ps.peers[key]; isIn {
			for p := range keyPeers {
				port = p.port
				break
			}
		} else {
			// Allocate port
			for idx := 1; ; idx++ { // skip 0
				if _, isIn := ps.ports[peerPort(idx)]; isIn {
					continue
				}
				port = peerPort(idx)
				break
			}
			ps.ports[port] = struct{}{}
			ps.peers[key] = make(map[*peer]struct{})
		}
		p = new(peer)
		p.peers = ps
		p.conn = conn
		p.done = make(chan struct{})
		p.key = key
		p.port = port
		p.prio = prio
		p.monitor.peer = p
		p.monitor.pDelay = ps.core.config.peerTimeout // It doesn't make sense to start the ping delay any shorter than this
		p.writer.peer = p
		p.writer.wbuf = bufio.NewWriter(p.conn)
		p.order = ps.order
		ps.order++
		ps.peers[p.key][p] = struct{}{}
	})
	return p, err
}

func (ps *peers) removePeer(p *peer) error {
	var err error
	phony.Block(ps, func() {
		kps := ps.peers[p.key]
		if _, isIn := kps[p]; !isIn {
			err = types.ErrPeerNotFound
		} else {
			delete(kps, p)
			if len(kps) == 0 {
				delete(ps.peers, p.key)
				delete(ps.ports, p.port)
			}
		}
	})
	return err
}

type peer struct {
	phony.Inbox // Only used to process or send some protocol traffic
	peers       *peers
	conn        net.Conn
	done        chan struct{}
	key         publicKey
	port        peerPort
	prio        uint8
	queue       packetQueue
	order       uint64 // order in which peers were connected (relative uptime)
	monitor     peerMonitor
	writer      peerWriter
	ready       bool // is the writer ready for traffic?
}

type peerMonitor struct {
	phony.Inbox
	peer           *peer
	keepAliveTimer *time.Timer
	pDelay         time.Duration
	deadlined      bool
}

func (m *peerMonitor) keepAlive() {
	m.Act(nil, func() {
		select {
		case <-m.peer.done:
			return
		default:
		}
		m.peer.writer.Act(m, func() {
			m.peer.writer._write([]byte{0x01, byte(wireKeepAlive)}, wireKeepAlive)
		})
	})
}

func (m *peerMonitor) sent(pType wirePacketType) {
	m.Act(&m.peer.writer, func() {
		if m.keepAliveTimer != nil {
			// We're sending a packet, so we definitely don't need to send a keepalive after this
			m.keepAliveTimer.Stop()
			m.keepAliveTimer = nil
		}
		switch {
		case m.deadlined:
			return
		case pType == wireDummy:
		case pType == wireKeepAlive:
		default:
			// We're sending non-keepalive traffic
			// This means we expect some kind of acknowledgement (at least a keepalive)
			// Set a read deadline for that (and make a note that we did so)
			m.peer.conn.SetReadDeadline(time.Now().Add(m.peer.peers.core.config.peerTimeout))
			m.deadlined = true
		}
	})
}

func (m *peerMonitor) recv(pType wirePacketType) {
	m.Act(nil, func() {
		m.peer.conn.SetReadDeadline(time.Time{})
		m.deadlined = false
		switch {
		case m.keepAliveTimer != nil:
		case pType == wireDummy:
		case pType == wireKeepAlive:
		default:
			// We just received non-keepalive traffic
			// The other side is expecting some kind of response, at least a keepalive
			// We set a timer to trigger a response later, if we don't send any traffic in the mean time
			select {
			case <-m.peer.done:
			default:
				m.keepAliveTimer = time.AfterFunc(m.peer.peers.core.config.peerKeepAliveDelay, m.keepAlive)
			}
		}
	})
}

type peerWriter struct {
	phony.Inbox
	peer *peer
	wbuf *bufio.Writer
	seq  uint64
}

func (w *peerWriter) _write(bs []byte, pType wirePacketType) {
	w.peer.monitor.sent(pType)
	// _, _ = w.peer.conn.Write(bs)
	_, _ = w.wbuf.Write(bs)
	w.seq++
	seq := w.seq
	w.Act(nil, func() {
		if seq == w.seq {
			w.peer.pop() // Ask for more traffic to send
		}
	})
}

func (w *peerWriter) sendPacket(pType wirePacketType, data wireEncodeable) {
	w.Act(nil, func() {
		bufSize := uint64(data.size() + 1)
		if bufSize > w.peer.peers.core.config.peerMaxMessageSize {
			return
		}
		writeBuf := allocBytes(0)
		defer freeBytes(writeBuf)
		// The +1 is from 1 byte for the pType
		writeBuf = binary.AppendUvarint(writeBuf[:], bufSize)
		var err error
		writeBuf, err = wireEncode(writeBuf, byte(pType), data)
		if err != nil {
			panic(err)
		}
		w._write(writeBuf, pType)
		switch tr := data.(type) {
		case *traffic:
			freeTraffic(tr)
		default:
			// Not a special case, don't free anything
		}
	})
}

func (p *peer) handler() error {
	defer func() {
		p.peers.core.router.removePeer(nil, p)
	}()
	defer p.monitor.Act(nil, func() {
		if p.monitor.keepAliveTimer != nil {
			p.monitor.keepAliveTimer.Stop()
			p.monitor.keepAliveTimer = nil
		}
	})
	defer close(p.done)
	p.conn.SetDeadline(time.Time{})
	// Add peer to the router, to kick off protocol exchanges
	p.peers.core.router.addPeer(p, p)
	// Now allocate buffers and start reading / handling packets...
	rbuf := bufio.NewReader(p.conn)
	for {
		var usize uint64
		var err error
		if usize, err = binary.ReadUvarint(rbuf); err != nil {
			return err
		}
		if usize > p.peers.core.config.peerMaxMessageSize {
			return types.ErrOversizedMessage
		}
		size := int(usize)
		bs := allocBytes(size)
		if _, err = io.ReadFull(rbuf, bs); err != nil {
			freeBytes(bs)
			return err
		}
		phony.Block(p, func() {
			err = p._handlePacket(bs)
		})
		freeBytes(bs)
		if err != nil {
			return err
		}
	}
}

func (p *peer) _handlePacket(bs []byte) error {
	// Note: this function should be non-blocking.
	// Individual handlers should send actor messages as needed.
	if len(bs) == 0 {
		return types.ErrEmptyMessage
	}
	pType := wirePacketType(bs[0])
	p.monitor.recv(pType)
	switch pType {
	case wireDummy:
		return nil
	case wireKeepAlive:
		return nil
	case wireProtoSigReq:
		return p._handleSigReq(bs[1:])
	case wireProtoSigRes:
		return p._handleSigRes(bs[1:])
	case wireProtoAnnounce:
		return p._handleAnnounce(bs[1:])
	case wireProtoBloomFilter:
		return p._handleBloom(bs[1:])
	case wireProtoPathLookup:
		return p._handlePathLookup(bs[1:])
	case wireProtoPathNotify:
		return p._handlePathNotify(bs[1:])
	case wireProtoPathBroken:
		return p._handlePathBroken(bs[1:])
	case wireTraffic:
		return p._handleTraffic(bs[1:])
	default:
		return types.ErrUnrecognizedMessage
	}
}

func (p *peer) sendDirect(from phony.Actor, pType wirePacketType, data wireEncodeable) {
	p.Act(from, func() {
		p.writer.sendPacket(pType, data)
	})
}

func (p *peer) _handleSigReq(bs []byte) error {
	req := new(routerSigReq)
	if err := req.decode(bs); err != nil {
		return err
	}
	p.peers.core.router.handleRequest(p, p, req)
	return nil
}

func (p *peer) sendSigReq(from phony.Actor, req *routerSigReq) {
	p.sendDirect(from, wireProtoSigReq, req)
}

func (p *peer) _handleSigRes(bs []byte) error {
	res := new(routerSigRes)
	if err := res.decode(bs); err != nil {
		return err
	}
	if !res.check(p.peers.core.crypto.publicKey, p.key) {
		return types.ErrBadMessage
	}
	p.peers.core.router.handleResponse(p, p, res)
	return nil
}

func (p *peer) sendSigRes(from phony.Actor, res *routerSigRes) {
	p.sendDirect(from, wireProtoSigRes, res)
}

func (p *peer) _handleAnnounce(bs []byte) error {
	ann := new(routerAnnounce)
	if err := ann.decode(bs); err != nil {
		return err
	}
	if !ann.check() {
		return types.ErrBadMessage
	}
	p.peers.core.router.handleAnnounce(p, p, ann)
	return nil
}

func (p *peer) sendAnnounce(from phony.Actor, ann *routerAnnounce) {
	p.sendDirect(from, wireProtoAnnounce, ann)
}

func (p *peer) _handleBloom(bs []byte) error {
	b := newBloom()
	if err := b.decode(bs); err != nil {
		return err
	}
	p.peers.core.router.blooms.handleBloom(p, b)
	return nil
}

func (p *peer) sendBloom(from phony.Actor, b *bloom) {
	p.sendDirect(from, wireProtoBloomFilter, b)
}

func (p *peer) _handlePathLookup(bs []byte) error {
	lookup := new(pathLookup)
	if err := lookup.decode(bs); err != nil {
		return err
	}
	p.peers.core.router.pathfinder.handleLookup(p, lookup)
	return nil
}

func (p *peer) _handlePathNotify(bs []byte) error {
	notify := new(pathNotify)
	if err := notify.decode(bs); err != nil {
		return err
	}
	p.peers.core.router.pathfinder.handleNotify(p, notify)
	return nil
}

func (p *peer) sendPathNotify(from phony.Actor, notify *pathNotify) {
	//p.sendDirect(from, wireProtoPathNotify, notify)
	p.sendQueued(from, notify)
}

func (p *peer) _handlePathBroken(bs []byte) error {
	broken := new(pathBroken)
	if err := broken.decode(bs); err != nil {
		return err
	}
	p.peers.core.router.pathfinder.handleBroken(p, broken)
	return nil
}

func (p *peer) sendPathBroken(from phony.Actor, broken *pathBroken) {
	//p.sendDirect(from, wireProtoPathBroken, broken)
	p.sendQueued(from, broken)
}

func (p *peer) _handleTraffic(bs []byte) error {
	tr := allocTraffic()
	if err := tr.decode(bs); err != nil {
		return err // This is just to check that it unmarshals correctly
	}
	p.peers.core.router.handleTraffic(p, tr)
	return nil
}

func (p *peer) sendTraffic(from phony.Actor, tr *traffic) {
	p.sendQueued(from, tr)
}

func (p *peer) sendQueued(from phony.Actor, packet pqPacket) {
	p.Act(from, func() {
		p._push(packet)
	})
}

func (p *peer) _push(packet pqPacket) {
	if p.ready {
		p.writer.sendPacket(packet.wireType(), packet)
		p.ready = false
		return
	}
	// We're waiting, so queue the packet up for later
	if info, ok := p.queue.peek(); ok && time.Since(info.time) > 25*time.Millisecond {
		// The queue already has a significant delay
		// Drop the oldest packet from the larget queue to make room
		p.queue.drop()
	}
	// Add the packet to the queue
	p.queue.push(packet)
}

func (p *peer) pop() {
	p.Act(nil, func() {
		if info, ok := p.queue.pop(); ok {
			p.writer.sendPacket(info.packet.wireType(), info.packet)
		} else {
			p.ready = true
			p.writer.Act(nil, func() {
				p.writer.wbuf.Flush()
			})
		}
	})
}
