package server

import (
	"log"

	"golang.org/x/net/websocket"

	v "hackerbots.us/vector"
)

// GameID is essentially the name of the game we want to join
type GameID struct {
	Id string `json:"id"`
}

// PlayerID is the internal hash we give to a client
type PlayerID struct {
	Type string `json:"type"`
	Hash string `json:"id"`
}

func NewPlayerID(id string) *PlayerID {
	return &PlayerID{
		Type: "idreq",
		Hash: id,
	}
}

// ClientID is how a player wants to be known
type ClientID struct {
	Type      string `json:"type"`
	Name      string `json:"name"`
	Useragent string `json:"useragent"`
}

// ClientID.Valid is used to be sure the player connecting is of appropriate
// type.
func (c *ClientID) Valid() (bool, string) {
	switch c.Type {
	case "robot", "spectator":
		return true, ""
	}
	return false, "useragent must be 'robot' or 'spectator'"
}

// ClientConfig embodies a map of stats requests
type ClientConfig struct {
	ID    string                  `json:"id"`
	Stats map[string]StatsRequest `json:"stats"`
}

// ClientConfig.Valid is what determins if a player has asked for too many
// points.
func (config ClientConfig) Valid(max int) bool {
	total := 0
	for _, s := range config.Stats {
		total += (s.Speed +
			s.Hp +
			s.WeaponRadius +
			s.ScannerRadius +
			s.Acceleration +
			s.TurnSpeed +
			s.FireRate +
			s.WeaponDamage +
			s.WeaponSpeed)
	}
	if total > max {
		return false
	}
	return true
}

// BoardSize is the response containing the geometry of the requested game.
type BoardSize struct {
	Width  float64 `json:"width"`
	Height float64 `json:"height"`
}

// GameParam is sent to the client to tell them of the geometry of the game
// requested, how many points they may use, and what encoding the server will
// use to communicate with the client.
type GameParam struct {
	// TODO: should have information about max points in here
	BoardSize BoardSize `json:"boardsize"`
	MaxPoints int       `json:"max_points"`
	Encoding  string    `json:"encoding"`
	Type      string    `json:"type"`
}

// Handshake is simply the response to a client to let them know if the number
// of stats they've asked for is reasonable. If false it means try again.
type Handshake struct {
	ID      string `json:"id"`
	Success bool   `json:"success"`
	Type    string `json:"type"`
}

func NewHandshake(id string, success bool) *Handshake {
	return &Handshake{
		ID:      id,
		Success: success,
		Type:    "handshake",
	}
}

// Message is an empty interface used to send out arbitrary JSON/gob to
// clients, both players/spectators. We might send out Boardstate or GameOver,
// hence the interface{}.
type Message interface{}

// Boardstate is the main struct calculated every tick (per player) and sent
// out to clients. It contains the appropriate subset of all data needed by
// each player/spectator.
type Boardstate struct {
	MyRobots    []Robot      `json:"my_robots"`
	OtherRobots []OtherRobot `json:"robots"`
	Projectiles []Projectile `json:"projectiles"`
	Splosions   []Splosion   `json:"splosions"`
	Obstacles   []Obstacle   `json:"objects"`
	Type        string       `json:"type"`
	Turn        int          `json:"turn"`
	AllBots     []BotHealth  `json:"all_bots"`
	Messages    []string     `json:"messages"`
}

func NewBoardstate() *Boardstate {
	return &Boardstate{
		MyRobots:    []Robot{},
		OtherRobots: []OtherRobot{},
		Projectiles: []Projectile{},
		Splosions:   []Splosion{},
		AllBots:     []BotHealth{},
		Type:        "boardstate",
	}
}

// Special outbound message with a []string of winners.
type GameOver struct {
	Winners []string `json:"winners"`
	Type    string   `json:"type"`
}

func NewGameOver() *GameOver {
	return &GameOver{
		Type:    "gameover",
		Winners: make([]string, 0),
	}
}

// Failure is a simple stuct that is typically converted to JSON and  sent out
// to the clients so they can know why something has failed.
type Failure struct {
	Reason string `json:"reason"`
	Type   string `json:"type"`
}

func NewFailure(reason string) *Failure {
	return &Failure{
		Reason: reason,
		Type:   "failure",
	}
}

