//go:build windows

/*
 * Copyright (C) 2019 The Winc Authors. All Rights Reserved.
 */

package winc

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"sort"
	"unsafe"

	"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
)

// Dockable component must satisfy interface to be docked.
type Dockable interface {
	Handle() w32.HWND

	Pos() (x, y int)
	Width() int
	Height() int
	Visible() bool

	SetPos(x, y int)
	SetSize(width, height int)

	OnMouseMove() *EventManager
	OnLBUp() *EventManager
}

// DockAllow is window, panel or other component that satisfies interface.
type DockAllow interface {
	Handle() w32.HWND
	ClientWidth() int
	ClientHeight() int
	SetLayout(mng LayoutManager)
}

// Various layout managers
type Direction int

const (
	Top Direction = iota
	Bottom
	Left
	Right
	Fill
)

type LayoutControl struct {
	child Dockable
	dir   Direction
}

type LayoutControls []*LayoutControl

type SimpleDock struct {
	parent      DockAllow
	layoutCtl   LayoutControls
	loadedState bool
}

// CtlState gets saved and loaded from json
type CtlState struct {
	X, Y, Width, Height int
}

type LayoutState struct {
	WindowState string
	Controls    []*CtlState
}

func (lc LayoutControls) Len() int           { return len(lc) }
func (lc LayoutControls) Swap(i, j int)      { lc[i], lc[j] = lc[j], lc[i] }
func (lc LayoutControls) Less(i, j int) bool { return lc[i].dir < lc[j].dir }

func NewSimpleDock(parent DockAllow) *SimpleDock {
	d := &SimpleDock{parent: parent}
	parent.SetLayout(d)
	return d
}

// Layout management for the child controls.
func (sd *SimpleDock) Dock(child Dockable, dir Direction) {
	sd.layoutCtl = append(sd.layoutCtl, &LayoutControl{child, dir})
}

// SaveState of the layout. Only works for Docks with parent set to main form.
func (sd *SimpleDock) SaveState(w io.Writer) error {
	var ls LayoutState

	var wp w32.WINDOWPLACEMENT
	wp.Length = uint32(unsafe.Sizeof(wp))
	if !w32.GetWindowPlacement(sd.parent.Handle(), &wp) {
		return fmt.Errorf("GetWindowPlacement failed")
	}

	ls.WindowState = fmt.Sprint(
		wp.Flags, wp.ShowCmd,
		wp.PtMinPosition.X, wp.PtMinPosition.Y,
		wp.PtMaxPosition.X, wp.PtMaxPosition.Y,
		wp.RcNormalPosition.Left, wp.RcNormalPosition.Top,
		wp.RcNormalPosition.Right, wp.RcNormalPosition.Bottom)

	for _, c := range sd.layoutCtl {
		x, y := c.child.Pos()
		w, h := c.child.Width(), c.child.Height()

		ctl := &CtlState{X: x, Y: y, Width: w, Height: h}
		ls.Controls = append(ls.Controls, ctl)
	}

	if err := json.NewEncoder(w).Encode(ls); err != nil {
		return err
	}

	return nil
}

// LoadState of the layout. Only works for Docks with parent set to main form.
func (sd *SimpleDock) LoadState(r io.Reader) error {
	var ls LayoutState

	if err := json.NewDecoder(r).Decode(&ls); err != nil {
		return err
	}

	var wp w32.WINDOWPLACEMENT
	if _, err := fmt.Sscan(ls.WindowState,
		&wp.Flags, &wp.ShowCmd,
		&wp.PtMinPosition.X, &wp.PtMinPosition.Y,
		&wp.PtMaxPosition.X, &wp.PtMaxPosition.Y,
		&wp.RcNormalPosition.Left, &wp.RcNormalPosition.Top,
		&wp.RcNormalPosition.Right, &wp.RcNormalPosition.Bottom); err != nil {
		return err
	}
	wp.Length = uint32(unsafe.Sizeof(wp))

	if !w32.SetWindowPlacement(sd.parent.Handle(), &wp) {
		return fmt.Errorf("SetWindowPlacement failed")
	}

	// if number of controls in the saved layout does not match
	// current number on screen - something changed and we do not reload
	// rest of control sizes from json
	if len(sd.layoutCtl) != len(ls.Controls) {
		return nil
	}

	for i, c := range sd.layoutCtl {
		c.child.SetPos(ls.Controls[i].X, ls.Controls[i].Y)
		c.child.SetSize(ls.Controls[i].Width, ls.Controls[i].Height)
	}
	return nil
}

// SaveStateFile convenience function.
func (sd *SimpleDock) SaveStateFile(file string) error {
	f, err := os.Create(file)
	if err != nil {
		return err
	}
	return sd.SaveState(f)
}

// LoadStateFile loads state ignores error if file is not found.
func (sd *SimpleDock) LoadStateFile(file string) error {
	f, err := os.Open(file)
	if err != nil {
		return nil // if file is not found or not accessible ignore it
	}
	return sd.LoadState(f)
}

// Update is called to resize child items based on layout directions.
func (sd *SimpleDock) Update() {
	sort.Stable(sd.layoutCtl)

	x, y := 0, 0
	w, h := sd.parent.ClientWidth(), sd.parent.ClientHeight()
	winw, winh := w, h

	for _, c := range sd.layoutCtl {
		// Non visible controls do not preserve space.
		if !c.child.Visible() {
			continue
		}

		switch c.dir {
		case Top:
			c.child.SetPos(x, y)
			c.child.SetSize(w, c.child.Height())
			h -= c.child.Height()
			y += c.child.Height()
		case Bottom:
			c.child.SetPos(x, winh-c.child.Height())
			c.child.SetSize(w, c.child.Height())
			h -= c.child.Height()
			winh -= c.child.Height()
		case Left:
			c.child.SetPos(x, y)
			c.child.SetSize(c.child.Width(), h)
			w -= c.child.Width()
			x += c.child.Width()
		case Right:
			c.child.SetPos(winw-c.child.Width(), y)
			c.child.SetSize(c.child.Width(), h)
			w -= c.child.Width()
			winw -= c.child.Width()
		case Fill:
			// fill available space
			c.child.SetPos(x, y)
			c.child.SetSize(w, h)
		}
		//c.child.Invalidate(true)
	}
}
