// 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 (
	"unsafe"

	"github.com/lxn/win"
)

type HatchStyle int

const (
	HatchHorizontal       HatchStyle = win.HS_HORIZONTAL
	HatchVertical         HatchStyle = win.HS_VERTICAL
	HatchForwardDiagonal  HatchStyle = win.HS_FDIAGONAL
	HatchBackwardDiagonal HatchStyle = win.HS_BDIAGONAL
	HatchCross            HatchStyle = win.HS_CROSS
	HatchDiagonalCross    HatchStyle = win.HS_DIAGCROSS
)

type SystemColor int

const (
	SysColor3DDkShadow              SystemColor = win.COLOR_3DDKSHADOW
	SysColor3DFace                  SystemColor = win.COLOR_3DFACE
	SysColor3DHighlight             SystemColor = win.COLOR_3DHIGHLIGHT
	SysColor3DLight                 SystemColor = win.COLOR_3DLIGHT
	SysColor3DShadow                SystemColor = win.COLOR_3DSHADOW
	SysColorActiveBorder            SystemColor = win.COLOR_ACTIVEBORDER
	SysColorActiveCaption           SystemColor = win.COLOR_ACTIVECAPTION
	SysColorAppWorkspace            SystemColor = win.COLOR_APPWORKSPACE
	SysColorBackground              SystemColor = win.COLOR_BACKGROUND
	SysColorDesktop                 SystemColor = win.COLOR_DESKTOP
	SysColorBtnFace                 SystemColor = win.COLOR_BTNFACE
	SysColorBtnHighlight            SystemColor = win.COLOR_BTNHIGHLIGHT
	SysColorBtnShadow               SystemColor = win.COLOR_BTNSHADOW
	SysColorBtnText                 SystemColor = win.COLOR_BTNTEXT
	SysColorCaptionText             SystemColor = win.COLOR_CAPTIONTEXT
	SysColorGrayText                SystemColor = win.COLOR_GRAYTEXT
	SysColorHighlight               SystemColor = win.COLOR_HIGHLIGHT
	SysColorHighlightText           SystemColor = win.COLOR_HIGHLIGHTTEXT
	SysColorInactiveBorder          SystemColor = win.COLOR_INACTIVEBORDER
	SysColorInactiveCaption         SystemColor = win.COLOR_INACTIVECAPTION
	SysColorInactiveCaptionText     SystemColor = win.COLOR_INACTIVECAPTIONTEXT
	SysColorInfoBk                  SystemColor = win.COLOR_INFOBK
	SysColorInfoText                SystemColor = win.COLOR_INFOTEXT
	SysColorMenu                    SystemColor = win.COLOR_MENU
	SysColorMenuText                SystemColor = win.COLOR_MENUTEXT
	SysColorScrollBar               SystemColor = win.COLOR_SCROLLBAR
	SysColorWindow                  SystemColor = win.COLOR_WINDOW
	SysColorWindowFrame             SystemColor = win.COLOR_WINDOWFRAME
	SysColorWindowText              SystemColor = win.COLOR_WINDOWTEXT
	SysColorHotLight                SystemColor = win.COLOR_HOTLIGHT
	SysColorGradientActiveCaption   SystemColor = win.COLOR_GRADIENTACTIVECAPTION
	SysColorGradientInactiveCaption SystemColor = win.COLOR_GRADIENTINACTIVECAPTION
)

type Brush interface {
	Dispose()
	handle() win.HBRUSH
	logbrush() *win.LOGBRUSH
	attachWindow(wb *WindowBase)
	detachWindow(wb *WindowBase)
	simple() bool
}

type perWindowBrush interface {
	Brush
	delegateForWindow(wb *WindowBase) Brush
}

type windowBrushInfo struct {
	SizeChangedHandle int
	Delegate          *BitmapBrush
}

type brushBase struct {
	hBrush  win.HBRUSH
	wb2info map[*WindowBase]*windowBrushInfo
}

func (bb *brushBase) Dispose() {
	if bb.hBrush != 0 {
		win.DeleteObject(win.HGDIOBJ(bb.hBrush))

		bb.hBrush = 0
	}
}

