package gioui

import (
	"fmt"
	"image"
	"strconv"
	"strings"

	"gioui.org/io/key"
	"gioui.org/io/pointer"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/op/clip"
	"gioui.org/op/paint"
	"gioui.org/text"
	"gioui.org/unit"
	"gioui.org/widget"
	"github.com/vsariola/sointu/tracker"
)

const patternCellHeight = 16
const patternCellWidth = 16
const patternRowMarkerWidth = 30

type OrderEditor struct {
	list         *layout.List
	titleList    *DragList
	scrollBar    *ScrollBar
	tag          bool
	focused      bool
	requestFocus bool
}

func NewOrderEditor() *OrderEditor {
	return &OrderEditor{
		list:      &layout.List{Axis: layout.Vertical},
		titleList: &DragList{List: &layout.List{Axis: layout.Horizontal}},
		scrollBar: &ScrollBar{Axis: layout.Vertical},
	}
}

func (oe *OrderEditor) Focus() {
	oe.requestFocus = true
}

func (oe *OrderEditor) Focused() bool {
	return oe.focused
}

func (oe *OrderEditor) Layout(gtx C, t *Tracker) D {
	return Surface{Gray: 24, Focus: oe.focused}.Layout(gtx, func(gtx C) D {
		return oe.doLayout(gtx, t)
	})
}

