// Copyright 2017 The Walk Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build windows

package walk

import (
	"syscall"
	"unsafe"

	"github.com/lxn/win"
)

type LinkLabel struct {
	WidgetBase
	textChangedPublisher   EventPublisher
	linkActivatedPublisher LinkLabelLinkEventPublisher
}

func NewLinkLabel(parent Container) (*LinkLabel, error) {
	ll := new(LinkLabel)

	if err := InitWidget(
		ll,
		parent,
		"SysLink",
		win.WS_TABSTOP|win.WS_VISIBLE,
		0); err != nil {
		return nil, err
	}

	ll.SetBackground(nullBrushSingleton)

	ll.MustRegisterProperty("Text", NewProperty(
		func() interface{} {
			return ll.Text()
		},
		func(v interface{}) error {
			return ll.SetText(assertStringOr(v, ""))
		},
		ll.textChangedPublisher.Event()))

	return ll, nil
}

func (ll *LinkLabel) Text() string {
	return ll.text()
}

func (ll *LinkLabel) SetText(value string) error {
	if value == ll.Text() {
		return nil
	}

	if err := ll.setText(value); err != nil {
		return err
	}

	ll.RequestLayout()

	return nil
}

func (ll *LinkLabel) LinkActivated() *LinkLabelLinkEvent {
	return ll.linkActivatedPublisher.Event()
}

func (ll *LinkLabel) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
	switch msg {
	case win.WM_NOTIFY:
		nml := (*win.NMLINK)(unsafe.Pointer(lParam))

		switch nml.Hdr.Code {
		case win.NM_CLICK, win.NM_RETURN:
			link := &LinkLabelLink{
				ll:    ll,
				index: int(nml.Item.ILink),
				id:    syscall.UTF16ToString(nml.Item.SzID[:]),
				url:   syscall.UTF16ToString(nml.Item.SzUrl[:]),
			}

			ll.linkActivatedPublisher.Publish(link)
		}

	case win.WM_KILLFOCUS:
		ll.ensureStyleBits(win.WS_TABSTOP, true)

	case win.WM_SETTEXT:
		ll.textChangedPublisher.Publish()

	case win.WM_WINDOWPOSCHANGED:
		wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam))

		if wp.Flags&win.SWP_NOSIZE != 0 {
			break
		}

		ll.Invalidate()
	}

	return ll.WidgetBase.WndProc(hwnd, msg, wParam, lParam)
}

type LinkLabelLinkEventHandler func(link *LinkLabelLink)

type LinkLabelLinkEvent struct {
	handlers []LinkLabelLinkEventHandler
}

func (e *LinkLabelLinkEvent) Attach(handler LinkLabelLinkEventHandler) int {
	for i, h := range e.handlers {
		if h == nil {
			e.handlers[i] = handler
			return i
		}
	}

	e.handlers = append(e.handlers, handler)
	return len(e.handlers) - 1
}

func (e *LinkLabelLinkEvent) Detach(handle int) {
	e.handlers[handle] = nil
}

type LinkLabelLinkEventPublisher struct {
	event LinkLabelLinkEvent
}

func (p *LinkLabelLinkEventPublisher) Event() *LinkLabelLinkEvent {
	return &p.event
}

func (p *LinkLabelLinkEventPublisher) Publish(link *LinkLabelLink) {
	for _, handler := range p.event.handlers {
		if handler != nil {
			handler(link)
		}
	}
}

type LinkLabelLink struct {
	ll    *LinkLabel
	index int
	id    string
	url   string
}

func (lll *LinkLabelLink) Index() int {
	return lll.index
}

func (lll *LinkLabelLink) Id() string {
	return lll.id
}

func (lll *LinkLabelLink) URL() string {
	return lll.url
}

func (lll *LinkLabelLink) Enabled() (bool, error) {
	return lll.hasState(win.LIS_ENABLED)
}

func (lll *LinkLabelLink) SetEnabled(enabled bool) error {
	return lll.setState(win.LIS_ENABLED, enabled)
}

func (lll *LinkLabelLink) Focused() (bool, error) {
	return lll.hasState(win.LIS_FOCUSED)
}

func (lll *LinkLabelLink) SetFocused(focused bool) error {
	return lll.setState(win.LIS_FOCUSED, focused)
}

func (lll *LinkLabelLink) Visited() (bool, error) {
	return lll.hasState(win.LIS_VISITED)
}

func (lll *LinkLabelLink) SetVisited(visited bool) error {
	return lll.setState(win.LIS_VISITED, visited)
}

func (lll *LinkLabelLink) hasState(state uint32) (bool, error) {
	li := win.LITEM{
		ILink:     int32(lll.index),
		Mask:      win.LIF_ITEMINDEX | win.LIF_STATE,
		StateMask: state,
	}

	if win.TRUE != lll.ll.SendMessage(win.LM_GETITEM, 0, uintptr(unsafe.Pointer(&li))) {
		return false, newError("LM_GETITEM")
	}

	return li.State&state == state, nil
}

func (lll *LinkLabelLink) setState(state uint32, set bool) error {
	li := win.LITEM{
		Mask:      win.LIF_STATE,
		StateMask: state,
	}

	if set {
		li.State = state
	}

	li.Mask |= win.LIF_ITEMINDEX
	li.ILink = int32(lll.index)

	if win.TRUE != lll.ll.SendMessage(win.LM_SETITEM, 0, uintptr(unsafe.Pointer(&li))) {
		return newError("LM_SETITEM")
	}

	return nil
}

func (ll *LinkLabel) CreateLayoutItem(ctx *LayoutContext) LayoutItem {
	var s win.SIZE
	ll.SendMessage(win.LM_GETIDEALSIZE, uintptr(ll.IntFrom96DPI(ll.maxSize96dpi.Width)), uintptr(unsafe.Pointer(&s)))

	return &linkLabelLayoutItem{
		idealSize: sizeFromSIZE(s),
	}
}

type linkLabelLayoutItem struct {
	LayoutItemBase
	idealSize Size // in native pixels
}

func (*linkLabelLayoutItem) LayoutFlags() LayoutFlags {
	return 0
}

func (li *linkLabelLayoutItem) IdealSize() Size {
	return li.idealSize
}

func (li *linkLabelLayoutItem) MinSize() Size {
	return li.idealSize
}
