// 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 (
	"bytes"
	"log"
	"strconv"
	"strings"
	"unsafe"

	"github.com/lxn/win"
)

const splitterWindowClass = `\o/ Walk_Splitter_Class \o/`

var splitterHandleDraggingBrush *SolidColorBrush

func init() {
	AppendToWalkInit(func() {
		MustRegisterWindowClass(splitterWindowClass)

		splitterHandleDraggingBrush, _ = NewSolidColorBrush(Color(win.GetSysColor(win.COLOR_BTNSHADOW)))
		splitterHandleDraggingBrush.wb2info = map[*WindowBase]*windowBrushInfo{nil: nil}
	})
}

type Splitter struct {
	ContainerBase
	handleWidth   int
	mouseDownPos  Point // in native pixels
	draggedHandle *splitterHandle
	persistent    bool
	removing      bool
}

func newSplitter(parent Container, orientation Orientation) (*Splitter, error) {
	layout := newSplitterLayout(Horizontal)
	s := &Splitter{
		ContainerBase: ContainerBase{
			layout: layout,
		},
		handleWidth: 5,
	}
	s.children = newWidgetList(s)
	layout.container = s

	if err := InitWidget(
		s,
		parent,
		splitterWindowClass,
		win.WS_VISIBLE,
		win.WS_EX_CONTROLPARENT); err != nil {
		return nil, err
	}

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

	s.SetBackground(NullBrush())

	if err := s.setOrientation(orientation); err != nil {
		return nil, err
	}

	s.SetPersistent(true)

	succeeded = true

	return s, nil
}

func NewHSplitter(parent Container) (*Splitter, error) {
	return newSplitter(parent, Horizontal)
}

func NewVSplitter(parent Container) (*Splitter, error) {
	return newSplitter(parent, Vertical)
}

func (s *Splitter) SetLayout(value Layout) error {
	return newError("not supported")
}

func (s *Splitter) HandleWidth() int {
	return s.handleWidth
}

func (s *Splitter) SetHandleWidth(value int) error {
	if value == s.handleWidth {
		return nil
	}

	if value < 1 {
		return newError("invalid handle width")
	}

	s.handleWidth = value

	s.RequestLayout()

	return nil
}

func (s *Splitter) Orientation() Orientation {
	layout := s.layout.(*splitterLayout)
	return layout.Orientation()
}

func (s *Splitter) setOrientation(value Orientation) error {
	var cursor Cursor
	if value == Horizontal {
		cursor = CursorSizeWE()
	} else {
		cursor = CursorSizeNS()
	}

	for i, wb := range s.Children().items {
		if i%2 == 1 {
			wb.window.SetCursor(cursor)
		}
	}

	layout := s.layout.(*splitterLayout)
	return layout.SetOrientation(value)
}

func (s *Splitter) updateMarginsForFocusEffect() {
	var margins Margins
	var parentLayout Layout

	if s.parent != nil {
		if parentLayout = s.parent.Layout(); parentLayout != nil {
			if m := parentLayout.Margins(); m.HNear < 9 || m.HFar < 9 || m.VNear < 9 || m.VFar < 9 {
				parentLayout = nil
			}
		}
	}

	var affected bool
	if FocusEffect != nil {
		for _, wb := range s.children.items {
			if wb.window.(Widget).GraphicsEffects().Contains(FocusEffect) {
				affected = true
				break
			}
		}
	}

	if affected {
		var marginsNeeded bool
		for _, wb := range s.children.items {
			switch wb.window.(type) {
			case *splitterHandle, *TabWidget, Container:

			default:
				marginsNeeded = true
				break
			}
		}

		if marginsNeeded {
			margins = Margins{5, 5, 5, 5}
		}
	}

	if parentLayout != nil {
		parentLayout.SetMargins(Margins{9 - margins.HNear, 9 - margins.VNear, 9 - margins.HFar, 9 - margins.VFar})
	}

	s.layout.SetMargins(margins)
}