func (oe *OrderEditor) doLayout(gtx C, t *Tracker) D {
	for _, e := range gtx.Events(&oe.tag) {
		switch e := e.(type) {
		case key.FocusEvent:
			oe.focused = e.Focus
		case pointer.Event:
			if e.Type == pointer.Press {
				key.FocusOp{Tag: &oe.tag}.Add(gtx.Ops)
			}
		case key.Event:
			if e.State != key.Press {
				continue
			}
			switch e.Name {
			case key.NameDeleteForward, key.NameDeleteBackward:
				if e.Modifiers.Contain(key.ModShortcut) {
					t.DeleteOrderRow(e.Name == key.NameDeleteForward)
				} else {
					t.DeletePatternSelection()
					if !(t.NoteTracking() && t.Playing()) && t.Step.Value > 0 {
						t.SetCursor(t.Cursor().AddPatterns(1))
						t.SetSelectionCorner(t.Cursor())
					}
				}
			case "Space":
				if !t.Playing() {
					t.SetNoteTracking(!e.Modifiers.Contain(key.ModShortcut))
					startRow := t.Cursor().SongRow
					startRow.Row = 0
					t.PlayFromPosition(startRow)
				} else {
					t.SetPlaying(false)
				}
			case key.NameReturn:
				t.AddOrderRow(!e.Modifiers.Contain(key.ModShortcut))
			case key.NameUpArrow:
				cursor := t.Cursor()
				if e.Modifiers.Contain(key.ModShortcut) {
					cursor.SongRow = tracker.SongRow{}
				} else {
					cursor.Row -= t.Song().Score.RowsPerPattern
				}
				t.SetNoteTracking(false)
				t.SetCursor(cursor)
			case key.NameDownArrow:
				cursor := t.Cursor()
				if e.Modifiers.Contain(key.ModShortcut) {
					cursor.Row = t.Song().Score.LengthInRows() - 1
				} else {
					cursor.Row += t.Song().Score.RowsPerPattern
				}
				t.SetNoteTracking(false)
				t.SetCursor(cursor)
			case key.NameLeftArrow:
				cursor := t.Cursor()
				if e.Modifiers.Contain(key.ModShortcut) {
					cursor.Track = 0
				} else {
					cursor.Track--
				}
				t.SetCursor(cursor)
			case key.NameRightArrow:
				cursor := t.Cursor()
				if e.Modifiers.Contain(key.ModShortcut) {
					cursor.Track = len(t.Song().Score.Tracks) - 1
				} else {
					cursor.Track++
				}
				t.SetCursor(cursor)
			case "+":
				t.AdjustPatternNumber(1, e.Modifiers.Contain(key.ModShortcut))
				continue
			case "-":
				t.AdjustPatternNumber(-1, e.Modifiers.Contain(key.ModShortcut))
				continue
			case key.NameHome:
				cursor := t.Cursor()
				cursor.Track = 0
				t.SetCursor(cursor)
			case key.NameEnd:
				cursor := t.Cursor()
				cursor.Track = len(t.Song().Score.Tracks) - 1
				t.SetCursor(cursor)
			}
			if (e.Name != key.NameLeftArrow &&
				e.Name != key.NameRightArrow &&
				e.Name != key.NameUpArrow &&
				e.Name != key.NameDownArrow) ||
				!e.Modifiers.Contain(key.ModShift) {
				t.SetSelectionCorner(t.Cursor())
			}
			if e.Modifiers.Contain(key.ModShortcut) {
				continue
			}
			if iv, err := strconv.Atoi(e.Name); err == nil {
				t.SetCurrentPattern(iv)
				if !(t.NoteTracking() && t.Playing()) && t.Step.Value > 0 {
					t.SetCursor(t.Cursor().AddPatterns(1))
					t.SetSelectionCorner(t.Cursor())
				}
			}
			if b := int(e.Name[0]) - 'A'; len(e.Name) == 1 && b >= 0 && b < 26 {
				t.SetCurrentPattern(b + 10)
				if !(t.NoteTracking() && t.Playing()) && t.Step.Value > 0 {
					t.SetCursor(t.Cursor().AddPatterns(1))
					t.SetSelectionCorner(t.Cursor())
				}
			}
		}
	}
	defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
	if oe.requestFocus {
		oe.requestFocus = false
		key.FocusOp{Tag: &oe.tag}.Add(gtx.Ops)
	}
	defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
	pointer.InputOp{Tag: &oe.tag,
		Types: pointer.Press,
	}.Add(gtx.Ops)

	key.InputOp{Tag: &oe.tag, Keys: "←|→|↑|↓|Shift-←|Shift-→|Shift-↑|Shift-↓|⏎|⇱|⇲|⌫|⌦|Ctrl-⌫|Ctrl-⌦|+|-|Space|0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z"}.Add(gtx.Ops)

	patternRect := tracker.SongRect{
		Corner1: tracker.SongPoint{SongRow: tracker.SongRow{Pattern: t.Cursor().Pattern}, Track: t.Cursor().Track},
		Corner2: tracker.SongPoint{SongRow: tracker.SongRow{Pattern: t.SelectionCorner().Pattern}, Track: t.SelectionCorner().Track},
	}

	// draw the single letter titles for tracks
	{
		gtx := gtx
		stack := op.Offset(image.Pt(patternRowMarkerWidth, 0)).Push(gtx.Ops)
		gtx.Constraints = layout.Exact(image.Pt(gtx.Constraints.Max.X-patternRowMarkerWidth, patternCellHeight))
		elem := func(gtx C, i int) D {
			gtx.Constraints = layout.Exact(image.Pt(patternCellWidth, patternCellHeight))
			instr, err := t.Song().Patch.InstrumentForVoice(t.Song().Score.FirstVoiceForTrack(i))
			var title string
			if err == nil && len(t.Song().Patch[instr].Name) > 0 {
				title = string(t.Song().Patch[instr].Name[0])
			} else {
				title = "?"
			}
			LabelStyle{Alignment: layout.N, Text: title, FontSize: unit.Sp(12), Color: mediumEmphasisTextColor}.Layout(gtx)
			return D{Size: gtx.Constraints.Min}
		}
		style := FilledDragList(t.Theme, oe.titleList, len(t.Song().Score.Tracks), elem, t.SwapTracks)
		style.HoverColor = transparent
		style.SelectedColor = transparent
		style.Layout(gtx)
		stack.Pop()
	}
	op.Offset(image.Pt(0, patternCellHeight)).Add(gtx.Ops)
	gtx.Constraints.Max.Y -= patternCellHeight
	gtx.Constraints.Min.Y -= patternCellHeight
	element := func(gtx C, j int) D {
		if playPos := t.PlayPosition(); t.Playing() && j == playPos.Pattern {
			paint.FillShape(gtx.Ops, patternPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, patternCellHeight)}.Op())
		}
		paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops)
		widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", j)), op.CallOp{})
		stack := op.Offset(image.Pt(patternRowMarkerWidth, 0)).Push(gtx.Ops)
		for i, track := range t.Song().Score.Tracks {
			paint.FillShape(gtx.Ops, patternCellColor, clip.Rect{Min: image.Pt(1, 1), Max: image.Pt(patternCellWidth-1, patternCellHeight-1)}.Op())
			paint.ColorOp{Color: patternTextColor}.Add(gtx.Ops)
			if j >= 0 && j < len(track.Order) && track.Order[j] >= 0 {
				gtx := gtx
				gtx.Constraints.Max.X = patternCellWidth
				op.Offset(image.Pt(0, -2)).Add(gtx.Ops)
				widget.Label{Alignment: text.Middle}.Layout(gtx, textShaper, trackerFont, trackerFontSize, patternIndexToString(track.Order[j]), op.CallOp{})
				op.Offset(image.Pt(0, 2)).Add(gtx.Ops)
			}
			point := tracker.SongPoint{Track: i, SongRow: tracker.SongRow{Pattern: j}}
			if oe.focused || t.TrackEditor.Focused() {
				if patternRect.Contains(point) {
					color := inactiveSelectionColor
					if oe.focused {
						color = selectionColor
						if point.Pattern == t.Cursor().Pattern && point.Track == t.Cursor().Track {
							color = cursorColor
						}
					}
					paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(patternCellWidth, patternCellHeight)}.Op())
				}
			}
			op.Offset(image.Pt(patternCellWidth, 0)).Add(gtx.Ops)
		}
		stack.Pop()
		return D{Size: image.Pt(gtx.Constraints.Max.X, patternCellHeight)}
	}

	return layout.Stack{Alignment: layout.NE}.Layout(gtx,
		layout.Expanded(func(gtx C) D {
			return oe.list.Layout(gtx, t.Song().Score.Length, element)
		}),
		layout.Expanded(func(gtx C) D {
			return oe.scrollBar.Layout(gtx, unit.Dp(10), t.Song().Score.Length, &oe.list.Position)
		}),
	)
}

func patternIndexToString(index int) string {
	if index < 0 {
		return ""
	} else if index < 10 {
		return string('0' + byte(index))
	}
	return string('A' + byte(index-10))
}