// Copyright 2014, 2015 by Sascha L. Teichmann
// Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.

package main

import (
	"bufio"
	"bytes"
	"context"
	"database/sql"
	"encoding/json"
	"html/template"
	"log"
	"math"
	"net/http"
	"os"
	"sort"
	"sync"
	"time"

	"bitbucket.org/s_l_teichmann/mtsatellite/common"
)

const (
	sleepInterval = time.Second * 5
	sleepPG       = time.Second
)

var geoJSONTmpl = template.Must(template.New("geojson").Parse(
	`{ "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [{{.Z}}, {{.X}}]
  },
  "properties": {
    "name": "{{.Name | html }}"
  }
}`))

type player struct {
	X    float64 `json:"x"`
	Y    float64 `json:"y"`
	Z    float64 `json:"z"`
	Name string  `json:"name"`
}

type players struct {
	fifo string
	wsf  *websocketForwarder
	pls  []*player
	mu   sync.RWMutex
}

func newPlayers(fifo string, wsf *websocketForwarder) *players {
	return &players{fifo: fifo, wsf: wsf, pls: []*player{}}
}

func (p *player) MarshalJSON() ([]byte, error) {
	var buf bytes.Buffer
	if err := geoJSONTmpl.Execute(&buf, p); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func (p *player) same(o *player) bool {
	return p.Name == o.Name &&
		math.Abs(p.X-o.X) < 0.000001 &&
		math.Abs(p.Y-o.Y) < 0.000001 &&
		math.Abs(p.Z-o.Z) < 0.000001
}

const selectPlayersSQL = `
SELECT posx/10.0, posy/10.0, posz/10.0, name
FROM player
WHERE modification_date > now() - '2m'::interval`

func playersFromPostgreSQL(connS string) ([]*player, error) {
	time.Sleep(sleepPG)
	db, err := sql.Open("pgx", connS)
	if err != nil {
		return nil, err
	}
	defer db.Close()
	rows, err := db.QueryContext(context.Background(), selectPlayersSQL)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var pls []*player
	for rows.Next() {
		var p player
		if err := rows.Scan(&p.X, &p.Y, &p.Z, &p.Name); err != nil {
			return nil, err
		}
		pls = append(pls, &p)
	}
	return pls, rows.Err()
}

func (ps *players) readFromFIFO() ([]*player, error) {
	if host, ok := common.IsPostgreSQL(ps.fifo); ok {
		return playersFromPostgreSQL(host)
	}

	file, err := os.Open(ps.fifo)
	if err != nil {
		return nil, err
	}
	defer file.Close()
	reader := bufio.NewReader(file)
	decoder := json.NewDecoder(reader)
	var pls []*player

	if err = decoder.Decode(&pls); err != nil {
		return nil, err
	}
	return pls, nil
}

func samePlayers(a, b []*player) bool {
	if len(a) != len(b) {
		return false
	}
	for i, p := range a {
		if !p.same(b[i]) {
			return false
		}
	}
	return true
}

func (ps *players) run() {
	for {
		empty := len(ps.current()) == 0

		pls, err := ps.readFromFIFO()
		if err != nil {
			//log.Printf("err: %s\n", err)
			time.Sleep(sleepInterval)
			continue
		}
		if empty && pls == nil {
			//log.Println("no players")
			continue
		}
		//log.Printf("%+q\n", pls)
		sort.Slice(pls, func(i, j int) bool {
			return pls[i].Name < pls[j].Name
		})
		var change bool
		ps.mu.Lock()
		//log.Printf("%+q\n", pls)
		//log.Printf("%+q\n", ps.pls)
		if change = !samePlayers(pls, ps.pls); change {
			ps.pls = pls
		}
		ps.mu.Unlock()
		if change && ps.wsf != nil {
			if pls == nil {
				pls = []*player{}
			}
			// TODO: Throttle this!
			ps.wsf.BroadcastPlayers(pls)
		}
	}
}

func (ps *players) current() []*player {
	ps.mu.RLock()
	defer ps.mu.RUnlock()
	return ps.pls
}

func (ps *players) initConnection(wsf *websocketForwarder, c *connection) {
	wsf.singleSend(c, &plsMsg{Pls: ps.current()})
}

func (ps *players) ServeHTTP(rw http.ResponseWriter, _ *http.Request) {
	rw.Header().Set("Content-Type", "application/json")
	pls := ps.current()
	encoder := json.NewEncoder(rw)
	if err := encoder.Encode(pls); err != nil {
		log.Printf("error: sending JSON failed: %s\n", err)
	}
}