// gomuks - A terminal Matrix client written in Go.
// Copyright (C) 2020 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

//go:build cgo

package ui

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"go.mau.fi/mauview"
	"go.mau.fi/tcell"

	"maunium.net/go/mautrix/crypto"
	"maunium.net/go/mautrix/event"

	"maunium.net/go/gomuks/config"
	"maunium.net/go/gomuks/debug"
)

type EmojiView struct {
	mauview.SimpleEventHandler
	Data crypto.SASData
}

func (e *EmojiView) Draw(screen mauview.Screen) {
	if e.Data == nil {
		return
	}
	switch e.Data.Type() {
	case event.SASEmoji:
		width := 10
		for i, emoji := range e.Data.(crypto.EmojiSASData) {
			x := i*width + i
			y := 0
			if i >= 4 {
				x = (i-4)*width + i
				y = 2
			}
			mauview.Print(screen, string(emoji.Emoji), x, y, width, mauview.AlignCenter, tcell.ColorDefault)
			mauview.Print(screen, emoji.Description, x, y+1, width, mauview.AlignCenter, tcell.ColorDefault)
		}
	case event.SASDecimal:
		maxWidth := 43
		for i, number := range e.Data.(crypto.DecimalSASData) {
			mauview.Print(screen, strconv.FormatUint(uint64(number), 10), 0, i, maxWidth, mauview.AlignCenter, tcell.ColorDefault)
		}
	}
}

type VerificationModal struct {
	mauview.Component

	device *crypto.DeviceIdentity

	container *mauview.Box

	waitingBar *mauview.ProgressBar
	infoText   *mauview.TextView
	emojiText  *EmojiView
	inputBar   *mauview.InputField

	progress    int
	progressMax int
	stopWaiting chan struct{}
	confirmChan chan bool
	done        bool

	parent *MainView
}

func NewVerificationModal(mainView *MainView, device *crypto.DeviceIdentity, timeout time.Duration) *VerificationModal {
	vm := &VerificationModal{
		parent:      mainView,
		device:      device,
		stopWaiting: make(chan struct{}),
		confirmChan: make(chan bool),
		done:        false,
	}

	vm.progressMax = int(timeout.Seconds())
	vm.progress = vm.progressMax
	vm.waitingBar = mauview.NewProgressBar().
		SetMax(vm.progressMax).
		SetProgress(vm.progress).
		SetIndeterminate(false)

	vm.infoText = mauview.NewTextView()
	vm.infoText.SetText(fmt.Sprintf("Waiting for %s\nto accept", device.UserID))

	vm.emojiText = &EmojiView{}

	vm.inputBar = mauview.NewInputField().
		SetBackgroundColor(tcell.ColorDefault).
		SetPlaceholderTextColor(tcell.ColorDefault)

	flex := mauview.NewFlex().
		SetDirection(mauview.FlexRow).
		AddFixedComponent(vm.waitingBar, 1).
		AddFixedComponent(vm.infoText, 4).
		AddFixedComponent(vm.emojiText, 4).
		AddFixedComponent(vm.inputBar, 1)

	vm.container = mauview.NewBox(flex).
		SetBorder(true).
		SetTitle("Interactive verification")

	vm.Component = mauview.Center(vm.container, 45, 12).SetAlwaysFocusChild(true)

	go vm.decrementWaitingBar()

	return vm
}

func (vm *VerificationModal) decrementWaitingBar() {
	for {
		select {
		case <-time.Tick(time.Second):
			if vm.progress <= 0 {
				vm.waitingBar.SetIndeterminate(true)
				vm.parent.parent.app.SetRedrawTicker(100 * time.Millisecond)
				return
			}
			vm.progress--
			vm.waitingBar.SetProgress(vm.progress)
			vm.parent.parent.Render()
		case <-vm.stopWaiting:
			return
		}
	}
}

func (vm *VerificationModal) VerificationMethods() []crypto.VerificationMethod {
	return []crypto.VerificationMethod{crypto.VerificationMethodEmoji{}, crypto.VerificationMethodDecimal{}}
}

