package etdd

import (
	"encoding/binary"
	"errors"
	"fmt"
	"math"
	"os"
	"regexp"
	"strconv"
	"strings"
)

const (
	maxMessageLength int    = 32768
	maxStringChars   int    = 1024
	maxConfigStrings int    = 1024
	bigInfoString    int    = 8192
	gEntityNumBits   int    = 10
	maxParseEntities int    = 2048
	maxGEntities     int    = 1 << uint(gEntityNumBits)
	packetBackup     int    = 32
	packetMask       int    = packetBackup - 1
	maxPlayersNum    int    = 64
	csServerInfo     int    = 0
	csLevelStartTime int    = 11
	csPlayers        int    = 689
	svcNop           byte   = byte(1)
	svcGameState     byte   = byte(2)
	svcConfigString  byte   = byte(3)
	svcBaseLine      byte   = byte(4)
	svcServerCommand byte   = byte(5)
	svcSnapshot      byte   = byte(7)
	svcEof           byte   = byte(8)
	Chat             string = "c"
	TeamChat         string = "tc"
)

// Player events are not that interesting. We track them, but not actually using them.
const (
	psEventSequence int = 27
	psEventNum      int = 4
	psEventParams   int = 4
	psClientNum     int = psEventSequence + psEventNum + psEventParams + 1
)

// Event fields beyond esEventSequence seem to be just movement related.
const (
	esType int = 0
)

// Event entity related stuff. Not sure how the field positioning works.
const (
	eeMod      int = 1
	eeVictim   int = 20
	eeAttacker int = 21
	eeObituary int = 71
)

const (
	etEvents int = 60
)

type snapshot struct {
	valid            bool
	deltaNum         int
	messageNum       int
	serverTime       int
	flags            int
	parseEntitiesNum int
	entitiesNum      int
	ps               PlayerState
}

type entityState struct {
	number int
	fields [71]int
}

type PlayerState struct {
	Num    int
	Name   string
	Team   int
	fields [77]int
}

type Demo struct {
	file             *os.File
	commandSequence  int
	messageSequence  int
	parseEntities    [maxParseEntities]entityState
	baselines        [maxGEntities]entityState
	snapshots        [packetBackup]snapshot
	players          [maxPlayersNum]PlayerState
	snapshot         snapshot
	parseEntitiesNum int
	startTime        int
	levelTime        int
	bloc             int // We need to maintain this in our thread.
	initialized      bool
	illegible        int
}

type message struct {
	oob  bool
	data []byte
	size int
	read int
	bit  int
	bloc *int
}

// crafted from netField_t entityStateFields[]
var entityFieldBits = []int{
	8, 24, 8, 32, 32, 0, 0, 0, 0, 0, 0, 8, 32, 32, 0, 0, 0, 0, 0, 0, 32, 32, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 8, 32, 32, 9, 9, 16, 8, 24, 10, 8, 8,
	8, 8, 8, 8, 8, 8, 8, 8, 16, 8, 10, 10, 10, 32, 32, 32, 8, 8, 32, 32, 32, 4, 2,
}

// netField_t playerStateFields[]
var playerStateFieldBits = []int{
	32, 8, 8, 16, -16, 0, 0, 0, 0, 0, 0, -16, -16, -16, 16, 0, 16, 16, 16, 16, 10, 16, 16,
	10, 10, 8, 24, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 32, 7, 4, 10, 0, 0, 0, 8, 8, 8, 8, 8,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 16, 8, 8, 32, 8, 8, 8, 8, 32, 8, 8, 2,
}

func Open(name string) (Demo, error) {

	d := Demo{}

	for i := 0; i < maxPlayersNum; i++ {
		d.players[i].Num = i
	}

	file, err := os.Open(name)
	if err == nil {
		d.file = file
	}

	return d, err

}

