// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600.  If not, see <https://www.gnu.org/licenses/>.

package sdlimgui

import (
	"fmt"
	"math"

	"github.com/inkyblackness/imgui-go/v4"
	"github.com/jetsetilly/gopher2600/gui/fonts"
)

type fontDisplay interface {
	displayDPI() (float32, error)
}

type fontAtlas struct {
	display fontDisplay

	// gui font
	gui     imgui.Font
	guiSize float32

	// used for notifications (eg. network access, etc.)
	largeFontAwesome     imgui.Font
	largeFontAwesomeSize float32

	// used for rewind/fast-forward state
	veryLargeFontAwesome     imgui.Font
	veryLargeFontAwesomeSize float32

	// custom icons for controllers and other peripherals
	gopher2600Icons     imgui.Font
	gopher2600IconsSize float32

	// annotation of diagrams
	diagram     imgui.Font
	diagramSize float32

	// terminal
	terminal     imgui.Font
	terminalSize float32

	// source code
	code     imgui.Font
	codeSize float32
}

func scaleFontForDPI(pt float32, dpi float32) float32 {
	return float32(math.Round(float64(pt * (dpi / 72))))
}

func (atlas *fontAtlas) initialise(display fontDisplay, renderer renderer, prefs *preferences) error {
	setFontBuilderFlags(imgui.CurrentIO().Fonts())
	dpi, err := display.displayDPI()
	if err != nil {
		return fmt.Errorf("font: %w", err)
	}

	err = atlas.setDefaultFont(prefs, dpi)
	if err != nil {
		return err
	}

	// load large font awesome
	if atlas.largeFontAwesome == 0 {
		largeFontAwesomeConfig := imgui.NewFontConfig()
		defer largeFontAwesomeConfig.Delete()
		largeFontAwesomeConfig.SetPixelSnapH(true)

		var largeFontAwesomeBuilder imgui.GlyphRangesBuilder
		largeFontAwesomeBuilder.Add(fonts.FontAwesomeMin, fonts.FontAwesomeMax)

		atlas.largeFontAwesomeSize = 22.0
		atlas.largeFontAwesome = imgui.CurrentIO().Fonts().AddFontFromMemoryTTFV(fonts.FontAwesome, atlas.largeFontAwesomeSize, largeFontAwesomeConfig, largeFontAwesomeBuilder.Build().GlyphRanges)
		if atlas.largeFontAwesome == 0 {
			return fmt.Errorf("font: error loading font-awesome from memory")
		}
	}

	// load very-large font awesome
	if atlas.veryLargeFontAwesome == 0 {
		veryLargeFontAwesomeConfig := imgui.NewFontConfig()
		defer veryLargeFontAwesomeConfig.Delete()
		veryLargeFontAwesomeConfig.SetPixelSnapH(true)

		var veryLargeFontAwesomeBuilder imgui.GlyphRangesBuilder
		veryLargeFontAwesomeBuilder.Add(fonts.FontAwesomeMin, fonts.FontAwesomeMax)

		atlas.veryLargeFontAwesomeSize = 44.0
		atlas.veryLargeFontAwesome = imgui.CurrentIO().Fonts().AddFontFromMemoryTTFV(fonts.FontAwesome, atlas.veryLargeFontAwesomeSize, veryLargeFontAwesomeConfig, veryLargeFontAwesomeBuilder.Build().GlyphRanges)
		if atlas.veryLargeFontAwesome == 0 {
			return fmt.Errorf("font: error loading font-awesome from memory")
		}
	}

	// load gopher icons
	if atlas.gopher2600Icons == 0 {
		gopher2600IconConfig := imgui.NewFontConfig()
		defer gopher2600IconConfig.Delete()
		gopher2600IconConfig.SetPixelSnapH(true)
		gopher2600IconConfig.SetGlyphOffsetY(1.0)

		var gopher2600IconBuilder imgui.GlyphRangesBuilder
		gopher2600IconBuilder.Add(fonts.Gopher2600IconMin, fonts.Gopher2600IconMax)

		atlas.gopher2600IconsSize = 60.0
		atlas.gopher2600Icons = imgui.CurrentIO().Fonts().AddFontFromMemoryTTFV(fonts.Gopher2600Icons, atlas.gopher2600IconsSize, gopher2600IconConfig, gopher2600IconBuilder.Build().GlyphRanges)
		if atlas.gopher2600Icons == 0 {
			return fmt.Errorf("font: error loading Gopher2600 font from memory")
		}
	}

	// load diagram font
	if atlas.diagram == 0 {
		diagramConfig := imgui.NewFontConfig()
		defer diagramConfig.Delete()
		diagramConfig.SetPixelSnapH(true)

		var diagramBuilder imgui.GlyphRangesBuilder
		diagramBuilder.Add(fonts.HackMin, fonts.HackMax)

		atlas.diagramSize = scaleFontForDPI(10.0, dpi)
		atlas.diagram = imgui.CurrentIO().Fonts().AddFontFromMemoryTTFV(fonts.Hack, atlas.diagramSize, diagramConfig, diagramBuilder.Build().GlyphRanges)
		if atlas.diagram == 0 {
			return fmt.Errorf("font: error loading hack font from memory")
		}
	}

	// load terminal font
	err = atlas.terminalFont(prefs, dpi)
	if err != nil {
		return fmt.Errorf("font: %w", err)
	}

	// load source code font
	err = atlas.sourceCodeFont(prefs, dpi)
	if err != nil {
		return fmt.Errorf("font: %w", err)
	}

	// create textures and register with imgui
	tex := renderer.addFontTexture(imgui.CurrentIO().Fonts())
	imgui.CurrentIO().Fonts().SetTextureID(imgui.TextureID(tex.getID()))

	return nil
}