func (vm *VerificationModal) VerifySASMatch(device *crypto.DeviceIdentity, data crypto.SASData) bool {
	vm.device = device
	var typeName string
	if data.Type() == event.SASDecimal {
		typeName = "numbers"
	} else if data.Type() == event.SASEmoji {
		typeName = "emojis"
	} else {
		return false
	}
	vm.infoText.SetText(fmt.Sprintf(
		"Check if the other device is showing the\n"+
			"same %s as below, then type \"yes\" to\n"+
			"accept, or \"no\" to reject", typeName))
	vm.inputBar.
		SetTextColor(tcell.ColorDefault).
		SetBackgroundColor(tcell.ColorDarkCyan).
		SetPlaceholder("Type \"yes\" or \"no\"").
		Focus()
	vm.emojiText.Data = data
	vm.parent.parent.Render()
	vm.progress = vm.progressMax
	confirm := <-vm.confirmChan
	vm.progress = vm.progressMax
	vm.emojiText.Data = nil
	vm.infoText.SetText(fmt.Sprintf("Waiting for %s\nto confirm", vm.device.UserID))
	vm.parent.parent.Render()
	return confirm
}

func (vm *VerificationModal) OnCancel(cancelledByUs bool, reason string, _ event.VerificationCancelCode) {
	vm.waitingBar.SetIndeterminate(false).SetMax(100).SetProgress(100)
	vm.parent.parent.app.SetRedrawTicker(1 * time.Minute)
	if cancelledByUs {
		vm.infoText.SetText(fmt.Sprintf("Verification failed: %s", reason))
	} else {
		vm.infoText.SetText(fmt.Sprintf("Verification cancelled by %s: %s", vm.device.UserID, reason))
	}
	vm.inputBar.SetPlaceholder("Press enter to close the dialog")
	vm.stopWaiting <- struct{}{}
	vm.done = true
	vm.parent.parent.Render()
}

func (vm *VerificationModal) OnSuccess() {
	vm.waitingBar.SetIndeterminate(false).SetMax(100).SetProgress(100)
	vm.parent.parent.app.SetRedrawTicker(1 * time.Minute)
	vm.infoText.SetText(fmt.Sprintf("Successfully verified %s (%s) of %s", vm.device.Name, vm.device.DeviceID, vm.device.UserID))
	vm.inputBar.SetPlaceholder("Press enter to close the dialog")
	vm.stopWaiting <- struct{}{}
	vm.done = true
	vm.parent.parent.Render()
	if vm.parent.config.SendToVerifiedOnly {
		// Hacky way to make new group sessions after verified
		vm.parent.matrix.Crypto().(*crypto.OlmMachine).OnDevicesChanged(vm.device.UserID)
	}
}

func (vm *VerificationModal) OnKeyEvent(event mauview.KeyEvent) bool {
	kb := config.Keybind{
		Key: event.Key(),
		Ch:  event.Rune(),
		Mod: event.Modifiers(),
	}
	if vm.done {
		if vm.parent.config.Keybindings.Modal[kb] == "cancel" || vm.parent.config.Keybindings.Modal[kb] == "confirm" {
			vm.parent.HideModal()
			return true
		}
		return false
	} else if vm.emojiText.Data == nil {
		debug.Print("Ignoring pre-emoji key event")
		return false
	}
	if vm.parent.config.Keybindings.Modal[kb] == "confirm" {
		text := strings.ToLower(strings.TrimSpace(vm.inputBar.GetText()))
		if text == "yes" {
			debug.Print("Confirming verification")
			vm.confirmChan <- true
		} else if text == "no" {
			debug.Print("Rejecting verification")
			vm.confirmChan <- false
		}
		vm.inputBar.
			SetPlaceholder("").
			SetTextAndMoveCursor("").
			SetBackgroundColor(tcell.ColorDefault).
			SetTextColor(tcell.ColorDefault)
		return true
	} else {
		return vm.inputBar.OnKeyEvent(event)
	}
}

func (vm *VerificationModal) Focus() {
	vm.container.Focus()
}

func (vm *VerificationModal) Blur() {
	vm.container.Blur()
}
