package HikNetSDK

import (
	"fmt"
	"github.com/ebitengine/purego"
	"runtime"
	"unsafe"
)

var libc uintptr

var (
	DVR_Init func() bool

	newHIKBallCamera func() unsafe.Pointer
	initBallCamera   func(core unsafe.Pointer, ip string, port string, username string, password string, BallMachineType string) bool
	ptzTo            func(core unsafe.Pointer, Action int, P float32, T float32, Z float32) bool
	ptzGet           func(unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer) bool
	stopBus          func(unsafe.Pointer, int) bool
	startBus         func(unsafe.Pointer, int, int) bool

	newHIKNvr                func() unsafe.Pointer
	initNvr                  func(unsafe.Pointer, string, string, string, string, int) bool
	checkTimeRegionWithMonth func(core unsafe.Pointer, year int, month int) string
	checkTimeRegionWithDay   func(core unsafe.Pointer, year int, month int, day int) string
)

func init() {
	var err error
	libc, err = openLibrary(getSystemLibrary())
	if err != nil {
		panic(err)
	}

	purego.RegisterLibFunc(&DVR_Init, libc, "DVR_Init")

	purego.RegisterLibFunc(&newHIKBallCamera, libc, "NewHIKBallCamera")
	purego.RegisterLibFunc(&initBallCamera, libc, "InitBallCamera")
	purego.RegisterLibFunc(&ptzTo, libc, "PtzGotoPut")
	purego.RegisterLibFunc(&ptzGet, libc, "PtzGet")
	purego.RegisterLibFunc(&stopBus, libc, "StopBus")
	purego.RegisterLibFunc(&startBus, libc, "StartBus")

	purego.RegisterLibFunc(&newHIKNvr, libc, "NewHIKNvr")
	purego.RegisterLibFunc(&initNvr, libc, "InitNvr")
	purego.RegisterLibFunc(&checkTimeRegionWithMonth, libc, "CheckTimeRegionWithMonth")
	purego.RegisterLibFunc(&checkTimeRegionWithDay, libc, "CheckTimeRegionWithDay")

}

func getSystemLibrary() string {
	switch runtime.GOOS {
	case "linux":
		if runtime.GOARCH == "amd64" || runtime.GOARCH == "386" {
			return "libHikNetSDKPkg_linux_amd64.so"
		} else {
			return " libHikNetSDKPkg_linux_arm64.so"
		}
	case "windows":
		return "HikNetSDKPkg_win_amd64.dll"
	default:
		panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS))
	}
}

type HIKBallCamera struct {
	core          unsafe.Pointer
	BallCameraCfg BallCamera
}

func NewHIKBallCamera(BallCameraCfg BallCamera) *HIKBallCamera {
	return &HIKBallCamera{
		core:          newHIKBallCamera(),
		BallCameraCfg: BallCameraCfg,
	}
}

func (h *HIKBallCamera) Login() bool {
	return initBallCamera(h.core, h.BallCameraCfg.Ip, h.BallCameraCfg.Port, h.BallCameraCfg.User, h.BallCameraCfg.Password, h.BallCameraCfg.Type)
}
func (h *HIKBallCamera) PtzTo(Action int, P, T, Z float32) bool {
	return ptzTo(h.core, Action, P, T, Z)
}
func (h *HIKBallCamera) PTZGet(P, T, Z *float32) bool {
	return ptzGet(h.core, unsafe.Pointer(P), unsafe.Pointer(T), unsafe.Pointer(Z))
}
func (h *HIKBallCamera) StopBus(Direction int) bool {
	return stopBus(h.core, Direction)
}
func (h *HIKBallCamera) StartBus(Direction, Speed int) bool {
	return startBus(h.core, Direction, Speed)
}

type HIKNvr struct {
	core      unsafe.Pointer
	NvrConfig Nvr
}

func NewHIKNvr(nvrConfig Nvr) *HIKNvr {
	return &HIKNvr{
		core:      newHIKNvr(),
		NvrConfig: nvrConfig,
	}
}

func (h *HIKNvr) Login() bool {
	return initNvr(h.core, h.NvrConfig.Ip, h.NvrConfig.Port, h.NvrConfig.User, h.NvrConfig.Password, h.NvrConfig.Channel)
}