func (s *Splitter) Persistent() bool {
	return s.persistent
}

func (s *Splitter) SetPersistent(value bool) {
	s.persistent = value
}

func (s *Splitter) SaveState() error {
	buf := bytes.NewBuffer(nil)

	count := s.children.Len()
	layout := s.Layout().(*splitterLayout)

	for i := 0; i < count; i += 2 {
		if i > 0 {
			buf.WriteString(" ")
		}

		item := layout.hwnd2Item[s.children.At(i).Handle()]
		size := item.oldExplicitSize
		if size == 0 {
			size = item.size
		}
		buf.WriteString(strconv.FormatInt(int64(size), 10))
	}

	s.WriteState(buf.String())

	for _, wb := range s.children.items {
		if persistable, ok := wb.window.(Persistable); ok {
			if err := persistable.SaveState(); err != nil {
				return err
			}
		}
	}

	return nil
}

func (s *Splitter) RestoreState() error {
	childCount := s.children.Len()/2 + 1
	if childCount == 0 {
		return nil
	}

	state, err := s.ReadState()
	if err != nil {
		return err
	}
	if state == "" {
		return nil
	}

	sizeStrs := strings.Split(state, " ")

	// FIXME: Solve this in a better way.
	if len(sizeStrs) != childCount {
		log.Print("*Splitter.RestoreState: failed due to unexpected child count (FIXME!)")
		return nil
	}

	layout := s.layout.(*splitterLayout)

	s.SetSuspended(true)
	layout.suspended = true
	defer func() {
		layout.suspended = false
		s.SetSuspended(false)
	}()

	var space int
	size := s.ClientBoundsPixels().Size()
	if s.Orientation() == Horizontal {
		space = size.Width
	} else {
		space = size.Height
	}
	regularSpace := space - layout.spaceUnavailableToRegularWidgets()

	for i, wb := range s.children.items {
		widget := wb.window.(Widget)

		if i%2 == 0 {
			j := i/2 + i%2
			s := sizeStrs[j]

			size, err := strconv.Atoi(s)
			if err != nil {
				// OK, we probably got old style settings which were stored as fractions.
				fraction, err := strconv.ParseFloat(s, 64)
				if err != nil {
					return err
				}

				size = int(float64(regularSpace) * fraction)
			}

			item := layout.hwnd2Item[widget.Handle()]
			item.size = size
			item.oldExplicitSize = size
		}
	}

	for _, wb := range s.children.items {
		if persistable, ok := wb.window.(Persistable); ok {
			if err := persistable.RestoreState(); err != nil {
				return err
			}
		}
	}

	return nil
}

func (s *Splitter) Fixed(widget Widget) bool {
	return s.layout.(*splitterLayout).Fixed(widget)
}

func (s *Splitter) SetFixed(widget Widget, fixed bool) error {
	item := s.layout.(*splitterLayout).hwnd2Item[widget.Handle()]
	if item == nil {
		return newError("unknown widget")
	}

	item.fixed = fixed

	if b := widget.BoundsPixels(); fixed && item.size == 0 && (b.Width == 0 || b.Height == 0) {
		b.Width, b.Height = 100, 100
		widget.SetBoundsPixels(b)
		item.size = 100
	}

	return nil
}

func (s *Splitter) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
	switch msg {
	case win.WM_WINDOWPOSCHANGED:
		wp := (*win.WINDOWPOS)(unsafe.Pointer(lParam))

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

		layout := s.layout.(*splitterLayout)
		layout.resetNeeded = false
		for _, item := range layout.hwnd2Item {
			item.oldExplicitSize = 0
		}
	}

	return s.ContainerBase.WndProc(hwnd, msg, wParam, lParam)
}

func (s *Splitter) onInsertingWidget(index int, widget Widget) (err error) {
	return s.ContainerBase.onInsertingWidget(index, widget)
}

