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

import (
	"github.com/lxn/win"
)

type CaseMode uint32

const (
	CaseModeMixed CaseMode = iota
	CaseModeUpper
	CaseModeLower
)

const (
	lineEditMinChars    = 1  // 10 // number of characters needed to make a LineEdit usable
	lineEditGreedyLimit = 29 // 80 // fields with MaxLength larger than this will be greedy (default length is 32767)
)

type LineEdit struct {
	WidgetBase
	editingFinishedPublisher EventPublisher
	readOnlyChangedPublisher EventPublisher
	textChangedPublisher     EventPublisher
	charWidthFont            *Font
	charWidth                int // in native pixels
	textColor                Color
}

func newLineEdit(parent Window) (*LineEdit, error) {
	le := new(LineEdit)

	if err := InitWindow(
		le,
		parent,
		"EDIT",
		win.WS_CHILD|win.WS_TABSTOP|win.WS_VISIBLE|win.ES_AUTOHSCROLL,
		win.WS_EX_CLIENTEDGE); err != nil {
		return nil, err
	}

	le.GraphicsEffects().Add(InteractionEffect)
	le.GraphicsEffects().Add(FocusEffect)

	le.MustRegisterProperty("ReadOnly", NewProperty(
		func() interface{} {
			return le.ReadOnly()
		},
		func(v interface{}) error {
			return le.SetReadOnly(v.(bool))
		},
		le.readOnlyChangedPublisher.Event()))

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

	return le, nil
}

func NewLineEdit(parent Container) (*LineEdit, error) {
	if parent == nil {
		return nil, newError("parent cannot be nil")
	}

	le, err := newLineEdit(parent)
	if err != nil {
		return nil, err
	}

	var succeeded bool
	defer func() {
		if !succeeded {
			le.Dispose()
		}
	}()

	le.parent = parent
	if err = parent.Children().Add(le); err != nil {
		return nil, err
	}

	succeeded = true

	return le, nil
}

func (le *LineEdit) CueBanner() string {
	buf := make([]uint16, 128)
	if win.FALSE == le.SendMessage(win.EM_GETCUEBANNER, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf))) {
		newError("EM_GETCUEBANNER failed")
		return ""
	}

	return syscall.UTF16ToString(buf)
}

func (le *LineEdit) SetCueBanner(value string) error {
	if win.FALSE == le.SendMessage(win.EM_SETCUEBANNER, win.FALSE, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(value)))) {
		return newError("EM_SETCUEBANNER failed")
	}

	return nil
}

func (le *LineEdit) MaxLength() int {
	return int(le.SendMessage(win.EM_GETLIMITTEXT, 0, 0))
}

func (le *LineEdit) SetMaxLength(value int) {
	le.SendMessage(win.EM_LIMITTEXT, uintptr(value), 0)
}

func (le *LineEdit) Text() string {
	return le.text()
}

func (le *LineEdit) SetText(value string) error {
	return le.setText(value)
}

func (le *LineEdit) TextSelection() (start, end int) {
	le.SendMessage(win.EM_GETSEL, uintptr(unsafe.Pointer(&start)), uintptr(unsafe.Pointer(&end)))
	return
}

func (le *LineEdit) SetTextSelection(start, end int) {
	le.SendMessage(win.EM_SETSEL, uintptr(start), uintptr(end))
}

func (le *LineEdit) TextAlignment() Alignment1D {
	switch win.GetWindowLong(le.hWnd, win.GWL_STYLE) & (win.ES_LEFT | win.ES_CENTER | win.ES_RIGHT) {
	case win.ES_CENTER:
		return AlignCenter

	case win.ES_RIGHT:
		return AlignFar
	}

	return AlignNear
}

func (le *LineEdit) SetTextAlignment(alignment Alignment1D) error {
	if alignment == AlignDefault {
		alignment = AlignNear
	}

	var bit uint32

	switch alignment {
	case AlignCenter:
		bit = win.ES_CENTER

	case AlignFar:
		bit = win.ES_RIGHT

	default:
		bit = win.ES_LEFT
	}

	return le.setAndClearStyleBits(bit, win.ES_LEFT|win.ES_CENTER|win.ES_RIGHT)
}

func (le *LineEdit) CaseMode() CaseMode {
	style := uint32(win.GetWindowLong(le.hWnd, win.GWL_STYLE))

	if style&win.ES_UPPERCASE != 0 {
		return CaseModeUpper
	} else if style&win.ES_LOWERCASE != 0 {
		return CaseModeLower
	} else {
		return CaseModeMixed
	}
}

