// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

package ddcci

//#cgo CFLAGS: -W -Wall -fstack-protector-all -fPIC
//#cgo LDFLAGS:-ldl
//#cgo pkg-config: ddcutil
//#include <ddcutil_c_api.h>
//#include <stdlib.h>
//#include "ddcci_wrapper.h"
import "C"
import (
	"bytes"
	"encoding/base64"
	"fmt"
	"os/exec"
	"path/filepath"
	"reflect"
	"strings"
	"sync"
	"unsafe"

	"github.com/linuxdeepin/go-lib/utils"
)

type ddcci struct {
	listPointer *C.DDCA_Display_Info_List
	listMu      sync.Mutex

	displayHandleMap map[string]*displayHandle //index -> handle
	//displayMap       map[string]int         //edidBase64 --> ddcutil monitor index
}

type displayHandle struct {
	ddcci  *ddcci
	idx    int
	edid   [128]byte
	info   *C.DDCA_Display_Info
	handle C.DDCA_Display_Handle
	val    int
	state  int
}

func (d *ddcci) newDisplayHandle(idx int) *displayHandle {
	dh := &displayHandle{
		idx: idx,
	}
	dh.info = d.getDisplayInfoByIdx(idx)
	return dh
}

func (d *displayHandle) Open() error {
	if d.state > 0 {
		return nil
	}
	status := C.ddca_open_display2(d.info.dref, C.bool(true), &d.handle)
	if status != C.int(0) {
		logger.Info("doing ddca_open_display2 error")
		return fmt.Errorf("brightness,init: failed to open monitor=%d", status)
	}
	d.state = 1
	return nil
}

func (d *displayHandle) Close() error {
	if d.state == 0 {
		return nil
	}
	C.ddca_close_display(d.handle)
	d.state = 0
	return nil
}

func (d *displayHandle) getState() int {
	return d.state
}

func (d *displayHandle) initEdid() {
	edid := C.GoBytes(unsafe.Pointer(&d.info.edid_bytes), 128)
	copy(d.edid[:], edid)
}

func (d *displayHandle) getEdidBase64() string {
	return base64.StdEncoding.EncodeToString(d.edid[:])
}

func (d *displayHandle) setBrightness(percent int) error {
	//var val C.DDCA_Non_Table_Vcp_Value
	//status := C.ddca_get_non_table_vcp_value(d.handle, brightnessVCP, &val)
	//if status != C.int(0) {
	//	return fmt.Errorf("brightness: failed to get brightness: %d", status)
	//}
	//
	//if int(val.sl) == percent {
	//	return nil
	//}
	status := C.ddca_set_non_table_vcp_value(d.handle, brightnessVCP, 0, C.uchar(percent))
	if status != C.int(0) {
		return fmt.Errorf("brightness: failed to set brightness via DDC/CI: %d", status)
	}
	return nil
}

const (
	brightnessVCP = 0x10
)

func newDDCCI() (*ddcci, error) {
	ddc := &ddcci{
		displayHandleMap: make(map[string]*displayHandle),
	}

	status := C.ddca_set_max_tries(C.DDCA_MULTI_PART_TRIES, 5)
	if status < C.int(0) {
		return nil, fmt.Errorf("brightness: Error setting retries: %d", status)
	}

	err := ddc.RefreshDisplays()
	if err != nil {
		return nil, err
	}

	content, err := exec.Command("/usr/bin/dpkg-architecture", "-qDEB_HOST_MULTIARCH").Output() // TODO: arch和rpm打包需要通过patch修改获取路径的方式
	if err != nil {
		logger.Warning(err)
	} else {
		path := filepath.Join("/usr/lib", strings.TrimSpace(string(content)), "libddcutil.so.0")
		logger.Debug("so path:", path)
		cStr := C.CString(path)
		defer C.free(unsafe.Pointer(cStr))
		ret := C.InitDDCCISo(cStr)
		if ret == -2 {
			logger.Debug("failed to initialize ddca_free_all_displays sym")
		}
	}

	return ddc, nil
}

