package main

import (
	"errors"
	"fmt"
	"image/png"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/spf13/afero"
	"opslag.de/schobers/allg5"
	"opslag.de/schobers/fs/vfs"
	"opslag.de/schobers/geom"
	"opslag.de/schobers/krampus19/alui"
	"opslag.de/schobers/krampus19/gut"
)

type game struct {
	ctx  *Context
	ui   *alui.UI
	main alui.Container

	info  *alui.Overlay
	cons  *alui.Overlay
	scene *alui.Overlay
}

func (g *game) initUI(disp *allg5.Display, cons *gut.Console, fps *gut.FPS) error {
	disp.SetWindowTitle("Krampushack '19 - Sokodragon - by Tharro")
	ui := alui.NewUI(disp, &g.main)
	ui.SetPalette(alui.Palette{
		Icon:    allg5.NewColor(0x21, 0x21, 0x21),
		Dark:    allg5.NewColor(0xf7, 0xd1, 0x56),
		Primary: allg5.NewColor(0xf7, 0xbd, 0x00),
		Text:    allg5.NewColor(0xff, 0xff, 0xff),
	})
	g.ui = ui
	g.ctx.Palette = &g.ui.Context().Palette
	g.info = &alui.Overlay{Proxy: &info{fps: fps}}
	g.cons = &alui.Overlay{Proxy: newConsole(cons)}
	g.scene = &alui.Overlay{Proxy: &splash{}, Visible: true}
	g.main.Children = append(g.main.Children, g.scene, &alui.Container{Children: []alui.Control{g.info, g.cons}})
	return nil
}

func (g *game) loadBitmap(path, name string) error {
	f, err := g.ctx.Resources.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()
	im, err := png.Decode(f)
	if err != nil {
		return err
	}
	bmp, err := allg5.NewBitmapFromImage(im, true)
	if err != nil {
		return err
	}
	g.ctx.Textures[name] = newTexture(bmp)
	return nil
}

func (g *game) loadFonts() error {
	openSansRegularPath, err := g.ctx.Resources.Retrieve("fonts/OpenSans-Regular.ttf")
	if err != nil {
		return err
	}
	openSansBoldPath, err := g.ctx.Resources.Retrieve("fonts/OpenSans-Bold.ttf")
	if err != nil {
		return err
	}
	firaMonoPath, err := g.ctx.Resources.Retrieve("fonts/FiraMono-Regular.ttf")
	if err != nil {
		return err
	}
	err = g.ui.Fonts().LoadFonts(alui.FontDescription{Path: openSansRegularPath, Name: "default", Size: 32})
	if err != nil {
		return err
	}
	err = g.ui.Fonts().LoadFonts(alui.FontDescription{Path: openSansBoldPath, Name: "header", Size: 32})
	if err != nil {
		return err
	}
	err = g.ui.Fonts().LoadFonts(alui.FontDescription{Path: firaMonoPath, Name: "console", Size: 12})
	if err != nil {
		return err
	}
	return nil
}

func (g *game) loadTextures(pathToName map[string]string) error {
	for path, name := range pathToName {
		err := g.loadBitmap(path, name)
		if err != nil {
			return err
		}
	}
	return nil
}

func (g *game) loadLevelPacks() error {
	g.ctx.Levels = newLevelPacks()

	var ids []string
	const root = "levels"
	err := afero.Walk(g.ctx.ResourcesFs, root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() {
			return nil
		}
		name := filepath.Base(path)
		if name == root {
			return nil
		}

		if !strings.HasPrefix(name, "pack") {
			return filepath.SkipDir
		}
		ids = append(ids, name[4:])
		return filepath.SkipDir
	})
	if err != nil {
		return err
	}

	for _, id := range ids {
		err := g.loadLevelPack(id)
		if err != nil {
			return err
		}
	}
	return nil
}

