// Copyright 2023 The Ebitengine Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this 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.

//go:build !ios

package textinput

// #cgo CFLAGS: -x objective-c
// #cgo LDFLAGS: -framework Cocoa
import "C"

import (
	"github.com/ebitengine/purego/objc"

	"github.com/hajimehoshi/ebiten/v2/internal/ui"
)

type textInput struct {
	// session must be accessed from the main thread.
	session *session
}

var theTextInput textInput

func (t *textInput) Start(x, y int) (chan State, func()) {
	var session *session
	ui.Get().RunOnMainThread(func() {
		t.end()
		start(x, y)
		session = newSession()
		t.session = session
	})
	return session.ch, session.end
}

//export ebitengine_textinput_update
func ebitengine_textinput_update(text *C.char, start, end C.int, committed C.int) {
	theTextInput.update(C.GoString(text), int(start), int(end), committed != 0)
}

func (t *textInput) update(text string, start, end int, committed bool) {
	if t.session != nil {
		startInBytes := convertUTF16CountToByteCount(text, start)
		endInBytes := convertUTF16CountToByteCount(text, end)
		t.session.trySend(State{
			Text:                             text,
			CompositionSelectionStartInBytes: startInBytes,
			CompositionSelectionEndInBytes:   endInBytes,
			Committed:                        committed,
		})
	}
	if committed {
		t.end()
	}
}

//export ebitengine_textinput_end
func ebitengine_textinput_end() {
	theTextInput.end()
}

func (t *textInput) end() {
	if t.session != nil {
		t.session.end()
		t.session = nil
	}
}

var (
	selAddSubview         = objc.RegisterName("addSubview:")
	selAlloc              = objc.RegisterName("alloc")
	selContentView        = objc.RegisterName("contentView")
	selFrame              = objc.RegisterName("frame")
	selInit               = objc.RegisterName("init")
	selMainWindow         = objc.RegisterName("mainWindow")
	selMakeFirstResponder = objc.RegisterName("makeFirstResponder:")
	selSetFrame           = objc.RegisterName("setFrame:")
	selSharedApplication  = objc.RegisterName("sharedApplication")

	idNSApplication = objc.ID(objc.GetClass("NSApplication"))
)

var theTextInputClient objc.ID

func getTextInputClient() objc.ID {
	if theTextInputClient == 0 {
		class := objc.ID(objc.GetClass("TextInputClient"))
		theTextInputClient = class.Send(selAlloc).Send(selInit)
	}
	return theTextInputClient
}

type nsPoint struct {
	x float64
	y float64
}

type nsSize struct {
	width  float64
	height float64
}

type nsRect struct {
	origin nsPoint
	size   nsSize
}

func start(x, y int) {
	t := getTextInputClient()
	window := idNSApplication.Send(selSharedApplication).Send(selMainWindow)
	contentView := window.Send(selContentView)
	contentView.Send(selAddSubview, t)
	window.Send(selMakeFirstResponder, t)

	r := objc.Send[nsRect](contentView, selFrame)
	y = int(r.size.height) - y - 4
	t.Send(selSetFrame, nsRect{
		origin: nsPoint{float64(x), float64(y)},
		size:   nsSize{1, 1},
	})
}