func (d *ddcci) freeList() {
	//logger.Debug("brightness: freeList, clear all display cache d.lisPointer", d.listPointer)
	for _, handle := range d.displayHandleMap {
		handle.Close()
	}

	d.displayHandleMap = make(map[string]*displayHandle)

	if d.listPointer != nil {
		C.ddca_free_display_info_list(d.listPointer)
		_ = C.freeAllDisplaysWrapper()
		d.listPointer = nil
	}
}

func (d *ddcci) RefreshDisplays() error {
	d.listMu.Lock()
	defer d.listMu.Unlock()

	d.freeList()

	status := C.ddca_get_display_info_list2(C.bool(true), &d.listPointer)
	if status != C.int(0) {
		return fmt.Errorf("brightness: failed to get display info list: %d", status)
	}
	logger.Debug("brightness: display-number=", int(d.listPointer.ct))
	for i := 0; i < int(d.listPointer.ct); i++ {
		err := d.initDisplay(i)
		if err != nil {
			logger.Warning(err)
		}
	}
	return nil
}

func (d *ddcci) initDisplay(idx int) error {
	logger.Debug("brightness: initDisplay enter, index=", idx)
	dh := d.newDisplayHandle(idx)
	dh.Open()
	dh.initEdid()
	d.displayHandleMap[dh.getEdidBase64()] = dh
	return nil
}

func (d *ddcci) SupportBrightness(edidBase64 string) bool {
	d.listMu.Lock()
	defer d.listMu.Unlock()

	index, find := d.findMonitorIndex(edidBase64)
	logger.Debug("brightness: SupportBrightness", index, find, edidBase64)
	return find
}

func (d *ddcci) checkSupportSetBrightness(edidBase64 string) bool {
	// 读取当前亮度值
	currentBrightness, maxBrightness, err := d.GetBrightness(edidBase64)
	if err != nil {
		logger.Warning("SupportBrightnessInit, failed to get brightness:", err)
		return false
	}
	logger.Info("actual currentBrightness : ", currentBrightness, maxBrightness)
	// 对于无法获取到最大亮度的情况，默认使用100进行比较
	if maxBrightness == 0 {
		maxBrightness = 100
		logger.Warning("UnSupport get maxBrightness, default use 100.")
	}
	if currentBrightness > maxBrightness {
		logger.Warning("SupportBrightnessInit, current brightness > max brightness")
		return false
	}

	// 使用当前值最接近的值来验证DDC/CI是否支持亮度调节
	// 计算一个接近当前亮度的测试值
	testBrightness := currentBrightness
	if testBrightness >= 50 {
		testBrightness = testBrightness - 1
	} else {
		testBrightness = testBrightness + 1
	}
	logger.Infof("SupportBrightnessInit, current brightness : %d, test brightness : %d", currentBrightness, testBrightness)

	// 将测试亮度值设置一遍，验证设置功能是否可用
	err = d.SetBrightness(edidBase64, testBrightness)
	if err != nil {
		logger.Warning("SupportBrightnessInit, Set test Brightness err : ", err)

		// 存在概率设置失败，需要将值设置回去
		err = d.SetBrightness(edidBase64, currentBrightness)
		if err != nil {
			logger.Warning("SupportBrightnessInit, reset current Brightness :", currentBrightness, err)
		}
		return false
	}

	// 读取设置后的亮度值，验证设置是否成功
	actualBrightness, _, err := d.GetBrightness(edidBase64)
	if err != nil {
		logger.Warning("SupportBrightnessInit, failed to get brightness:", err)
		return false
	}
	if actualBrightness != testBrightness {
		logger.Warning("SupportBrightnessInit, actualBrightness != testBrightness : ", actualBrightness, testBrightness)
		return false
	}

	// 测试值设置成功了，说明支持设置亮度，需要再设置回原来的值
	err = d.SetBrightness(edidBase64, currentBrightness)
	if err != nil {
		logger.Warning("SupportBrightnessInit, failed to restore brightness to original value :", err)
		return false
	}

	logger.Debug("All checks passed, Support ddcutil set brightness.")
	return true
}

