// Copyright 2015 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"

	"go.mau.fi/tcell"
)

// Widget is the base object that all onscreen elements implement.
type Widget interface {
	// Draw is called to inform the widget to draw itself.  A containing
	// Widget will generally call this during the application draw loop.
	Draw()

	// Resize is called in response to a resize of the View.  Unlike with
	// other events, Resize performed by parents first, and they must
	// then call their children.  This is because the children need to
	// see the updated sizes from the parents before they are called.
	// In general this is done *after* the views have updated.
	Resize()

	// HandleEvent is called to ask the widget to handle any events.
	// If the widget has consumed the event, it should return true.
	// Generally, events are handled by the lower layers first, that
	// is for example, a button may have a chance to handle an event
	// before the enclosing window or panel.
	//
	// Its expected that Resize events are consumed by the outermost
	// Widget, and the turned into a Resize() call.
	HandleEvent(ev tcell.Event) bool

	// SetView is used by callers to set the visual context of the
	// Widget.  The Widget should use the View as a context for
	// drawing.
	SetView(view View)

	// Size returns the size of the widget (content size) as width, height
	// in columns.  Layout managers should attempt to ensure that at least
	// this much space is made available to the View for this Widget.  Extra
	// space may be allocated on as an needed basis.
	Size() (int, int)

	// Watch is used to register an interest in this widget's events.
	// The handler will receive EventWidget events for this widget.
	// The order of event delivery when there are multiple watchers is
	// not specified, and may change from one event to the next.
	Watch(handler tcell.EventHandler)

	// Unwatch is used to urnegister an interest in this widget's events.
	Unwatch(handler tcell.EventHandler)
}

// EventWidget is an event delivered by a specific widget.
type EventWidget interface {
	Widget() Widget
	tcell.Event
}

type widgetEvent struct {
	widget Widget
	tcell.EventTime
}

func (wev *widgetEvent) Widget() Widget {
	return wev.widget
}

func (wev *widgetEvent) SetWidget(widget Widget) {
	wev.widget = widget
}

// WidgetWatchers provides a common implementation for base Widget Watch and
// Unwatch interfaces, suitable for embedding in more concrete widget
// implementations. This implementation is thread-safe, meaning an embedder can
// expect safety when calling WidgetWatcher methods from separate goroutines.
// In general, the views package does not support thread safety.
type WidgetWatchers struct {
	sync.Mutex
	watchers map[tcell.EventHandler]struct{}
}

// Watch monitors this WidgetWatcher, causing the handler to be fired
// with EventWidget as they are occur on the watched Widget.
func (ww *WidgetWatchers) Watch(handler tcell.EventHandler) {
	ww.Lock()
	defer ww.Unlock()
	if ww.watchers == nil {
		ww.watchers = make(map[tcell.EventHandler]struct{})
	}
	ww.watchers[handler] = struct{}{}
}

// Unwatch stops monitoring this WidgetWatcher. The handler will no longer
// be fired for Widget events.
func (ww *WidgetWatchers) Unwatch(handler tcell.EventHandler) {
	ww.Lock()
	defer ww.Unlock()
	if ww.watchers != nil {
		delete(ww.watchers, handler)
	}
}

// PostEvent delivers the EventWidget to all registered watchers.  It is
// to be called by the Widget implementation.
func (ww *WidgetWatchers) PostEvent(wev EventWidget) {
	ww.Lock()
	watcherCopy := make(map[tcell.EventHandler]struct{}, len(ww.watchers))
	for k := range ww.watchers {
		watcherCopy[k] = struct{}{}
	}
	ww.Unlock()
	for watcher := range watcherCopy {
		// Deliver events to all listeners, ignoring return value.
		watcher.HandleEvent(wev)
	}
}

// PostEventWidgetContent is called by the Widget when its content is
// changed, delivering EventWidgetContent to all watchers.
func (ww *WidgetWatchers) PostEventWidgetContent(w Widget) {
	ev := &EventWidgetContent{}
	ev.SetWidget(w)
	ev.SetEventNow()
	ww.PostEvent(ev)
}

// PostEventWidgetResize is called by the Widget when the underlying View
// has resized, delivering EventWidgetResize to all watchers.
func (ww *WidgetWatchers) PostEventWidgetResize(w Widget) {
	ev := &EventWidgetResize{}
	ev.SetWidget(w)
	ev.SetEventNow()
	ww.PostEvent(ev)
}

// PostEventWidgetMove is called by the Widget when it is moved to a new
// location, delivering EventWidgetMove to all watchers.
func (ww *WidgetWatchers) PostEventWidgetMove(w Widget) {
	ev := &EventWidgetMove{}
	ev.SetWidget(w)
	ev.SetEventNow()
	ww.PostEvent(ev)
}

// XXX: WidgetExposed, Hidden?
// XXX: WidgetExposed, Hidden?

// EventWidgetContent is fired whenever a widget's content changes.
type EventWidgetContent struct {
	widgetEvent
}

// EventWidgetResize is fired whenever a widget is resized.
type EventWidgetResize struct {
	widgetEvent
}

// EventWidgetMove is fired whenver a widget changes location.
type EventWidgetMove struct {
	widgetEvent
}