func (s *Splitter) onInsertedWidget(index int, widget Widget) (err error) {
	defer func() {
		if err != nil {
			return
		}

		s.updateMarginsForFocusEffect()
	}()

	_, isHandle := widget.(*splitterHandle)
	if isHandle {
		if s.Orientation() == Horizontal {
			widget.SetCursor(CursorSizeWE())
		} else {
			widget.SetCursor(CursorSizeNS())
		}
	} else {
		layout := s.Layout().(*splitterLayout)
		item := &splitterLayoutItem{stretchFactor: 1, wasVisible: true}
		layout.hwnd2Item[widget.Handle()] = item

		layout.resetNeeded = true
		if !layout.suspended && widget.AsWidgetBase().visible {
			s.RequestLayout()
		}

		item.visibleChangedHandle = widget.VisibleChanged().Attach(func() {
			if !layout.suspended && widget.AsWidgetBase().visible != item.wasVisible {
				layout.resetNeeded = true
				s.RequestLayout()
			}
		})

		if s.children.Len()%2 == 0 {
			defer func() {
				if err != nil {
					return
				}

				var handle *splitterHandle
				handle, err = newSplitterHandle(s)
				if err != nil {
					return
				}

				closestVisibleWidget := func(offset, direction int) Widget {
					index := offset + direction

					for index >= 0 && index < len(s.children.items) {
						if wb := s.children.items[index]; wb.visible {
							return wb.window.(Widget)
						}

						index += direction
					}

					return nil
				}

				handleIndex := index + 1 - index%2
				err = s.children.Insert(handleIndex, handle)
				if err == nil {
					// FIXME: These handlers will be leaked, if widgets get removed.
					handle.MouseDown().Attach(func(x, y int, button MouseButton) {
						if button != LeftButton {
							return
						}

						s.draggedHandle = handle
						s.mouseDownPos = Point{x, y}
						handle.SetBackground(splitterHandleDraggingBrush)
					})

					handle.MouseMove().Attach(func(x, y int, button MouseButton) {
						if s.draggedHandle == nil {
							return
						}

						handleIndex := s.children.Index(s.draggedHandle)
						bh := s.draggedHandle.BoundsPixels()

						prev := closestVisibleWidget(handleIndex, -1)
						bp := prev.BoundsPixels()
						msep := minSizeEffective(createLayoutItemForWidget(prev))

						next := closestVisibleWidget(handleIndex, 1)
						bn := next.BoundsPixels()
						msen := minSizeEffective(createLayoutItemForWidget(next))

						dpi := s.draggedHandle.DPI()
						handleWidth := IntFrom96DPI(s.handleWidth, dpi)

						if s.Orientation() == Horizontal {
							xh := s.draggedHandle.XPixels()

							xnew := xh + x - s.mouseDownPos.X
							if xnew < bp.X+msep.Width {
								xnew = bp.X + msep.Width
							} else if xnew >= bn.X+bn.Width-msen.Width-handleWidth {
								xnew = bn.X + bn.Width - msen.Width - handleWidth
							}

							if e := s.draggedHandle.SetXPixels(xnew); e != nil {
								return
							}
						} else {
							yh := s.draggedHandle.YPixels()

							ynew := yh + y - s.mouseDownPos.Y
							if ynew < bp.Y+msep.Height {
								ynew = bp.Y + msep.Height
							} else if ynew >= bn.Y+bn.Height-msen.Height-handleWidth {
								ynew = bn.Y + bn.Height - msen.Height - handleWidth
							}

							if e := s.draggedHandle.SetYPixels(ynew); e != nil {
								return
							}
						}

						rc := bh.toRECT()
						if s.Orientation() == Horizontal {
							rc.Left -= int32(bp.X)
							rc.Right -= int32(bp.X)
						} else {
							rc.Top -= int32(bp.Y)
							rc.Bottom -= int32(bp.Y)
						}
						win.InvalidateRect(prev.Handle(), &rc, true)

						rc = bh.toRECT()
						if s.Orientation() == Horizontal {
							rc.Left -= int32(bn.X)
							rc.Right -= int32(bn.X)
						} else {
							rc.Top -= int32(bn.Y)
							rc.Bottom -= int32(bn.Y)
						}
						win.InvalidateRect(next.Handle(), &rc, true)

						s.draggedHandle.Invalidate()
					})

					handle.MouseUp().Attach(func(x, y int, button MouseButton) {
						if s.draggedHandle == nil {
							return
						}

						defer s.RequestLayout()

						dragHandle := s.draggedHandle

						handleIndex := s.children.Index(dragHandle)
						prev := closestVisibleWidget(handleIndex, -1)
						next := closestVisibleWidget(handleIndex, 1)

						s.draggedHandle = nil
						dragHandle.SetBackground(NullBrush())
						prev.AsWidgetBase().invalidateBorderInParent()
						next.AsWidgetBase().invalidateBorderInParent()

						prev.SetSuspended(true)
						defer prev.Invalidate()
						defer prev.SetSuspended(false)
						next.SetSuspended(true)
						defer next.Invalidate()
						defer next.SetSuspended(false)

						bh := dragHandle.BoundsPixels()
						bp := prev.BoundsPixels()
						bn := next.BoundsPixels()

						var sizePrev int
						var sizeNext int

						if s.Orientation() == Horizontal {
							bp.Width = bh.X - bp.X
							bn.Width -= (bh.X + bh.Width) - bn.X
							bn.X = bh.X + bh.Width
							sizePrev = bp.Width
							sizeNext = bn.Width
						} else {
							bp.Height = bh.Y - bp.Y
							bn.Height -= (bh.Y + bh.Height) - bn.Y
							bn.Y = bh.Y + bh.Height
							sizePrev = bp.Height
							sizeNext = bn.Height
						}

						layout := s.Layout().(*splitterLayout)

						prevItem := layout.hwnd2Item[prev.Handle()]
						prevItem.size = sizePrev
						prevItem.oldExplicitSize = sizePrev

						nextItem := layout.hwnd2Item[next.Handle()]
						nextItem.size = sizeNext
						nextItem.oldExplicitSize = sizeNext
					})
				}
			}()
		}
	}

	return s.ContainerBase.onInsertedWidget(index, widget)
}