func (d *Demo) Read(handler *Logger) {

	for {

		if d.file == nil {
			break
		} else {

			// Sequence number.
			seq, err := d.readLittleLong()
			if err != nil {
				handler.Error(err)
				break
			}

			// Needed by delta parser.
			d.messageSequence = seq

			// Message length.
			size, err := d.readLittleLong()
			if err != nil {
				handler.Error(err)
				break
			}

			// Graceful end.
			if size == math.MaxUint32 {
				break
			}

			if size > maxMessageLength {
				handler.Error(errors.New("message size overflow"))
				break
			}

			// Initialize the message.
			m := message{false, make([]byte, size), size, 0, 0, &d.bloc}

			r, err := d.readBuffer(m.data)
			if err != nil {

				if r < size {
					handler.Error(errors.New(err.Error() + "; demo file was truncated"))
				} else {
					handler.Error(err)
				}

				break

			}

			// Parse the message.
			m.Parse(handler, d)

		}

	}

	handler.End()

}

func (d *Demo) readLittleLong() (int, error) {

	s, err := d.readBytes(4)

	if err != nil {
		d.Close()
		return -1, err
	}

	return int(binary.LittleEndian.Uint32(s)), nil

}

func (d *Demo) readBytes(n int) ([]byte, error) {
	s := make([]byte, n)
	_, err := d.readBuffer(s)
	return s, err
}

func (d *Demo) readBuffer(buffer []byte) (int, error) {

	r, err := d.file.Read(buffer)

	if err != nil {
		d.Close()
	}

	if r != len(buffer) && err == nil {
		err = errors.New("incorrect number of bytes read")
	}

	return r, err

}

func (d *Demo) Close() {

	if d.file != nil {
		_ = d.file.Close()
		d.file = nil
	}

}

func (d *Demo) checkEvents(handler *Logger) {

	// Entities event detection.
	for i := 0; i < d.snapshot.entitiesNum; i++ {

		entity := &d.parseEntities[(d.snapshot.parseEntitiesNum+i)&(maxParseEntities-1)]

		// We are only interested in event entities.
		if entity.fields[esType] > etEvents {

			switch (entity.fields[esType] - etEvents) & (^768) {

			case eeObituary:
				d.obituary(handler, entity.fields[eeVictim], entity.fields[eeAttacker], entity.fields[eeMod])
				break

			}

			// Excludes from subsequent event processing.
			entity.fields[esType] = 0

		}

	}

}

func (d *Demo) obituary(handler *Logger, victimNum int, attackerNum int, mod int) {

	var victim *PlayerState
	var attacker *PlayerState

	if victimNum < 0 || victimNum >= maxPlayersNum {
		return
	}

	victim = &d.players[victimNum]

	if attackerNum >= 0 && attackerNum < maxPlayersNum {
		attacker = &d.players[attackerNum]
	}

	handler.Obituary(d.levelTime, victim, attacker, mod)

}

func (m *message) Parse(handler *Logger, demo *Demo) {

	// Acknowledge number.
	m.readLong()

	for {

		if demo.file == nil {
			break
		}

		if m.read > m.size {
			handler.Error(errors.New("read past end of server message"))
			break
		}

		command := m.readByte()

		if command == svcEof || demo.file == nil {
			break
		}

		switch command {

		case svcNop:
			break

		case svcGameState:
			m.parseGameState(handler, demo)
			break

		case svcServerCommand:
			m.parseCommandString(handler, demo)
			break

		case svcSnapshot:
			m.parseSnapshot(handler, demo)
			break

		default:
			demo.illegible++
			handler.Error(errors.New(fmt.Sprintf("illegible server message %d", int(command))))

		}

		if demo.illegible == 10 {
			demo.file = nil
			break
		}

	}

	m.parseBinaryMessage(handler, demo)

	// Is this the best time to do it?
	demo.checkEvents(handler)

}

func (m *message) parseGameState(handler *Logger, demo *Demo) {

	demo.commandSequence = m.readLong()

	for {

		command := m.readByte()

		if command == svcEof {
			break
		}

		switch command {

		case svcConfigString:

			i := m.readShort()

			if i < 0 || i >= maxConfigStrings {
				handler.Error(errors.New(fmt.Sprintf("config strings overflow")))
				demo.Close()
				return
			}

			s := m.readBigString()
			demo.cs(handler, i, s)

			break

		case svcBaseLine:

			nn := m.readBits(10)

			if nn < 0 || nn >= maxGEntities {
				handler.Error(errors.New(fmt.Sprintf("baseline number out of range: %d", nn)))
				demo.Close()
				return
			}

			nullState := entityState{}
			m.readDeltaEntity(demo, &nullState, &demo.baselines[nn], nn)

			break

		default:
			handler.Error(errors.New(fmt.Sprintf("bad command byte %d", int(command))))
			return

		}

	}

	m.readLong() // clc.clientNum...
	m.readLong() // ...and a checksum.

}