// Controller.AddPlayer is the HTTP -> websocket route that is used to
// negociate a connection with a player/spectator.
func (c *Controller) AddPlayer(ws *websocket.Conn) {
	var gid GameID
	err := websocket.JSON.Receive(ws, &gid)
	if err != nil {
		log.Println("problem parsing the requested game id")
		return
	}

	game := c.Games.Get(gid.Id)
	if game == nil {
		var err error
		game, err = NewGame(
			gid.Id,
			float64(c.Conf.Width),
			float64(c.Conf.Height),
			c.Conf.Tick,
			c.Conf.MaxPoints,
			"",
		)
		game.defaultObstacles = c.Conf.Obstacles
		game.obstacleCount = c.Conf.ObstacleCount
		log.Printf("%t", len(game.defaultObstacles) == 0)
		if len(game.defaultObstacles) == 0 {
			game.obstacles = GenerateObstacles(
				game.obstacleCount,
				game.width,
				game.height,
			)
		} else {
			game.obstacles = c.Conf.Obstacles
		}
		if err != nil {
			log.Printf("problem creating game: %s", gid.Id)
			websocket.JSON.Send(ws, NewFailure("game creation error"))
			return
		}
		go game.run()
		c.Games.Add(game)
	}

	player_id := c.Idg.Hash()
	err = websocket.JSON.Send(ws, NewPlayerID(player_id))
	if err != nil {
		log.Printf("game %s: unable to send player_id to player %s", gid.Id, player_id)
		websocket.JSON.Send(ws, NewFailure("send error"))
		return
	} else {
		log.Printf("game %s: sent player id: %s", gid.Id, player_id)
	}

	var clientid ClientID
	err = websocket.JSON.Receive(ws, &clientid)
	if err != nil {
		log.Printf("unable to parse ClientID: gid: %s, player: %s", gid.Id, player_id)
		websocket.JSON.Send(ws, NewFailure("parse error"))
		return
	} else {
		log.Printf("game %s: recieved: %+v", gid.Id, clientid)
	}
	if v, msg := clientid.Valid(); !v {
		log.Printf("clientid is invalid: %+v", clientid)
		websocket.JSON.Send(
			ws,
			NewFailure(msg),
		)
		return
	}

	reqEncs := []string{}
	err = websocket.JSON.Receive(ws, &reqEncs)
	if err != nil {
		log.Printf("%s %s unable to parse requested encodings", gid.Id, player_id)
		websocket.JSON.Send(ws, NewFailure("encoding recieve error"))
		return
	}
	prefEncs := []string{
		"gob",
		"json",
	}

	var encoding string
encodingLoops:
	for _, prefEnc := range prefEncs {
		for _, reqEnc := range reqEncs {
			if reqEnc == prefEnc {
				encoding = prefEnc
				log.Println("selected following encoding:", encoding)
				break encodingLoops
			}
		}
	}
	if encoding == "" {
		log.Printf("%s %s unable to negociate encoding", gid.Id, player_id)
		websocket.JSON.Send(
			ws,
			NewFailure("no overlap on supported encodings; I suggest using json"),
		)
		return
	}

	gameParam := game.gameParam()
	gp := struct {
		GameParam
		Encoding string `json:"encoding"`
	}{
		GameParam: *gameParam,
		Encoding:  encoding,
	}
	err = websocket.JSON.Send(ws, gp)
	if err != nil {
		log.Printf("%s %s game param send error", gid.Id, player_id)
		websocket.JSON.Send(ws, NewFailure("game param send error"))
		return
	} else {
		log.Printf("%s -> %s: sent %+v", gid.Id, player_id, gameParam)
	}

	switch clientid.Type {
	case "robot":
		var conf ClientConfig
		for {
			log.Printf("%s Waiting for client to send conf ...", player_id)
			err = websocket.JSON.Receive(ws, &conf)
			log.Printf("%s: conf received: %s", player_id, conf.ID)

			if err != nil {
				log.Printf("%s %s config parse error", gid.Id, player_id)
				websocket.JSON.Send(ws, NewFailure("config parse error"))
				return
			}

			// TODO: verify conf's type
			if conf.Valid(game.maxPoints) {
				log.Printf("%s -> %s: valid client config", gid.Id, player_id)
				_ = websocket.JSON.Send(ws, NewHandshake(player_id, true))
				break
			} else {
				log.Printf("%s: Config is INVALID, abort", player_id)
				_ = websocket.JSON.Send(ws, NewFailure("invalid config"))
				return
			}
		}

		p := NewPlayer(player_id, ws, game.bw, encoding)
		log.Printf("%s: made a player: %s", gid.Id, p.Id)

		convertedStats := map[string]Stats{}
		for name, stats := range conf.Stats {
			dstat := DeriveStats(stats)
			convertedStats[name] = dstat
			r := Robot{
				Stats:    dstat,
				Id:       c.Idg.Hash(),
				Name:     name,
				Health:   10,
				Heading:  v.Vector2d{X: 1, Y: 0},
				Scanners: make([]Scanner, 0),
				Delta:    c.Conf.Delta,
				idg:      c.Idg,
			}
			r.Health = r.Stats.Hp
			log.Printf("%s: adding robot: %s", p.Id, r.Id)
			r.reset(game)
			p.Robots = append(p.Robots, &r)
		}

		statsPayload := struct {
			Stats map[string]Stats `json:"stats"`
			Type  string           `json:"type"`
		}{
			Stats: convertedStats,
			Type:  "stats",
		}
		err = websocket.JSON.Send(ws, &statsPayload)
		if err != nil {
			log.Printf("error sending convertedStats to client: %s", err)
			websocket.JSON.Send(ws, NewFailure("protocol error: convertedStats"))
			return
		} else {
			log.Printf("%s -> %s: sent stats payload", gid.Id, p.Id)
		}

		log.Printf("%s, %s: about to register this player", gid.Id, p.Id)
		game.register <- p
		log.Printf("%s, %s: registered player", gid.Id, p.Id)

		defer func() {
			log.Printf("%s, %s: about to unregister this player", gid.Id, p.Id)
			game.unregister <- p
			log.Printf("%s, %s: unregistered player", gid.Id, p.Id)
		}()
		go p.Sender()
		log.Printf("%s -> %s: p.sender went", gid.Id, p.Id)
		p.Recv()
		log.Printf(
			"%s (player):  %v (robot) has been disconnected from %s (game)",
			p.Id,
			p.Robots[0].Id,
			gid.Id,
		)
	case "spectator":
		s := NewSpectator(player_id, ws, game.bw, encoding)
		log.Printf("%s, %s: about to register this spectator", gid.Id, s.Id)
		game.sregister <- s
		log.Printf("%s, %s: registered spectator", gid.Id, s.Id)
		defer func() {
			log.Printf("%s, %s: about to unregister this spectator", gid.Id, s.Id)
			game.sunregister <- s
			log.Printf("%s, %s: unregistered spectator", gid.Id, s.Id)
		}()
		go s.Sender()
		log.Printf("%s -> %s: s.sender went", gid.Id, s.Id)
		s.Recv()
		log.Printf("game %s: spectator %+v has been disconnected from this game", gid.Id, s)
	}
	log.Printf("exiting AddPlayer")
}
