package server

import (
	"bytes"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type GameType int

const (
	FreeForAll     GameType = 0
	Tournament     GameType = 1
	SinglePlayer   GameType = 2
	TeamDeathmatch GameType = 3
	CaptureTheFlag GameType = 4
)

func (gt GameType) String() string {
	switch gt {
	case FreeForAll:
		return "FreeForAll"
	case Tournament:
		return "Tournament"
	case SinglePlayer:
		return "SinglePlayer"
	case TeamDeathmatch:
		return "TeamDeathmatch"
	case CaptureTheFlag:
		return "CaptureTheFlag"
	default:
		return "Unknown"
	}
}

func (gt *GameType) UnmarshalText(data []byte) error {
	switch string(data) {
	case "FreeForAll", "FFA":
		*gt = FreeForAll
	case "Tournament":
		*gt = Tournament
	case "SinglePlayer":
		*gt = SinglePlayer
	case "TeamDeathmatch":
		*gt = TeamDeathmatch
	case "CaptureTheFlag", "CTF":
		*gt = CaptureTheFlag
	default:
		return errors.Errorf("unknown GameType: %s", data)
	}
	return nil
}

type Config struct {
	FragLimit int             `name:"fraglimit"`
	TimeLimit metav1.Duration `name:"timelimit"`

	GameConfig       `json:"game"`
	FileServerConfig `json:"fs"`
	ServerConfig     `json:"server"`

	Maps
}

type GameConfig struct {
	ForceRespawn  bool            `name:"g_forcerespawn"`
	GameType      GameType        `json:"type" name:"g_gametype"`
	Inactivity    metav1.Duration `name:"g_inactivity"`
	Log           string          `name:"g_log"`
	MOTD          string          `name:"g_motd"`
	QuadFactor    int             `name:"g_quadfactor"`
	WeaponRespawn int             `name:"g_weaponrespawn"`
}

type FileServerConfig struct {
	// allows people to base mods upon mods syntax to follow
	BaseGame string `name:"fs_basegame"`
	// set base path root C:\Program Files\Quake III Arena for files to be
	// downloaded from this path may change for TC's and MOD's
	BasePath string `name:"fs_basepath"`
	// toggle if files can be copied from servers or if client will download
	CopyFiles bool `name:"fs_copyfiles"`
	// possibly enables file server debug mode for download/uploads or
	// something
	Debug bool `name:"fs_debug"`
	// set gamedir set the game folder/dir default is baseq3
	Game string `name:"fs_game"`
	// possibly for TC's and MODS the default is the path to quake3.exe
	HomePath string `name:"fs_homepath"`
}

type ServerConfig struct {
	AllowDownload bool   `name:"sv_allowDownload"`
	DownloadURL   string `name:"sv_dlURL"`
	Hostname      string `name:"sv_hostname"`
	MaxClients    int    `name:"sv_maxclients"`
	Password      string `name:"rconpassword"`
}

func (c *Config) Marshal() ([]byte, error) {
	return writeStruct(reflect.Indirect(reflect.ValueOf(c)))
}

func writeStruct(v reflect.Value) ([]byte, error) {
	if v.Kind() != reflect.Struct {
		return nil, errors.Errorf("expected struct, received %T", v.Kind())
	}
	var b bytes.Buffer
	for i := 0; i < v.Type().NumField(); i++ {
		fv := v.Field(i)
		switch fv.Kind() {
		case reflect.Struct:
			data, err := writeStruct(fv)
			if err != nil {
				return nil, err
			}
			b.Write(data)
		case reflect.Slice:
			switch val := fv.Interface().(type) {
			case Maps:
				data, _ := val.Marshal()
				b.Write(data)
			default:
				panic(fmt.Errorf("received unknown type %T", val))
			}
		default:
			tv, ok := v.Type().Field(i).Tag.Lookup("name")
			if !ok {
				continue
			}
			s := toString(v.Type().Field(i).Name, fv)
			switch tv {
			case "sv_dlURL":
				if s != "" {
					b.WriteString(fmt.Sprintf("sets %s %s\n", tv, s))
				}
			default:
				b.WriteString(fmt.Sprintf("seta %s %s\n", tv, strconv.Quote(s)))
			}
		}
	}
	return b.Bytes(), nil
}

func toString(name string, v reflect.Value) string {
	switch val := v.Interface().(type) {
	case string:
		return val
	case int:
		return strconv.Itoa(val)
	case metav1.Duration:
		switch name {
		case "TimeLimit":
			return fmt.Sprintf("%d", int(val.Minutes()))
		default:
			return fmt.Sprintf("%d", int(val.Seconds()))
		}
	case bool:
		if val {
			return "1"
		}
		return "0"
	case GameType:
		return fmt.Sprintf("%d", val)
	case Maps:
		data, _ := val.Marshal()
		return string(data)
	default:
		panic(fmt.Errorf("received unknown type %T", v.Interface()))
	}
}

func Default() *Config {
	return &Config{
		FragLimit: 25,
		TimeLimit: metav1.Duration{Duration: 15 * time.Minute},
		GameConfig: GameConfig{
			Log:           "",
			MOTD:          "Welcome to Critical Stack",
			QuadFactor:    3,
			GameType:      FreeForAll,
			WeaponRespawn: 3,
			Inactivity:    metav1.Duration{Duration: 10 * time.Minute},
			ForceRespawn:  false,
		},
		ServerConfig: ServerConfig{
			MaxClients: 12,
			Hostname:   "quakekube",
			Password:   "changeme",
		},
		Maps: Maps{
			{Name: "q3dm7", Type: FreeForAll},
			{Name: "q3dm17", Type: FreeForAll},
		},
	}
}

type Map struct {
	Name string   `json:"name"`
	Type GameType `json:"type"`

	CaptureLimit int           `json:"captureLimit"`
	FragLimit    int           `json:"fragLimit"`
	TimeLimit    time.Duration `json:"timeLimit"`
}

type Maps []Map

func (maps Maps) Marshal() ([]byte, error) {
	var b bytes.Buffer
	for i, m := range maps {
		cmds := []string{
			fmt.Sprintf("g_gametype %d", m.Type),
		}
		if m.Type == CaptureTheFlag && m.CaptureLimit != 0 {
			cmds = append(cmds, fmt.Sprintf("capturelimit %d", m.CaptureLimit))
		}
		if m.FragLimit != 0 {
			cmds = append(cmds, fmt.Sprintf("fraglimit %d", m.FragLimit))
		}
		if m.TimeLimit != 0 {
			cmds = append(cmds, fmt.Sprintf("timelimit %d", int(m.TimeLimit.Minutes())))
		}
		cmds = append(cmds, fmt.Sprintf("map %s", m.Name))
		nextmap := "d0"
		if i < len(maps)-1 {
			nextmap = fmt.Sprintf("d%d", i+1)
		}
		cmds = append(cmds, fmt.Sprintf("set nextmap vstr %s", nextmap))
		b.WriteString(fmt.Sprintf("set d%d \"seta %s\"\n", i, strings.Join(cmds, " ; ")))
	}
	b.WriteString("vstr d0")
	return b.Bytes(), nil
}