func (bb *brushBase) handle() win.HBRUSH {
	return bb.hBrush
}

func (bb *brushBase) attachWindow(wb *WindowBase) {
	if wb == nil {
		return
	}

	if bb.wb2info == nil {
		bb.wb2info = make(map[*WindowBase]*windowBrushInfo)
	}

	bb.wb2info[wb] = nil
}

func (bb *brushBase) detachWindow(wb *WindowBase) {
	if bb.wb2info == nil || wb == nil {
		return
	}

	delete(bb.wb2info, wb)

	if len(bb.wb2info) == 0 {
		bb.Dispose()
	}
}

type nullBrush struct {
	brushBase
}

func newNullBrush() *nullBrush {
	lb := &win.LOGBRUSH{LbStyle: win.BS_NULL}

	hBrush := win.CreateBrushIndirect(lb)
	if hBrush == 0 {
		panic("failed to create null brush")
	}

	return &nullBrush{brushBase: brushBase{hBrush: hBrush}}
}

func (b *nullBrush) Dispose() {
	if b == nullBrushSingleton {
		return
	}

	b.brushBase.Dispose()
}

func (*nullBrush) logbrush() *win.LOGBRUSH {
	return &win.LOGBRUSH{LbStyle: win.BS_NULL}
}

func (*nullBrush) simple() bool {
	return true
}

var (
	nullBrushSingleton   Brush
	sysColorBtnFaceBrush *SystemColorBrush
)

func NullBrush() Brush {
	return nullBrushSingleton
}

type SystemColorBrush struct {
	brushBase
	sysColor SystemColor
}

func init() {
	AppendToWalkInit(func() {
		nullBrushSingleton = newNullBrush()
		sysColorBtnFaceBrush, _ = NewSystemColorBrush(SysColorBtnFace)
	})
}

func NewSystemColorBrush(sysColor SystemColor) (*SystemColorBrush, error) {
	hBrush := win.GetSysColorBrush(int(sysColor))
	if hBrush == 0 {
		return nil, newError("GetSysColorBrush failed")
	}

	return &SystemColorBrush{brushBase: brushBase{hBrush: hBrush}, sysColor: sysColor}, nil
}

func (b *SystemColorBrush) Color() Color {
	return Color(win.GetSysColor(int(b.sysColor)))
}

func (b *SystemColorBrush) SystemColor() SystemColor {
	return b.sysColor
}

func (*SystemColorBrush) Dispose() {
	// nop
}

func (b *SystemColorBrush) logbrush() *win.LOGBRUSH {
	return &win.LOGBRUSH{
		LbStyle: win.BS_SOLID,
		LbColor: win.COLORREF(win.GetSysColor(int(b.sysColor))),
	}
}

func (*SystemColorBrush) simple() bool {
	return true
}

type SolidColorBrush struct {
	brushBase
	color Color
}

func NewSolidColorBrush(color Color) (*SolidColorBrush, error) {
	lb := &win.LOGBRUSH{LbStyle: win.BS_SOLID, LbColor: win.COLORREF(color)}

	hBrush := win.CreateBrushIndirect(lb)
	if hBrush == 0 {
		return nil, newError("CreateBrushIndirect failed")
	}

	return &SolidColorBrush{brushBase: brushBase{hBrush: hBrush}, color: color}, nil
}

func (b *SolidColorBrush) Color() Color {
	return b.color
}

func (b *SolidColorBrush) logbrush() *win.LOGBRUSH {
	return &win.LOGBRUSH{LbStyle: win.BS_SOLID, LbColor: win.COLORREF(b.color)}
}

func (*SolidColorBrush) simple() bool {
	return true
}

type HatchBrush struct {
	brushBase
	color Color
	style HatchStyle
}

func NewHatchBrush(color Color, style HatchStyle) (*HatchBrush, error) {
	lb := &win.LOGBRUSH{LbStyle: win.BS_HATCHED, LbColor: win.COLORREF(color), LbHatch: uintptr(style)}

	hBrush := win.CreateBrushIndirect(lb)
	if hBrush == 0 {
		return nil, newError("CreateBrushIndirect failed")
	}

	return &HatchBrush{brushBase: brushBase{hBrush: hBrush}, color: color, style: style}, nil
}