func (s *Splitter) onRemovingWidget(index int, widget Widget) (err error) {
	return s.ContainerBase.onRemovingWidget(index, widget)
}

func (s *Splitter) onRemovedWidget(index int, widget Widget) (err error) {
	defer func() {
		if err != nil {
			return
		}

		s.updateMarginsForFocusEffect()
	}()

	_, isHandle := widget.(*splitterHandle)
	if !s.removing && isHandle && s.children.Len()%2 == 1 {
		return newError("cannot remove splitter handle")
	}

	if !isHandle {
		sl := s.layout.(*splitterLayout)
		widget.AsWidgetBase().Property("Visible").Changed().Detach(sl.hwnd2Item[widget.Handle()].visibleChangedHandle)
	}

	if !isHandle && s.children.Len() > 1 {
		defer func() {
			if err != nil {
				return
			}

			var handleIndex int
			if index == 0 {
				handleIndex = 0
			} else {
				handleIndex = index - 1
			}

			s.removing = true
			handle := s.children.items[handleIndex].window.(*splitterHandle)

			if err = handle.SetParent(nil); err == nil {
				sl := s.layout.(*splitterLayout)

				for _, item := range sl.hwnd2Item {
					item.oldExplicitSize = 0
					item.keepSize = false
				}

				sl.resetNeeded = true
				s.RequestLayout()

				handle.Dispose()
			}

			s.removing = false
		}()
	}

	err = s.ContainerBase.onRemovedWidget(index, widget)

	return
}

func (s *Splitter) onClearingWidgets() (err error) {
	panic("not implemented")
}

func (s *Splitter) onClearedWidgets() (err error) {
	panic("not implemented")
}

func (s *Splitter) CreateLayoutItem(ctx *LayoutContext) LayoutItem {
	return s.layout.CreateLayoutItem(ctx)
}