// SupportBrightnessInit 更严格的亮度支持检查，包含完整的验证逻辑，只有初始化时调用
func (d *ddcci) SupportBrightnessInit(edidBase64 string) bool {
	d.listMu.Lock()

	// 步骤2：判断显示器是否支持DDC/CI协议（能否找到显示器）
	index, find := d.findMonitorIndex(edidBase64)
	logger.Debug("brightness: SupportBrightnessInit step2 - find monitor", index, find, edidBase64)
	if !find {
		d.listMu.Unlock()
		return false
	}

	// 获取显示器handle用于后续检查
	handle, ok := d.displayHandleMap[edidBase64]
	if !ok || handle == nil {
		handle = d.getDisplayHandleByIdx(index)
		if handle == nil {
			logger.Warning("brightness: failed to get display handle")
			d.listMu.Unlock()
			return false
		}
	}

	// 确保handle已打开
	if handle.getState() == 0 {
		err := handle.Open()
		if err != nil {
			logger.Warning("brightness: failed to open display handle:", err)
			d.listMu.Unlock()
			return false
		}
	}

	// 步骤4：尝试读取当前亮度值，然后设置相同值来验证读写功能都可用
	// 需要临时释放锁，因为GetBrightness和SetBrightness内部也会尝试获取锁
	d.listMu.Unlock()

	checkSupportSetBrightnessRet := false
	for tryCount := 1; tryCount <= 3; tryCount++ {
		checkSupportSetBrightnessRet = d.checkSupportSetBrightness(edidBase64)
		if checkSupportSetBrightnessRet {
			logger.Info("SupportBrightnessInit, Support ddcutil.")
			break
		} else {
			logger.Warning("SupportBrightnessInit->checkSupportSetBrightness failed, will try again : ", tryCount)
		}
	}
	if !checkSupportSetBrightnessRet {
		logger.Warning("SupportBrightnessInit, UnSupport ddcutil.")
	}

	return checkSupportSetBrightnessRet
}

func (d *ddcci) getBrightness(edidBase64 string) (brightness int, maxBrightness int, err error) {
	d.listMu.Lock()
	defer d.listMu.Unlock()

	handle, ok := d.displayHandleMap[edidBase64]
	if !ok || handle == nil {
		//ignore any bytes
		idx, find := d.findMonitorIndex(edidBase64)
		if find {
			handle = d.getDisplayHandleByIdx(idx)
			logger.Info("get display handle by index:", idx)
		} else {
			err = fmt.Errorf("brightness: failed to find monitor")
			return
		}
	}

	var val C.DDCA_Non_Table_Vcp_Value
	status := C.ddca_get_non_table_vcp_value(handle.handle, brightnessVCP, &val)
	if status != C.int(0) {
		err = fmt.Errorf("brightness: failed to get brightness: %d", status)
		return
	}

	brightness = int(val.sl)
	maxBrightness = int(val.sh)
	return
}

func (d *ddcci) GetBrightness(edidBase64 string) (brightness int, maxBrightness int, err error) {
	for tryGet := 1; tryGet <= 3; tryGet++ {
		brightness, maxBrightness, err = d.getBrightness(edidBase64)
		if err != nil {
			logger.Warning("GetBrightness failed, will get again, err : ", err, tryGet)
		} else {
			return brightness, maxBrightness, err
		}
	}
	return brightness, maxBrightness, err
}