func (m *message) parseCommandString(handler *Logger, demo *Demo) {

	seq := m.readLong()
	s := m.readString()

	if demo.commandSequence >= seq {
		return
	}

	demo.commandSequence = seq

	if len(s) == 0 || s[0] == '*' {
		return
	}

	var command string

	if !word(&s, &command) {
		return
	}

	switch command {

	case "chat", "print", "cpm", "cp", "b", "sc":

		if command == "b" {
			var __ string
			word(&s, &__)
		}

		r := regexp.MustCompile(`^("|\s)*|("|\s|\^.)*$|\[lo[nf]]|" -?\d+ \d+$`)
		s = r.ReplaceAllString(s, "")

		// Line break would fuck statistics up.
		if command == "sc" {

			// I wonder what this is, huh?
			if s == "0" {
				break
			}

			lines := strings.Split(strings.TrimSpace(s), "\n")

			for _, line := range lines {
				if line != "" {
					handler.Print(demo.levelTime, line)
				}
			}

		} else {
			handler.Print(demo.levelTime, s)
		}

		break

	case Chat, TeamChat, "cs":

		var ps string
		if word(&s, &ps) {

			p, _ := strconv.Atoi(ps)

			var message string
			if quoted(&s, &message) {
				if command == "cs" {
					demo.cs(handler, p, message)
				} else {
					handler.Chat(demo.levelTime, demo.players[p], message, command)
				}
			}

		}

		break

	default:
		return

	}

}

func (m *message) parseSnapshot(handler *Logger, demo *Demo) {

	newSnap := snapshot{}
	newSnap.messageNum = demo.messageSequence
	newSnap.serverTime = m.readLong()

	deltaNum := m.readBits(8)

	if deltaNum == 0 {
		newSnap.deltaNum = -1
	} else {
		newSnap.deltaNum = demo.messageSequence - deltaNum
	}

	newSnap.flags = m.readBits(8)

	var old *snapshot

	if newSnap.deltaNum <= 0 {
		newSnap.valid = true
	} else {

		old = &demo.snapshots[newSnap.deltaNum&packetMask]

		if old.valid && old.messageNum == newSnap.deltaNum && demo.parseEntitiesNum-old.parseEntitiesNum <= maxParseEntities-128 {
			newSnap.valid = true
		}

	}

	areaMask := m.readBits(8)
	if areaMask > 32 {
		return
	}

	for i := 0; i < areaMask; i++ {
		m.readBits(8)
	}

	if old != nil {
		m.readDeltaPlayerState(&old.ps, &newSnap.ps)
	} else {
		m.readDeltaPlayerState(nil, &newSnap.ps)
	}

	// This is basically CL_ParsePacketEntities()

	newSnap.parseEntitiesNum = demo.parseEntitiesNum
	newSnap.entitiesNum = 0

	index, oNum := 0, 0
	var oldState *entityState = nil

	if old == nil {
		oNum = 99999
	} else {

		if index >= old.entitiesNum {
			oNum = 99999
		} else {
			oldState = &demo.parseEntities[(old.parseEntitiesNum+index)&(maxParseEntities-1)]
			oNum = oldState.number
		}

	}

	for {

		nn := m.readBits(gEntityNumBits)

		if nn == maxGEntities-1 {
			break
		}

		if m.read > m.size {
			demo.Close()
			return
		}

		for oNum < nn {

			m.deltaEntity(demo, &newSnap, oNum, oldState, true)
			index++

			if index >= old.entitiesNum {
				oNum = 99999
			} else {
				oldState = &demo.parseEntities[(old.parseEntitiesNum+index)&(maxParseEntities-1)]
				oNum = oldState.number
			}

		}

		if oNum == nn {

			m.deltaEntity(demo, &newSnap, nn, oldState, false)
			index++

			if index >= old.entitiesNum {
				oNum = 99999
			} else {
				oldState = &demo.parseEntities[(old.parseEntitiesNum+index)&(maxParseEntities-1)]
				oNum = oldState.number
			}

			continue

		}

		if oNum > nn && nn < maxGEntities {
			m.deltaEntity(demo, &newSnap, nn, &demo.baselines[nn], false)
		}

	}

	for oNum != 99999 {

		m.deltaEntity(demo, &newSnap, oNum, oldState, true)
		index++

		if index >= old.entitiesNum {
			oNum = 99999
		} else {
			oldState = &demo.parseEntities[(old.parseEntitiesNum+index)&(maxParseEntities-1)]
			oNum = oldState.number
		}

	}

	// CL_ParsePacketEntities end.

	if !newSnap.valid {
		return
	}

	oldMessageNum := demo.snapshot.messageNum + 1

	if newSnap.messageNum-oldMessageNum >= packetBackup {
		oldMessageNum = newSnap.messageNum - (packetBackup - 1)
	}

	for oldMessageNum < newSnap.messageNum {
		demo.snapshots[oldMessageNum&packetMask].valid = false
		oldMessageNum++
	}

	demo.snapshot = newSnap
	demo.snapshots[newSnap.messageNum&packetMask] = newSnap
	demo.levelTime = newSnap.serverTime - demo.startTime

	// Transfer player state updates to CG players struct.
	player := &demo.players[newSnap.ps.fields[psClientNum]]
	for i := 0; i < len(player.fields); i++ {
		player.fields[i] = newSnap.ps.fields[i]
	}

}

