// Copyright 2024 The Tcell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package views

import (
	"sync"

	"github.com/gdamore/tcell/v2"
)

// Application represents an event-driven application running on a screen.
type Application struct {
	widget   Widget
	screen   tcell.Screen
	style    tcell.Style
	err      error
	wg       sync.WaitGroup
	paste    bool
	stopQ    chan struct{}
	eventQ   chan tcell.Event
	stopOnce sync.Once
}

// SetRootWidget sets the primary (root, main) Widget to be displayed.
func (app *Application) SetRootWidget(widget Widget) {
	app.widget = widget
}

// EnablePaste enables or disables pasting support in the application.
// It must be called before Start or Run or else it will have no effect.
func (app *Application) EnablePaste(on bool) {
	app.paste = on
}

// initialize initializes the application.  It will normally attempt to
// allocate a default screen if one is not already established.
func (app *Application) initialize() error {
	if app.screen == nil {
		if app.screen, app.err = tcell.NewScreen(); app.err != nil {
			return app.err
		}
		app.screen.SetStyle(app.style)
	}
	return nil
}

// Quit causes the application to shutdown gracefully.  It does not wait
// for the application to exit, but returns immediately.
func (app *Application) Quit() {
	app.stopOnce.Do(func() { close(app.stopQ) })
}

// Refresh causes the application forcibly redraw everything.  Use this
// to clear up screen corruption, etc.
func (app *Application) Refresh() {
	if scr := app.screen; scr != nil {
		scr.Sync()
	}
}

// Update asks the application to draw any screen updates that have not
// been drawn yet.  It is not necessary to call this from inside an
// event handler, as a draw will be done implicitly after handling events.
func (app *Application) Update() {
	if scr := app.screen; scr != nil {
		scr.Show()
	}
}

// PostFunc posts a function to be executed in the context of the
// application's event loop.  Functions that need to update displayed
// state, etc. can do this to avoid holding locks.
func (app *Application) PostFunc(fn func()) {
	ev := &eventAppFunc{fn: fn}
	ev.SetEventNow()
	if scr := app.screen; scr != nil {
		go func() { scr.PostEventWait(ev) }()
	}
}

// SetScreen sets the screen to use for the application.  This must be
// done before the application starts to run or is initialized.
func (app *Application) SetScreen(scr tcell.Screen) {
	if app.screen == nil {
		app.screen = scr
		app.err = nil
	}
}

// SetStyle sets the default style (background) to be used for Widgets
// that have not specified any other style.
func (app *Application) SetStyle(style tcell.Style) {
	app.style = style
	if app.screen != nil {
		app.screen.SetStyle(style)
	}
}

func (app *Application) run() {

	screen := app.screen
	widget := app.widget

	if widget == nil {
		app.wg.Done()
		return
	}
	if screen == nil {
		if e := app.initialize(); e != nil {
			app.wg.Done()
			return
		}
		screen = app.screen
	}
	defer func() {
		screen.Fini()
		app.wg.Done()
	}()
	screen.Init()
	screen.EnableMouse()
	if app.paste {
		screen.EnablePaste()
	}
	screen.Clear()
	widget.SetView(screen)

	app.eventQ = make(chan tcell.Event, 16)
	app.stopQ = make(chan struct{})
	go screen.ChannelEvents(app.eventQ, app.stopQ)

loop:
	for {
		if widget = app.widget; widget == nil {
			break
		}
		widget.Draw()
		screen.Show()

		ev := <-app.eventQ
		if ev == nil {
			screen.Fini()
			break loop
		}
		switch nev := ev.(type) {
		case *eventAppFunc:
			nev.fn()
		case *tcell.EventResize:
			screen.Sync()
			widget.Resize()
		default:
			widget.HandleEvent(ev)
		}
	}
}

// Start starts the application loop, initializing the screen
// and starting the Event loop.  The application will run in a goroutine
// until Quit is called.
func (app *Application) Start() {
	app.wg.Add(1)
	if app.screen == nil {
		if e := app.initialize(); e != nil {
			app.wg.Done()
			return
		}
	}
	go app.run()
}

// Wait waits until the application finishes.
func (app *Application) Wait() error {
	app.wg.Wait()
	return app.err
}

// Run runs the application, waiting until the application loop exits.
// It is equivalent to app.Start() followed by app.Wait()
func (app *Application) Run() error {
	app.Start()
	return app.Wait()
}

type eventAppFunc struct {
	tcell.EventTime
	fn func()
}
