package server

import (
	"encoding/gob"
	"encoding/json"
	"log"

	"golang.org/x/net/websocket"

	"mcquay.me/bandwidth"
)

const maxMessageSize = 1024

type encoder interface {
	Encode(v interface{}) error
}

type decoder interface {
	Decode(v interface{}) error
}

// StreamCount is the wrapper we use around reads and writes to keep track of
// bandwidths.
type StreamCount struct {
	ws *websocket.Conn
	bw *bandwidth.Bandwidth
}

// StreamCount.Read implements io.Reader
func (sc *StreamCount) Read(p []byte) (n int, err error) {
	n, err = sc.ws.Read(p)
	sc.bw.AddRx <- n
	return n, err
}

// StreamCount.Write implements io.Writer
func (sc *StreamCount) Write(p []byte) (n int, err error) {
	n, err = sc.ws.Write(p)
	sc.bw.AddTx <- n
	return n, err
}

// Close is for cleanup
func (sc *StreamCount) Close() error {
	return sc.ws.Close()
}

// ProtoTalker is the simplest form of struct that talks to consumers of the
// service. There are two important methods here: Sender and Recv.
type ProtoTalker struct {
	enc     encoder
	dec     decoder
	counter *StreamCount
	send    chan Message
	Id      string
}

func NewProtoTalker(id string, ws *websocket.Conn, bw *bandwidth.Bandwidth, encoding string) *ProtoTalker {
	var enc encoder
	var dec decoder
	comptroller := &StreamCount{
		ws: ws,
		bw: bw,
	}
	if encoding == "json" {
		enc = json.NewEncoder(comptroller)
		dec = json.NewDecoder(comptroller)
	} else {
		enc = gob.NewEncoder(comptroller)
		dec = gob.NewDecoder(comptroller)
	}
	return &ProtoTalker{
		send:    make(chan Message, 16),
		enc:     enc,
		dec:     dec,
		counter: comptroller,
		Id:      id,
	}
}

// Sender is the single implementation for data output to clients, both players
// and spectators.
func (pt *ProtoTalker) Sender() {
	log.Printf("%s: %T Sender launched", pt.Id, pt.enc)
	for things := range pt.send {
		err := pt.enc.Encode(things)
		if err != nil {
			log.Println(err)
			break
		}
	}
	pt.counter.Close()
	log.Printf("%s: Sender close", pt.Id)
}

// player uses protoTalker's Sender method, but adds a Recv that knows how to
// deal with game play instructions from the player.
type Player struct {
	Robots      []*Robot
	Instruction Instruction
	ProtoTalker
}

func NewPlayer(id string, ws *websocket.Conn, bw *bandwidth.Bandwidth, encoding string) *Player {
	return &Player{
		Robots:      []*Robot{},
		ProtoTalker: *NewProtoTalker(id, ws, bw, encoding),
	}
}

// Player.Recv is the function responsible for parsing out player instructions
// and sending them to the game.
func (p *Player) Recv() {
	log.Println("starting Recv")
	for {
		var msgs map[string]Instruction
		err := p.dec.Decode(&msgs)
		if err != nil {
			log.Printf("%s: %s", p.Id, err)
			break
		}

		for _, r := range p.Robots {
			msg, ok := msgs[r.Id]

			if !ok {
				continue
			}

			if msg.Repair != nil && *msg.Repair == true {
				r.ActiveScan = false
				r.TargetSpeed = 0
				r.FireAt = nil
				r.MoveTo = nil
				if r.RepairCounter <= 0 {
					r.RepairCounter = 3.0
				}
			} else if msg.Scan != nil && *msg.Scan == true {
				r.RepairCounter = 0
				r.TargetSpeed = 0
				r.FireAt = nil
				r.MoveTo = nil
				r.ActiveScan = true
			} else {
				r.RepairCounter = 0
				r.ActiveScan = false

				// Reapiring halts all other activity
				if msg.MoveTo != nil {
					r.MoveTo = msg.MoveTo
				}
				if msg.Heading != nil {
					r.DesiredHeading = msg.Heading
				}
				if msg.FireAt != nil {
					r.FireAt = msg.FireAt
				} else {
					r.FireAt = nil
				}

				if msg.TargetSpeed != nil {
					r.TargetSpeed = *msg.TargetSpeed
				} else {
					r.TargetSpeed = r.Stats.Speed
				}
			}

			if msg.Probe != nil {
				r.Probe = msg.Probe
				r.ProbeResult = nil
			} else {
				r.Probe = nil
			}

			if msg.Message != nil {
				r.Message = *msg.Message
			}
		}
	}

	log.Printf("%s: Recv close", p.Id)
	p.counter.Close()
}

// Spectator merely sends out game state, does not receive meaningful
// instructions from spectators.
type Spectator struct {
	ProtoTalker
}

func NewSpectator(id string, ws *websocket.Conn, bw *bandwidth.Bandwidth, encoding string) *Spectator {
	return &Spectator{
		ProtoTalker: *NewProtoTalker(id, ws, bw, encoding),
	}
}

// Spectator.Recv is an interesting beast. We had to add it as for whatever
// reason the server would lock up if we weren't reading the empty responses
// from spectators.
func (s *Spectator) Recv() {
	for {
		var msgs map[string]Instruction
		err := s.dec.Decode(&msgs)
		if err != nil {
			log.Printf("%s: %s", s.Id, err)
			break
		}

		// After the first bit of handshaking, the rest of the messages should
		// only be "{}" for spectators, and the following could hold true:
		//
		// if string(buff[:n]) != "{}" {
		// 	log.Printf("protocol breach!!")
		// 	break
		// }
	}
	log.Printf("%s: Recv close", s.Id)
	s.counter.Close()
}