func (le *LineEdit) SetCaseMode(mode CaseMode) error {
	var set, clear uint32

	switch mode {
	case CaseModeMixed:
		clear = win.ES_UPPERCASE | win.ES_LOWERCASE

	case CaseModeUpper:
		set = win.ES_UPPERCASE
		clear = win.ES_LOWERCASE

	case CaseModeLower:
		set = win.ES_LOWERCASE
		clear = win.ES_UPPERCASE

	default:
		panic("invalid CaseMode")
	}

	return le.setAndClearStyleBits(set, clear)
}

func (le *LineEdit) PasswordMode() bool {
	return le.SendMessage(win.EM_GETPASSWORDCHAR, 0, 0) != 0
}

func (le *LineEdit) SetPasswordMode(value bool) {
	var c uintptr
	if value {
		c = uintptr('*')
	}

	le.SendMessage(win.EM_SETPASSWORDCHAR, c, 0)
}

func (le *LineEdit) ReadOnly() bool {
	return le.hasStyleBits(win.ES_READONLY)
}

func (le *LineEdit) SetReadOnly(readOnly bool) error {
	if 0 == le.SendMessage(win.EM_SETREADONLY, uintptr(win.BoolToBOOL(readOnly)), 0) {
		return newError("SendMessage(EM_SETREADONLY)")
	}

	if readOnly != le.ReadOnly() {
		le.invalidateBorderInParent()
	}

	le.readOnlyChangedPublisher.Publish()

	return nil
}

// sizeHintForLimit returns size hint for given limit in native pixels
func (le *LineEdit) sizeHintForLimit(limit int) (size Size) {
	size = le.dialogBaseUnitsToPixels(Size{50, 12})
	le.initCharWidth()
	n := le.MaxLength()
	if n > limit {
		n = limit
	}
	size.Width = le.charWidth * (n + 1)
	return
}

func (le *LineEdit) initCharWidth() {
	font := le.Font()
	if font == le.charWidthFont {
		return
	}
	le.charWidthFont = font
	le.charWidth = 8

	hdc := win.GetDC(le.hWnd)
	if hdc == 0 {
		newError("GetDC failed")
		return
	}
	defer win.ReleaseDC(le.hWnd, hdc)

	defer win.SelectObject(hdc, win.SelectObject(hdc, win.HGDIOBJ(font.handleForDPI(le.DPI()))))

	buf := []uint16{'M'}

	var s win.SIZE
	if !win.GetTextExtentPoint32(hdc, &buf[0], int32(len(buf)), &s) {
		newError("GetTextExtentPoint32 failed")
		return
	}
	le.charWidth = int(s.CX)
}

func (le *LineEdit) EditingFinished() *Event {
	return le.editingFinishedPublisher.Event()
}

func (le *LineEdit) TextChanged() *Event {
	return le.textChangedPublisher.Event()
}

func (le *LineEdit) TextColor() Color {
	return le.textColor
}

func (le *LineEdit) SetTextColor(c Color) {
	le.textColor = c

	le.Invalidate()
}

func (*LineEdit) NeedsWmSize() bool {
	return true
}

func (le *LineEdit) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
	switch msg {
	case win.WM_COMMAND:
		switch win.HIWORD(uint32(wParam)) {
		case win.EN_CHANGE:
			le.textChangedPublisher.Publish()
		}

	case win.WM_GETDLGCODE:
		if form := ancestor(le); form != nil {
			if dlg, ok := form.(dialogish); ok {
				if dlg.DefaultButton() != nil {
					// If the LineEdit lives in a Dialog that has a DefaultButton,
					// we won't swallow the return key.
					break
				}
			}
		}

		if wParam == win.VK_RETURN {
			return win.DLGC_WANTALLKEYS
		}

	case win.WM_KEYDOWN:
		switch Key(wParam) {
		case KeyA:
			if ControlDown() {
				le.SetTextSelection(0, -1)
			}

		case KeyReturn:
			le.editingFinishedPublisher.Publish()
		}

	case win.WM_KILLFOCUS:
		// FIXME: This may be dangerous, see remarks section:
		// http://msdn.microsoft.com/en-us/library/ms646282(v=vs.85).aspx
		le.editingFinishedPublisher.Publish()
	}

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

func (le *LineEdit) CreateLayoutItem(ctx *LayoutContext) LayoutItem {
	lf := ShrinkableHorz | GrowableHorz
	if le.MaxLength() > lineEditGreedyLimit {
		lf |= GreedyHorz
	}

	return &lineEditLayoutItem{
		layoutFlags: lf,
		idealSize:   le.sizeHintForLimit(lineEditGreedyLimit),
		minSize:     le.sizeHintForLimit(lineEditMinChars),
	}
}

type lineEditLayoutItem struct {
	LayoutItemBase
	layoutFlags LayoutFlags
	idealSize   Size // in native pixels
	minSize     Size // in native pixels
}

func (li *lineEditLayoutItem) LayoutFlags() LayoutFlags {
	return li.layoutFlags
}

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

func (li *lineEditLayoutItem) MinSize() Size {
	return li.minSize
}
