// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2002-2006 Marcus Geelnard
// SPDX-FileCopyrightText: 2006-2019 Camilla Löwy <elmindreda@glfw.org>
// SPDX-FileCopyrightText: 2022 The Ebitengine Authors

package glfw

import (
	"sort"
)

func abs(x int) uint {
	if x < 0 {
		return uint(-x)
	}
	return uint(x)
}

func (v *VidMode) equals(other *VidMode) bool {
	if v.RedBits+v.GreenBits+v.BlueBits != other.RedBits+other.GreenBits+other.BlueBits {
		return false
	}

	if v.Width != other.Width {
		return false
	}

	if v.Height != other.Height {
		return false
	}

	if v.RefreshRate != other.RefreshRate {
		return false
	}

	return true
}

func (m *Monitor) refreshVideoModes() error {
	m.modes = m.modes[:0]
	modes, err := m.platformAppendVideoModes(m.modes)
	if err != nil {
		return err
	}
	sort.Slice(modes, func(i, j int) bool {
		a := modes[i]
		b := modes[j]
		abpp := a.RedBits + a.GreenBits + a.BlueBits
		bbpp := b.RedBits + b.GreenBits + b.BlueBits
		if abpp != bbpp {
			return abpp < bbpp
		}
		aarea := a.Width * a.Height
		barea := b.Width * b.Height
		if aarea != barea {
			return aarea < barea
		}
		if a.Width != b.Width {
			return a.Width < b.Width
		}
		return a.RefreshRate < b.RefreshRate
	})
	m.modes = modes
	return nil
}

func inputMonitor(monitor *Monitor, action PeripheralEvent, placement int) error {
	switch action {
	case Connected:
		switch placement {
		case _GLFW_INSERT_FIRST:
			_glfw.monitors = append(_glfw.monitors, nil)
			copy(_glfw.monitors[1:], _glfw.monitors)
			_glfw.monitors[0] = monitor
		case _GLFW_INSERT_LAST:
			_glfw.monitors = append(_glfw.monitors, monitor)
		}
	case Disconnected:
		for _, window := range _glfw.windows {
			if window.monitor == monitor {
				width, height, err := window.platformGetWindowSize()
				if err != nil {
					return err
				}
				if err := window.platformSetWindowMonitor(nil, 0, 0, width, height, 0); err != nil {
					return err
				}
				xoff, yoff, _, _, err := window.platformGetWindowFrameSize()
				if err != nil {
					return err
				}
				if err := window.platformSetWindowPos(xoff, yoff); err != nil {
					return err
				}
			}
		}
		for i, m := range _glfw.monitors {
			if m == monitor {
				copy(_glfw.monitors[i:], _glfw.monitors[i+1:])
				_glfw.monitors[len(_glfw.monitors)-1] = nil
				_glfw.monitors = _glfw.monitors[:len(_glfw.monitors)-1]
				break
			}
		}
	}

	if _glfw.callbacks.monitor != nil {
		_glfw.callbacks.monitor(monitor, action)
	}

	return nil
}

func (m *Monitor) inputMonitorWindow(window *Window) {
	m.window = window
}

func (m *Monitor) chooseVideoMode(desired *VidMode) (*VidMode, error) {
	if err := m.refreshVideoModes(); err != nil {
		return nil, err
	}

	// math.MaxUint was added at Go 1.17. See https://github.com/golang/go/issues/28538
	const (
		intSize = 32 << (^uint(0) >> 63)
		maxUint = 1<<intSize - 1
	)

	var (
		leastColorDiff uint = maxUint
		leastSizeDiff  uint = maxUint
		leastRateDiff  uint = maxUint
	)

	var closest *VidMode
	for _, v := range m.modes {
		var colorDiff uint
		if desired.RedBits != DontCare {
			colorDiff += abs(v.RedBits - desired.RedBits)
		}
		if desired.GreenBits != DontCare {
			colorDiff += abs(v.GreenBits - desired.GreenBits)
		}
		if desired.BlueBits != DontCare {
			colorDiff += abs(v.BlueBits - desired.BlueBits)
		}

		sizeDiff := abs((v.Width-desired.Width)*(v.Width-desired.Width) +
			(v.Height-desired.Height)*(v.Height-desired.Height))

		var rateDiff uint
		if desired.RefreshRate != DontCare {
			rateDiff = abs(v.RefreshRate - desired.RefreshRate)
		} else {
			rateDiff = maxUint - uint(v.RefreshRate)
		}

		if colorDiff < leastColorDiff ||
			colorDiff == leastColorDiff && sizeDiff < leastSizeDiff ||
			colorDiff == leastColorDiff && sizeDiff == leastSizeDiff && rateDiff < leastRateDiff {
			closest = v
			leastColorDiff = colorDiff
			leastSizeDiff = sizeDiff
			leastRateDiff = rateDiff
		}
	}

	return closest, nil
}

func splitBPP(bpp int) (red, green, blue int) {
	// We assume that by 32 the user really meant 24
	if bpp == 32 {
		bpp = 24
	}

	// Convert "bits per pixel" to red, green & blue sizes
	red = bpp / 3
	green = bpp / 3
	blue = bpp / 3
	delta := bpp - (red * 3)
	if delta >= 1 {
		green++
	}
	if delta == 2 {
		red++
	}
	return
}

// GLFW public APIs

func GetMonitors() ([]*Monitor, error) {
	if !_glfw.initialized {
		return nil, NotInitialized
	}
	return _glfw.monitors, nil
}

func GetPrimaryMonitor() (*Monitor, error) {
	if !_glfw.initialized {
		return nil, NotInitialized
	}
	if len(_glfw.monitors) == 0 {
		return nil, nil
	}
	return _glfw.monitors[0], nil
}

func (m *Monitor) GetPos() (xpos, ypos int, err error) {
	if !_glfw.initialized {
		return 0, 0, NotInitialized
	}
	xpos, ypos, ok := m.platformGetMonitorPos()
	if !ok {
		return 0, 0, nil
	}
	return xpos, ypos, nil
}

func (m *Monitor) GetWorkarea() (xpos, ypos, width, height int, err error) {
	if !_glfw.initialized {
		return 0, 0, 0, 0, NotInitialized
	}
	xpos, ypos, width, height = m.platformGetMonitorWorkarea()
	return
}

// GetPhysicalSize is not implemented.

func (m *Monitor) GetContentScale() (xscale, yscale float32, err error) {
	if !_glfw.initialized {
		return 0, 0, NotInitialized
	}
	xscale, yscale, err = m.platformGetMonitorContentScale()
	return
}

func (m *Monitor) GetName() (string, error) {
	if !_glfw.initialized {
		return "", NotInitialized
	}
	return m.name, nil
}

// SetUserPointer is not implemented.
// GetUserPointer is not implemented.

func SetMonitorCallback(cbfun MonitorCallback) (MonitorCallback, error) {
	if !_glfw.initialized {
		return nil, NotInitialized
	}
	old := _glfw.callbacks.monitor
	_glfw.callbacks.monitor = cbfun
	return old, nil
}

func (m *Monitor) GetVideoModes() ([]*VidMode, error) {
	if !_glfw.initialized {
		return nil, NotInitialized
	}
	return m.modes, nil
}

func (m *Monitor) GetVideoMode() (*VidMode, error) {
	if !_glfw.initialized {
		return nil, NotInitialized
	}
	return m.platformGetVideoMode(), nil
}

// SetGamma is not implemented.
// GetGammaRamp is not implemented.
// SetGammaRamp is not implemented.