func (d *ddcci) setBrightness(edidBase64 string, percent int) error {
	d.listMu.Lock()
	defer d.listMu.Unlock()

	// 开启结果验证，防止返回设置成功，但实际上没有生效的情况
	// 此方法仅对当前线程生效
	C.ddca_enable_verify(false)
	dh, ok := d.displayHandleMap[edidBase64]
	if !ok || dh == nil {
		//ignore any bytes
		idx, find := d.findMonitorIndex(edidBase64)
		if find {
			dh = d.getDisplayHandleByIdx(idx)
			logger.Info("get display handle by index---------", idx)
		} else {
			return fmt.Errorf("brightness: failed to find monitor")
		}
	}

	if dh.getState() == 0 {
		dh.Open()
	}
	return dh.setBrightness(percent)
}

func (d *ddcci) SetBrightness(edidBase64 string, percent int) error {
	var err error
	for trySet := 1; trySet <= 3; trySet++ {
		err = d.setBrightness(edidBase64, percent)
		if err != nil {
			logger.Warning("SetBrightness failed, err : ", err)
		} else {
			return err
		}
	}
	return err
}

func (d *ddcci) getDisplayHandleByIdx(idx int) *displayHandle {
	for _, handle := range d.displayHandleMap {
		if handle.idx == idx {
			logger.Debugf("getDisplayHandleByIdx:=== %d\n", idx)
			return handle
		}
	}
	return nil
}

func zeroEdidMonitorName(byBuf []byte) bool {
	if len(byBuf) < 128 {
		logger.Infof("zeroEdidMonitorName: len error, %d\n", len(byBuf))
		return false
	}
	hasModify := false
	//解释几个数字: edid长度128,头部长54，扩展部分18字节一段，54 + 18 * 4 = 126
	for i := 0; i < 4; i++ {
		offset := 0x36 + i*18
		//fmt.Printf("zeroEdidMonitorName: idx=%d, %x\n", i, byBuf[offset: offset + 5])
		if reflect.DeepEqual(byBuf[offset:offset+3], []byte{0, 0, 0}) {
			if byBuf[offset+3] == 0xfc {
				//logger.Debugf("brightness: zeroEdidMonitorName, block index=%d\n", i)
				hasModify = true
				zeroStart := offset + 5
				zeroLen := 13
				for j := 0; j < zeroLen; j++ {
					byBuf[zeroStart+j] = 0
				}
			}
		}
	}
	return hasModify
}

func (d *ddcci) findMonitorIndex(edidBase64 string) (int, bool) {
	logger.Debug("edidBase64:", edidBase64)
	dh, find := d.displayHandleMap[edidBase64]
	if find {
		logger.Info("find df for edidBase64:", edidBase64)
		return dh.idx, find
	}

	idx := 0
	bufInput, _ := base64.StdEncoding.DecodeString(edidBase64)
	zeroEdidMonitorName(bufInput)
	for edidIter, idxIter := range d.displayHandleMap {
		//logger.Debugf("edid:", edidIter)
		bufIter, _ := base64.StdEncoding.DecodeString(edidIter)
		zeroEdidMonitorName(bufIter)
		// ddcci只存储128个字节，所以之比较前128个字节
		if bytes.HasPrefix(bufInput, bufIter) {
			idx = idxIter.idx
			find = true
			//logger.Debugf("brightness: DeepEqual\n")
		}
	}
	return idx, find
}

func (d *ddcci) getDisplayInfoByIdx(idx int) *C.DDCA_Display_Info {
	start := unsafe.Pointer(uintptr(unsafe.Pointer(d.listPointer)) + uintptr(C.sizeof_DDCA_Display_Info_List))
	size := uintptr(C.sizeof_DDCA_Display_Info)

	return (*C.DDCA_Display_Info)(unsafe.Pointer(uintptr(start) + size*uintptr(idx)))
}

func getEDIDChecksum(edid []byte) string {
	if len(edid) < 128 {
		return ""
	}

	id, _ := utils.SumStrMd5(string(edid[:128]))
	return id
}