func (b *HatchBrush) Color() Color {
	return b.color
}

func (b *HatchBrush) logbrush() *win.LOGBRUSH {
	return &win.LOGBRUSH{LbStyle: win.BS_HATCHED, LbColor: win.COLORREF(b.color), LbHatch: uintptr(b.style)}
}

func (b *HatchBrush) Style() HatchStyle {
	return b.style
}

func (b *HatchBrush) simple() bool {
	return false
}

type BitmapBrush struct {
	brushBase
	bitmap *Bitmap
}

func NewBitmapBrush(bitmap *Bitmap) (*BitmapBrush, error) {
	if bitmap == nil {
		return nil, newError("bitmap cannot be nil")
	}

	hBrush := win.CreatePatternBrush(bitmap.hBmp)
	if hBrush == 0 {
		return nil, newError("CreatePatternBrush failed")
	}

	return &BitmapBrush{brushBase: brushBase{hBrush: hBrush}, bitmap: bitmap}, nil
}

func (b *BitmapBrush) logbrush() *win.LOGBRUSH {
	return &win.LOGBRUSH{LbStyle: win.BS_DIBPATTERN, LbColor: win.DIB_RGB_COLORS, LbHatch: uintptr(b.bitmap.hPackedDIB)}
}

func (b *BitmapBrush) Bitmap() *Bitmap {
	return b.bitmap
}

func (b *BitmapBrush) simple() bool {
	return false
}

type GradientStop struct {
	Offset float64
	Color  Color
}

type GradientVertex struct {
	X     float64
	Y     float64
	Color Color
}

type GradientTriangle struct {
	Vertex1 int
	Vertex2 int
	Vertex3 int
}

type GradientBrush struct {
	brushBase
	mainDelegate *BitmapBrush
	vertexes     []GradientVertex
	triangles    []GradientTriangle
	orientation  gradientOrientation
	absolute     bool
}

type gradientOrientation int

const (
	gradientOrientationNone gradientOrientation = iota
	gradientOrientationHorizontal
	gradientOrientationVertical
)

func NewHorizontalGradientBrush(stops []GradientStop) (*GradientBrush, error) {
	return newGradientBrushWithOrientation(stops, gradientOrientationHorizontal)
}

func NewVerticalGradientBrush(stops []GradientStop) (*GradientBrush, error) {
	return newGradientBrushWithOrientation(stops, gradientOrientationVertical)
}

func newGradientBrushWithOrientation(stops []GradientStop, orientation gradientOrientation) (*GradientBrush, error) {
	if len(stops) < 2 {
		return nil, newError("at least 2 stops are required")
	}

	var vertexes []GradientVertex
	var triangles []GradientTriangle

	for i, stop := range stops {
		var x0, y0, x1, y1 float64
		if orientation == gradientOrientationHorizontal {
			x0 = stop.Offset
			x1 = stop.Offset
			y1 = 1.0
		} else {
			y0 = stop.Offset
			x1 = 1.0
			y1 = stop.Offset
		}

		vertexes = append(vertexes, GradientVertex{X: x0, Y: y0, Color: stop.Color})
		vertexes = append(vertexes, GradientVertex{X: x1, Y: y1, Color: stop.Color})

		if i > 0 {
			triangles = append(triangles, GradientTriangle{Vertex1: i*2 - 2, Vertex2: i*2 + 1, Vertex3: i*2 - 1})
			triangles = append(triangles, GradientTriangle{Vertex1: i*2 - 2, Vertex2: i * 2, Vertex3: i*2 + 1})
		}
	}

	return newGradientBrush(vertexes, triangles, orientation)
}

func NewGradientBrush(vertexes []GradientVertex, triangles []GradientTriangle) (*GradientBrush, error) {
	if len(vertexes) < 3 {
		return nil, newError("at least 3 vertexes are required")
	}

	if len(triangles) < 1 {
		return nil, newError("at least 1 triangle is required")
	}

	return newGradientBrush(vertexes, triangles, gradientOrientationNone)
}