func (g *game) loadLevelPack(id string) error {
	fileName := fmt.Sprintf("levels/pack%s/info.txt", id)
	f, err := g.ctx.Resources.Open(fileName)
	if err != nil {
		return err
	}
	defer f.Close()
	pack, err := parseLevelPackAsset(f, func(levelID string) (io.ReadCloser, error) {
		fileName := fmt.Sprintf("levels/pack%s/level%s.txt", id, levelID)
		return g.ctx.Resources.Open(fileName)
	})
	if err != nil {
		return err
	}
	g.ctx.Levels.Add(id, pack)
	return nil
}

func (g *game) loadSprites(names ...string) error {
	g.ctx.Sprites = map[string]sprite{}
	for _, name := range names {
		fileName := fmt.Sprintf("sprites/%s.txt", name)
		f, err := g.ctx.Resources.Open(fileName)
		if err != nil {
			return err
		}
		defer f.Close()
		sprite, err := loadSpriteAsset(f)
		if err != nil {
			return err
		}

		texture, ok := g.ctx.Textures[sprite.texture]
		if !ok {
			return errors.New("sprite texture not found")
		}
		for _, part := range sprite.parts {
			sub := part.sub
			texture.Subs[part.name] = texture.Sub(sub.Min.X, sub.Min.Y, sub.Max.X-sub.Min.X, sub.Max.Y-sub.Min.Y)
		}
		g.ctx.Sprites[name] = sprite
	}
	return nil
}

func (g *game) loadAssets() error {
	log.Println("Loading textures...")
	err := g.loadTextures(map[string]string{
		"entity_brick.png":  "brick",
		"entity_dragon.png": "dragon",
		"entity_egg.png":    "egg",

		"tile_lava_brick.png": "lava_brick",
		"tile_magma.png":      "magma",

		"particles.png": "particles",
		"ui.png":        "ui",
	})
	if err != nil {
		return err
	}
	log.Printf("Loaded %d textures.\n", len(g.ctx.Textures))

	log.Println("Loading level packs...")
	err = g.loadLevelPacks()
	if err != nil {
		return err
	}
	log.Printf("Loaded %d levels packs (%d levels).\n", g.ctx.Levels.Len(), g.ctx.Levels.LenLevels())

	log.Println("Loading fonts...")
	err = g.loadFonts()
	if err != nil {
		return err
	}
	log.Printf("Loaded %d fonts.\n", g.ui.Fonts().Len())

	log.Println("Loading sprites")
	err = g.loadSprites("brick", "dragon", "egg", "lava_brick", "magma", "particles", "ui")
	if err != nil {
		return err
	}
	log.Printf("Loaded %d sprites.\n", len(g.ctx.Sprites))
	return nil
}

func (g *game) Destroy() {
	g.ui.Destroy()
	g.ctx.Destroy()
}

func (g *game) Init(disp *allg5.Display, settings settings, res vfs.CopyDir, resFs afero.Fs, cons *gut.Console, fps *gut.FPS) error {
	log.Print("Initializing game...")
	g.ctx = &Context{Resources: res, ResourcesFs: resFs, Textures: map[string]texture{}, Settings: settings, Navigation: navigation{game: g}}
	g.ctx.DisplaySize = geom.Pt(disp.Width(), disp.Height())
	g.ctx.SpriteDrawer.ctx = g.ctx
	err := g.ctx.Progress.load()
	if err != nil {
		log.Printf("Unable to load previous progress.")
	}
	if err := g.initUI(disp, cons, fps); err != nil {
		return err
	}
	log.Print("Loading assets...")
	if err := g.loadAssets(); err != nil {
		return err
	}
	log.Print("Loaded assets.")

	g.ctx.Navigation.ShowMainMenu()

	return nil
}

func (g *game) Handle(e allg5.Event) {
	switch e := e.(type) {
	case *allg5.KeyDownEvent:
		if e.KeyCode == allg5.KeyF3 {
			g.info.Visible = !g.info.Visible
		}
		if e.KeyCode == allg5.KeyF4 {
			g.cons.Visible = !g.cons.Visible
		}
	}
	g.ui.Handle(e)
}

func (g *game) Render() { g.ui.Render() }
