// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build freebsd || linux || darwin || windows || openbsd

package app

import (
	"fyne.io/fyne/v2/internal/async"
	"fyne.io/fyne/v2/internal/driver/mobile/event/lifecycle"
	"fyne.io/fyne/v2/internal/driver/mobile/event/size"
	"fyne.io/fyne/v2/internal/driver/mobile/gl"

	// Initialize necessary mobile functionality, such as logging.
	_ "fyne.io/fyne/v2/internal/driver/mobile/mobileinit"
)

// Main is called by the main.main function to run the mobile application.
//
// It calls f on the App, in a separate goroutine, as some OS-specific
// libraries require being on 'the main thread'.
func Main(f func(App)) {
	main(f)
}

// App is how a GUI mobile application interacts with the OS.
type App interface {
	// Events returns the events channel. It carries events from the system to
	// the app. The type of such events include:
	//  - lifecycle.Event
	//  - mouse.Event
	//  - paint.Event
	//  - size.Event
	//  - touch.Event
	// from the golang.org/x/mobile/event/etc packages. Other packages may
	// define other event types that are carried on this channel.
	Events() <-chan any

	// Send sends an event on the events channel. It does not block.
	Send(event any)

	// Publish flushes any pending drawing commands, such as OpenGL calls, and
	// swaps the back buffer to the screen.
	Publish() PublishResult

	// TODO: replace filters (and the Events channel) with a NextEvent method?

	// Filter calls each registered event filter function in sequence.
	Filter(event any) any

	// RegisterFilter registers a event filter function to be called by Filter. The
	// function can return a different event, or return nil to consume the event,
	// but the function can also return its argument unchanged, where its purpose
	// is to trigger a side effect rather than modify the event.
	RegisterFilter(f func(any) any)

	ShowVirtualKeyboard(KeyboardType)
	HideVirtualKeyboard()
	ShowFileOpenPicker(func(string, func()), *FileFilter)
	ShowFileSavePicker(func(string, func()), *FileFilter, string)
}

// FileFilter is a filter of files.
type FileFilter struct {
	Extensions []string
	MimeTypes  []string
}

// PublishResult is the result of an App.Publish call.
type PublishResult struct {
	// BackBufferPreserved is whether the contents of the back buffer was
	// preserved. If false, the contents are undefined.
	BackBufferPreserved bool
}

var theApp = &app{
	events:         async.NewUnboundedChan[any](),
	lifecycleStage: lifecycle.StageDead,
	publish:        make(chan struct{}),
	publishResult:  make(chan PublishResult),
}

func init() {
	theApp.glctx, theApp.worker = gl.NewContext()
}

func (a *app) sendLifecycle(to lifecycle.Stage) {
	if a.lifecycleStage == to {
		return
	}
	a.events.In() <- lifecycle.Event{
		From:        a.lifecycleStage,
		To:          to,
		DrawContext: a.glctx,
	}
	a.lifecycleStage = to
}

type app struct {
	filters []func(any) any

	events         *async.UnboundedChan[any]
	lifecycleStage lifecycle.Stage
	publish        chan struct{}
	publishResult  chan PublishResult

	glctx  gl.Context
	worker gl.Worker
}

func (a *app) Events() <-chan any {
	return a.events.Out()
}

func (a *app) Send(event any) {
	a.events.In() <- event
}

func (a *app) Publish() PublishResult {
	// gl.Flush is a lightweight (on modern GL drivers) blocking call
	// that ensures all GL functions pending in the gl package have
	// been passed onto the GL driver before the app package attempts
	// to swap the screen buffer.
	//
	// This enforces that the final receive (for this paint cycle) on
	// gl.WorkAvailable happens before the send on endPaint.
	a.glctx.Flush()
	a.publish <- struct{}{}
	return <-a.publishResult
}

func (a *app) Filter(event any) any {
	for _, f := range a.filters {
		event = f(event)
	}
	return event
}

func (a *app) RegisterFilter(f func(any) any) {
	a.filters = append(a.filters, f)
}

func (a *app) ShowVirtualKeyboard(keyboard KeyboardType) {
	driverShowVirtualKeyboard(keyboard)
}

func (a *app) HideVirtualKeyboard() {
	driverHideVirtualKeyboard()
}

func (a *app) ShowFileOpenPicker(callback func(string, func()), filter *FileFilter) {
	driverShowFileOpenPicker(callback, filter)
}
func (a *app) ShowFileSavePicker(callback func(string, func()), filter *FileFilter, filename string) {
	driverShowFileSavePicker(callback, filter, filename)
}

// TODO: do this for all build targets, not just linux (x11 and Android)? If
// so, should package gl instead of this package call RegisterFilter??
//
// TODO: does Android need this?? It seems to work without it (Nexus 7,
// KitKat). If only x11 needs this, should we move this to x11.go??
func (a *app) registerGLViewportFilter() {
	a.RegisterFilter(func(e any) any {
		if e, ok := e.(size.Event); ok {
			a.glctx.Viewport(0, 0, e.WidthPx, e.HeightPx)
		}
		return e
	})
}

func screenOrientation(width, height int) size.Orientation {
	if width > height {
		return size.OrientationLandscape
	}

	return size.OrientationPortrait
}