func (h *HIKNvr) CheckTimeRegionWithMonth(year int, month int) string {
	return checkTimeRegionWithMonth(h.core, year, month)
}
func (h *HIKNvr) CheckTimeRegionWithDay(year int, month int, day int) string {
	return checkTimeRegionWithDay(h.core, year, month, day)
}

func (h *HIKBallCamera) WarpingPtByHomography(matrix []float64, p Point) Point {
	var x, y, z float64
	x = matrix[0]*p.X + matrix[1]*p.Y + 1.*matrix[2]
	y = matrix[3]*p.X + matrix[4]*p.Y + 1.*matrix[5]
	z = matrix[6]*p.X + matrix[7]*p.Y + 1.*matrix[8]

	x /= z
	y /= z
	return Point{X: x, Y: y}
}

func (h *HIKBallCamera) mapping(startV float64, max float64, value float64, direction string, method string) float64 {
	if direction == "+" {
		if method == "inv" {
			if value > (max - startV) {
				return value - (max - startV)
			} else {
				return startV + value // 映射
			}
		} else {
			if value > startV {
				return value - startV
			} else {
				return (max - startV) + value // 映射
			}
		}
	} else {
		if value > startV {
			return startV + max - value
		} else {
			return startV - value
		}
	}
}

func (h *HIKBallCamera) OneClickToSeeInFullView(point Point) bool {
	TransPoint := h.WarpingPtByHomography(h.BallCameraCfg.Matrix.Matrix, point)
	return h.PtzTo(5,
		float32(h.mapping(h.BallCameraCfg.Matrix.PStart, h.BallCameraCfg.Matrix.PMax, TransPoint.X, h.BallCameraCfg.Matrix.PPositiveDirection, "inv")),
		float32(h.mapping(h.BallCameraCfg.Matrix.TStart, h.BallCameraCfg.Matrix.TMax, TransPoint.Y, h.BallCameraCfg.Matrix.TPositiveDirection, "inv")),
		0.0,
	)
}

func (h *HIKBallCamera) PTZ2FullView() (Point, error) {
	var ptz PTZ
	if !h.PTZGet(&ptz.P, &ptz.T, &ptz.Z) {
		return Point{}, fmt.Errorf("PTZ Get Error")
	}
	return h.WarpingPtByHomography(h.BallCameraCfg.Matrix.InvMatrix, Point{
		X: h.mapping(h.BallCameraCfg.Matrix.PStart, h.BallCameraCfg.Matrix.PMax, float64(ptz.P), h.BallCameraCfg.Matrix.PPositiveDirection, ""),
		Y: h.mapping(h.BallCameraCfg.Matrix.TStart, h.BallCameraCfg.Matrix.TMax, float64(ptz.T), h.BallCameraCfg.Matrix.TPositiveDirection, "")}), nil
}

func (hikBC *HIKBallCamera) Invert3x3() bool {

	a := hikBC.BallCameraCfg.Matrix.Matrix[0]
	b := hikBC.BallCameraCfg.Matrix.Matrix[1]
	c := hikBC.BallCameraCfg.Matrix.Matrix[2]
	d := hikBC.BallCameraCfg.Matrix.Matrix[3]
	e := hikBC.BallCameraCfg.Matrix.Matrix[4]
	f := hikBC.BallCameraCfg.Matrix.Matrix[5]
	g := hikBC.BallCameraCfg.Matrix.Matrix[6]
	h := hikBC.BallCameraCfg.Matrix.Matrix[7]
	i := hikBC.BallCameraCfg.Matrix.Matrix[8]

	det := a*(e*i-f*h) - b*(d*i-f*g) + c*(d*h-e*g)

	if det == 0 {
		return false
	}

	invDet := 1.0 / det
	hikBC.BallCameraCfg.Matrix.InvMatrix = []float64{
		(e*i - f*h) * invDet,
		(c*h - b*i) * invDet,
		(b*f - c*e) * invDet,
		(f*g - d*i) * invDet,
		(a*i - c*g) * invDet,
		(c*d - a*f) * invDet,
		(d*h - e*g) * invDet,
		(b*g - a*h) * invDet,
		(a*e - b*d) * invDet,
	}
	return true
}
