// 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"
)

// CellModel models the content of a CellView.  The dimensions used within
// a CellModel are always logical, and have origin 0, 0.
type CellModel interface {
	GetCell(x, y int) (rune, tcell.Style, []rune, int)
	GetBounds() (int, int)
	SetCursor(int, int)
	GetCursor() (int, int, bool, bool)
	MoveCursor(offx, offy int)
}

// CellView is a flexible view of a CellModel, offering both cursor
// management and a panning.
type CellView struct {
	port    *ViewPort
	view    View
	content Widget
	style   tcell.Style
	lines   []string
	model   CellModel
	once    sync.Once

	WidgetWatchers
}

// Draw draws the content.
func (a *CellView) Draw() {

	port := a.port
	model := a.model
	port.Fill(' ', a.style)

	if a.view == nil {
		return
	}
	if model == nil {
		return
	}
	vw, vh := a.view.Size()
	for y := 0; y < vh; y++ {
		for x := 0; x < vw; x++ {
			a.view.SetContent(x, y, ' ', nil, a.style)
		}
	}

	ex, ey := model.GetBounds()
	vx, vy := port.Size()
	if ex < vx {
		ex = vx
	}
	if ey < vy {
		ey = vy
	}

	cx, cy, en, sh := a.model.GetCursor()
	for y := 0; y < ey; y++ {
		for x := 0; x < ex; x++ {
			ch, style, comb, wid := model.GetCell(x, y)
			if ch == 0 {
				ch = ' '
				style = a.style
			}
			if en && x == cx && y == cy && sh {
				style = style.Reverse(true)
			}
			port.SetContent(x, y, ch, comb, style)
			x += wid - 1
		}
	}
}

func (a *CellView) keyUp() {
	if _, _, en, _ := a.model.GetCursor(); !en {
		a.port.ScrollUp(1)
		return
	}
	a.model.MoveCursor(0, -1)
	a.MakeCursorVisible()
}

func (a *CellView) keyDown() {
	if _, _, en, _ := a.model.GetCursor(); !en {
		a.port.ScrollDown(1)
		return
	}
	a.model.MoveCursor(0, 1)
	a.MakeCursorVisible()
}

func (a *CellView) keyLeft() {
	if _, _, en, _ := a.model.GetCursor(); !en {
		a.port.ScrollLeft(1)
		return
	}
	a.model.MoveCursor(-1, 0)
	a.MakeCursorVisible()
}

func (a *CellView) keyRight() {
	if _, _, en, _ := a.model.GetCursor(); !en {
		a.port.ScrollRight(1)
		return
	}
	a.model.MoveCursor(+1, 0)
	a.MakeCursorVisible()
}

func (a *CellView) keyPgUp() {
	_, vy := a.port.Size()
	if _, _, en, _ := a.model.GetCursor(); !en {
		a.port.ScrollUp(vy)
		return
	}
	a.model.MoveCursor(0, -vy)
	a.MakeCursorVisible()
}

func (a *CellView) keyPgDn() {
	_, vy := a.port.Size()
	if _, _, en, _ := a.model.GetCursor(); !en {
		a.port.ScrollDown(vy)
		return
	}
	a.model.MoveCursor(0, +vy)
	a.MakeCursorVisible()
}

func (a *CellView) keyHome() {
	vx, vy := a.model.GetBounds()
	if _, _, en, _ := a.model.GetCursor(); !en {
		a.port.ScrollUp(vy)
		a.port.ScrollLeft(vx)
		return
	}
	a.model.SetCursor(0, 0)
	a.MakeCursorVisible()
}

func (a *CellView) keyEnd() {
	vx, vy := a.model.GetBounds()
	if _, _, en, _ := a.model.GetCursor(); !en {
		a.port.ScrollDown(vy)
		a.port.ScrollRight(vx)
		return
	}
	a.model.SetCursor(vx, vy)
	a.MakeCursorVisible()
}