func (atlas *fontAtlas) setDefaultFont(prefs *preferences, dpi float32) error {
	guiFontSize := float32(prefs.guiFontSize.Get().(int))
	if atlas.guiSize != 0 && guiFontSize == atlas.guiSize {
		return nil
	}
	atlas.guiSize = guiFontSize

	// load gui font
	cfg := imgui.NewFontConfig()
	defer cfg.Delete()

	var builder imgui.GlyphRangesBuilder
	builder.Add(fonts.JetBrainsMonoMin, fonts.JetBrainsMonoMax)

	guiFontSize = scaleFontForDPI(guiFontSize, dpi)
	atlas.gui = imgui.CurrentIO().Fonts().AddFontFromMemoryTTFV(fonts.JetBrainsMono, guiFontSize, cfg, builder.Build().GlyphRanges)
	if atlas.gui == 0 {
		return fmt.Errorf("font: error loading JetBrainsMono font from memory")
	}

	atlas.mergeFontAwesome(guiFontSize, 1.0)

	return nil
}

func (atlas *fontAtlas) sourceCodeFont(prefs *preferences, dpi float32) error {
	codeSize := float32(prefs.codeFontSize.Get().(int))
	if atlas.codeSize != 0 && codeSize == atlas.codeSize {
		return nil
	}
	atlas.codeSize = codeSize

	cfg := imgui.NewFontConfig()
	defer cfg.Delete()

	var builder imgui.GlyphRangesBuilder
	builder.Add(fonts.JetBrainsMonoMin, fonts.JetBrainsMonoMax)

	codeSize = scaleFontForDPI(codeSize, dpi)
	atlas.code = imgui.CurrentIO().Fonts().AddFontFromMemoryTTFV(fonts.JetBrainsMono, codeSize, cfg, builder.Build().GlyphRanges)
	if atlas.code == 0 {
		return fmt.Errorf("font: error loading JetBrainsMono font from memory")
	}

	atlas.mergeFontAwesome(codeSize, 0.0)

	return nil
}

func (atlas *fontAtlas) terminalFont(prefs *preferences, dpi float32) error {
	terminalSize := float32(prefs.terminalFontSize.Get().(int))
	if atlas.terminalSize != 0 && terminalSize == atlas.terminalSize {
		return nil
	}
	atlas.terminalSize = terminalSize

	cfg := imgui.NewFontConfig()
	defer cfg.Delete()

	var builder imgui.GlyphRangesBuilder
	builder.Add(fonts.JetBrainsMonoMin, fonts.JetBrainsMonoMax)

	terminalSize = scaleFontForDPI(terminalSize, dpi)
	atlas.terminal = imgui.CurrentIO().Fonts().AddFontFromMemoryTTFV(fonts.JetBrainsMono, terminalSize, cfg, builder.Build().GlyphRanges)
	if atlas.terminal == 0 {
		return fmt.Errorf("font: error loading JetBrainsMono font from memory")
	}

	atlas.mergeFontAwesome(terminalSize, 0.0)

	return nil
}

func (atlas *fontAtlas) mergeFontAwesome(size float32, adjust float32) error {
	mergeConfig := imgui.NewFontConfig()
	defer mergeConfig.Delete()
	mergeConfig.SetMergeMode(true)
	mergeConfig.SetGlyphOffsetY(adjust)

	// limit what glyphs we load
	var glyphBuilder imgui.GlyphRangesBuilder
	glyphBuilder.Add(fonts.FontAwesomeMin, fonts.FontAwesomeMax)

	// merge font awesome
	merge := imgui.CurrentIO().Fonts().AddFontFromMemoryTTFV(fonts.FontAwesome, size, mergeConfig, glyphBuilder.Build().GlyphRanges)
	if merge == 0 {
		return fmt.Errorf("font: error loading font-awesome from memory")
	}

	return nil
}