func (m *message) parseBinaryMessage(handler *Logger, demo *Demo) {

	// Switch to uncompressed reading mode.
	m.bit = (m.bit + 1) & ^7
	m.oob = true

	size := m.size - m.read

	if size <= 0 || size > maxMessageLength {
		return
	}

	// Nothing happens here?

}

func (m *message) deltaEntity(demo *Demo, snapshot *snapshot, num int, oldState *entityState, unchanged bool) {

	state := &demo.parseEntities[demo.parseEntitiesNum&(maxParseEntities-1)]

	if unchanged {
		*state = *oldState
	} else {
		m.readDeltaEntity(demo, oldState, state, num)
	}

	if state.number == maxGEntities-1 {
		return
	}

	demo.parseEntitiesNum++
	snapshot.entitiesNum++

}

func (m *message) readDeltaEntity(demo *Demo, from *entityState, to *entityState, number int) {

	if m.readBits(1) == 1 {
		*to = entityState{}
		to.number = maxGEntities - 1
		return
	}

	if m.readBits(1) == 0 {
		*to = *from
		to.number = number
		return
	}

	lc := m.readBits(8)

	if lc > len(entityFieldBits) {
		lc = len(entityFieldBits)
	}

	to.number = number

	for i := 0; i < lc; i++ {

		if m.readBits(1) == 1 {

			if entityFieldBits[i] == 0 {

				if m.readBits(1) == 1 {
					if m.readBits(1) == 0 {
						to.fields[i] = m.readBits(13)
					} else {
						to.fields[i] = m.readBits(32)
					}
				}

			} else {

				if m.readBits(1) == 1 {
					to.fields[i] = m.readBits(entityFieldBits[i])
				}

			}

		}

	}

}

func (m *message) readDeltaPlayerState(from *PlayerState, to *PlayerState) {

	if from == nil {
		from = &PlayerState{}
	}

	*to = *from

	lc := m.readBits(8)

	for i := 0; i < lc && i < len(playerStateFieldBits); i++ {

		if m.readBits(1) == 1 {

			if playerStateFieldBits[i] == 0 {

				if m.readBits(1) == 0 {
					to.fields[i] = m.readBits(13)
				} else {
					to.fields[i] = m.readBits(32)
				}

			} else {
				to.fields[i] = m.readBits(playerStateFieldBits[i])
			}

		}

	}

	if m.readBits(1) == 1 {

		// stats, persistent and holdable stats
		for a := 0; a < 3; a++ {

			if m.readBits(1) == 1 {

				bits := m.readShort()

				for i := 0; i < 16; i++ {
					if (bits & (1 << uint(i))) != 0 {
						m.readShort()
					}
				}

			}

		}

		// powerups
		if m.readBits(1) == 1 {

			bits := m.readShort()

			for i := 0; i < 16; i++ {
				if (bits & (1 << uint(i))) != 0 {
					m.readLong()
				}
			}

		}

	}

	// check for any ammo change (0-63)
	if m.readBits(1) == 1 {

		for j := 0; j < 4; j++ {

			if m.readBits(1) == 1 {

				bits := m.readShort()

				for i := 0; i < 16; i++ {
					if (bits & (1 << uint(i))) != 0 {
						m.readShort()
					}
				}

			}

		}

	}

	// ammo in clip
	for j := 0; j < 4; j++ {

		if m.readBits(1) == 1 {

			bits := m.readShort()

			for i := 0; i < 16; i++ {
				if (bits & (1 << uint(i))) != 0 {
					m.readShort()
				}
			}

		}

	}

}