func newGradientBrush(vertexes []GradientVertex, triangles []GradientTriangle, orientation gradientOrientation) (*GradientBrush, error) {
	var size Size
	for _, v := range vertexes {
		size = maxSize(size, Size{int(v.X), int(v.Y)})
	}

	gb := &GradientBrush{vertexes: vertexes, triangles: triangles, orientation: orientation, absolute: size.Width > 1 || size.Height > 1}

	if gb.absolute {
		bb, err := gb.create(size)
		if err != nil {
			return nil, err
		}

		gb.mainDelegate = bb
		gb.hBrush = bb.hBrush
	}

	return gb, nil
}

func (b *GradientBrush) logbrush() *win.LOGBRUSH {
	if b.mainDelegate == nil {
		return nil
	}

	return b.mainDelegate.logbrush()
}

func (*GradientBrush) simple() bool {
	return false
}

// create creates a gradient brush at given size in native pixels.
func (b *GradientBrush) create(size Size) (*BitmapBrush, error) {
	var disposables Disposables
	defer disposables.Treat()

	switch b.orientation {
	case gradientOrientationHorizontal:
		size.Height = 1

	case gradientOrientationVertical:
		size.Width = 1
	}

	bitmap, err := NewBitmapForDPI(size, 96) // Size is in native pixels and bitmap is used for brush only => DPI is not used anywhere.
	if err != nil {
		return nil, err
	}
	disposables.Add(bitmap)

	canvas, err := NewCanvasFromImage(bitmap)
	if err != nil {
		return nil, err
	}
	defer canvas.Dispose()

	var scaleX, scaleY float64
	if b.absolute {
		scaleX, scaleY = 1, 1
	} else {
		scaleX, scaleY = float64(size.Width), float64(size.Height)
	}

	vertexes := make([]win.TRIVERTEX, len(b.vertexes))
	for i, src := range b.vertexes {
		dst := &vertexes[i]

		dst.X = int32(src.X * scaleX)
		dst.Y = int32(src.Y * scaleY)
		dst.Red = uint16(src.Color.R()) * 256
		dst.Green = uint16(src.Color.G()) * 256
		dst.Blue = uint16(src.Color.B()) * 256
	}

	triangles := make([]win.GRADIENT_TRIANGLE, len(b.triangles))
	for i, src := range b.triangles {
		dst := &triangles[i]

		dst.Vertex1 = uint32(src.Vertex1)
		dst.Vertex2 = uint32(src.Vertex2)
		dst.Vertex3 = uint32(src.Vertex3)
	}

	if !win.GradientFill(canvas.hdc, &vertexes[0], uint32(len(vertexes)), unsafe.Pointer(&triangles[0]), uint32(len(triangles)), win.GRADIENT_FILL_TRIANGLE) {
		return nil, newError("GradientFill failed")
	}

	disposables.Spare()

	return NewBitmapBrush(bitmap)
}

func (b *GradientBrush) attachWindow(wb *WindowBase) {
	b.brushBase.attachWindow(wb)

	if b.absolute {
		return
	}

	var info *windowBrushInfo

	update := func() {
		if bb, err := b.create(wb.window.ClientBoundsPixels().Size()); err == nil {
			if info.Delegate != nil {
				info.Delegate.bitmap.Dispose()
				info.Delegate.Dispose()
			}

			info.Delegate = bb

			wb.Invalidate()
		}
	}

	info = &windowBrushInfo{
		SizeChangedHandle: wb.SizeChanged().Attach(update),
	}

	update()

	b.wb2info[wb] = info
}

func (b *GradientBrush) detachWindow(wb *WindowBase) {
	if !b.absolute {
		if info, ok := b.wb2info[wb]; ok {
			if info.Delegate != nil {
				info.Delegate.bitmap.Dispose()
				info.Delegate.Dispose()
			}

			wb.SizeChanged().Detach(info.SizeChangedHandle)
		}
	}

	b.brushBase.detachWindow(wb)
}

func (b *GradientBrush) delegateForWindow(wb *WindowBase) Brush {
	if b.absolute {
		return b.mainDelegate
	}

	if info, ok := b.wb2info[wb]; ok && info.Delegate != nil {
		return info.Delegate
	}

	return nil
}