// MakeCursorVisible ensures that the cursor is visible, panning the ViewPort
// as necessary, if the cursor is enabled.
func (a *CellView) MakeCursorVisible() {
	if a.model == nil {
		return
	}
	x, y, enabled, _ := a.model.GetCursor()
	if enabled {
		a.MakeVisible(x, y)
	}
}

// HandleEvent handles events.  In particular, it handles certain key events
// to move the cursor or pan the view.
func (a *CellView) HandleEvent(e tcell.Event) bool {
	if a.model == nil {
		return false
	}
	switch e := e.(type) {
	case *tcell.EventKey:
		switch e.Key() {
		case tcell.KeyUp, tcell.KeyCtrlP:
			a.keyUp()
			return true
		case tcell.KeyDown, tcell.KeyCtrlN:
			a.keyDown()
			return true
		case tcell.KeyRight, tcell.KeyCtrlF:
			a.keyRight()
			return true
		case tcell.KeyLeft, tcell.KeyCtrlB:
			a.keyLeft()
			return true
		case tcell.KeyPgDn:
			a.keyPgDn()
			return true
		case tcell.KeyPgUp:
			a.keyPgUp()
			return true
		case tcell.KeyEnd:
			a.keyEnd()
			return true
		case tcell.KeyHome:
			a.keyHome()
			return true
		}
	}
	return false
}

// Size returns the content size, based on the model.
func (a *CellView) Size() (int, int) {
	// We always return a minimum of two rows, and two columns.
	w, h := a.model.GetBounds()
	// Clip to a 2x2 minimum square; we can scroll within that.
	if w < 2 {
		w = 2
	}
	if h < 2 {
		h = 2
	}
	return w, h
}

// GetModel gets the model for this CellView
func (a *CellView) GetModel() CellModel {
	return a.model
}

// SetModel sets the model for this CellView.
func (a *CellView) SetModel(model CellModel) {
	w, h := model.GetBounds()
	a.model = model
	a.port.SetContentSize(w, h, true)
	a.port.ValidateView()
	a.PostEventWidgetContent(a)
}

// SetView sets the View context.
func (a *CellView) SetView(view View) {
	port := a.port
	port.SetView(view)
	a.view = view
	if view == nil {
		return
	}
	width, height := view.Size()
	a.port.Resize(0, 0, width, height)
	if a.model != nil {
		w, h := a.model.GetBounds()
		a.port.SetContentSize(w, h, true)
	}
	a.Resize()
}

// Resize is called when the View is resized.  It will ensure that the
// cursor is visible, if present.
func (a *CellView) Resize() {
	// We might want to reflow text
	width, height := a.view.Size()
	a.port.Resize(0, 0, width, height)
	a.port.ValidateView()
	a.MakeCursorVisible()
}

// SetCursor sets the the cursor position.
func (a *CellView) SetCursor(x, y int) {
	a.model.SetCursor(x, y)
}

// SetCursorX sets the the cursor column.
func (a *CellView) SetCursorX(x int) {
	_, y, _, _ := a.model.GetCursor()
	a.SetCursor(x, y)
}

// SetCursorY sets the the cursor row.
func (a *CellView) SetCursorY(y int) {
	x, _, _, _ := a.model.GetCursor()
	a.SetCursor(x, y)
}

// MakeVisible makes the given coordinates visible, if they are not already.
// It does this by moving the ViewPort for the CellView.
func (a *CellView) MakeVisible(x, y int) {
	a.port.MakeVisible(x, y)
}

// SetStyle sets the the default fill style.
func (a *CellView) SetStyle(s tcell.Style) {
	a.style = s
}

// Init initializes a new CellView for use.
func (a *CellView) Init() {
	a.once.Do(func() {
		a.port = NewViewPort(nil, 0, 0, 0, 0)
		a.style = tcell.StyleDefault
	})
}

// NewCellView creates a CellView.
func NewCellView() *CellView {
	cv := &CellView{}
	cv.Init()
	return cv
}