func (m *message) readLong() int {

	c := m.readBits(32)

	if m.read > m.size {
		return -1
	}

	return c

}

func (m *message) readShort() int {

	c := m.readBits(16)

	if m.read > m.size {
		return -1
	}

	return c

}

func (m *message) readString() string {

	s := ""

	for l := 0; l < maxStringChars && m.read <= m.size; l++ {

		c := m.readByte()

		if c == byte(0) {
			break
		}

		if c == '%' || c > 127 {
			c = '.'
		}

		s += string(c)

	}

	return s

}

func (m *message) readBigString() string {

	s := ""

	for l := 0; l < bigInfoString && m.read <= m.size; l++ {

		c := m.readByte()

		if c == byte(0) {
			break
		}

		if c == '%' {
			c = '.'
		}

		s += string(c)

	}

	return s

}

func (m *message) readByte() byte {
	return byte(m.readBits(8))
}

func (m *message) readBits(bits int) int {

	value, r, s, i, n := 0, 0, false, 0, 0

	if bits < 0 {
		bits = -bits
		s = true
	}

	if m.oob {

		if bits == 8 || bits == 16 || bits == 32 {

			for i*8 < bits {
				value |= int(m.data[m.read]) << uint8(i*8)
				m.read += 1
				m.bit += bits
				i++
			}

		} else {
			panic(fmt.Sprintf("can't read %d bits", bits))
		}

	} else {

		if (bits & 7) != 0 {
			n = bits & 7
			for i = 0; i < n; i++ {
				value |= HuffGetBit(m.data, &m.bit, m.bloc) << uint(i)
			}
			bits = bits - n
		}

		for i = 0; bits != 0 && i < bits; i += 8 {
			HuffOffsetReceive(&r, m.data, &m.bit, m.bloc)
			value |= r << uint(i+n)
		}

		m.read = (m.bit >> 3) + 1

	}

	if s && (value&(1<<uint(bits-1))) != 0 {
		value |= -1 ^ ((1 << uint(bits)) - 1)
	}

	return value

}

func (d *Demo) cs(handler *Logger, p int, s string) {

	if p == csServerInfo && !d.initialized {

		d.initialized = true

		bits := strings.Split(s, "\\")
		vars := make(map[string]string)

		for i := 1; i+1 < len(bits); i += 2 {
			vars[bits[i]] = bits[i+1]
		}

		if hostname, ok := vars["sv_hostname"]; ok {
			if mapname, ok := vars["mapname"]; ok {
				handler.Start(hostname, mapname)
			}
		}

		return

	}

	if p == csLevelStartTime {
		d.startTime, _ = strconv.Atoi(s)
		return
	}

	if p < csPlayers || p >= csPlayers+maxPlayersNum {
		return
	}

	bits := strings.Split(s, "\\")
	state := &d.players[p-csPlayers]

	for i := 0; i < len(bits); i += 2 {

		switch bits[i] {

		case "n":
			state.Name = bits[i+1]
			break

		case "t":
			state.Team, _ = strconv.Atoi(bits[i+1])
			break

		}

	}

}

func word(s *string, word *string) bool {

	i := strings.Index(*s, " ")

	if i < 1 {
		return false
	}

	*word = (*s)[0:i]
	*s = (*s)[i+1:]

	return true

}

func quoted(s *string, quoted *string) bool {

	if (*s)[0] == '"' {
		*s = (*s)[1:]
	}

	i := strings.Index(*s, "\"")

	if i < 1 {
		return false
	}

	*quoted = (*s)[0:i]
	*s = (*s)[i+1:]

	return true

}