package devmapper

/*
#cgo LDFLAGS: -L. -ldevmapper
#include <libdevmapper.h>
#include <linux/loop.h> // FIXME: present only for defines, maybe we can remove it?
#include <linux/fs.h>   // FIXME: present only for BLKGETSIZE64, maybe we can remove it?

// FIXME: Can't we find a way to do the logging in pure Go?
extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str);

static void	log_cb(int level, const char *file, int line, int dm_errno_or_class, const char *f, ...)
{
  char buffer[256];
  va_list ap;

  va_start(ap, f);
  vsnprintf(buffer, 256, f, ap);
  va_end(ap);

  DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer);
}

static void	log_with_errno_init()
{
  dm_log_with_errno_init(log_cb);
}
*/
import "C"

import (
	"unsafe"
)

type (
	CDmTask C.struct_dm_task
)

// FIXME: Make sure the values are defined in C
const (
	LoopSetFd        = C.LOOP_SET_FD
	LoopCtlGetFree   = C.LOOP_CTL_GET_FREE
	LoopSetStatus64  = C.LOOP_SET_STATUS64
	LoopClrFd        = C.LOOP_CLR_FD
	LoFlagsAutoClear = C.LO_FLAGS_AUTOCLEAR
	LoFlagsReadOnly  = C.LO_FLAGS_READ_ONLY
	LoFlagsPartScan  = C.LO_FLAGS_PARTSCAN
	LoKeySize        = C.LO_KEY_SIZE
	LoNameSize       = C.LO_NAME_SIZE
)

var (
	DmAttachLoopDevice       = dmAttachLoopDeviceFct
	DmGetBlockSize           = dmGetBlockSizeFct
	DmGetLibraryVersion      = dmGetLibraryVersionFct
	DmGetNextTarget          = dmGetNextTargetFct
	DmLogInitVerbose         = dmLogInitVerboseFct
	DmSetDevDir              = dmSetDevDirFct
	DmTaskAddTarget          = dmTaskAddTargetFct
	DmTaskCreate             = dmTaskCreateFct
	DmTaskDestroy            = dmTaskDestroyFct
	DmTaskGetInfo            = dmTaskGetInfoFct
	DmTaskRun                = dmTaskRunFct
	DmTaskSetAddNode         = dmTaskSetAddNodeFct
	DmTaskSetCookie          = dmTaskSetCookieFct
	DmTaskSetMessage         = dmTaskSetMessageFct
	DmTaskSetName            = dmTaskSetNameFct
	DmTaskSetRo              = dmTaskSetRoFct
	DmTaskSetSector          = dmTaskSetSectorFct
	DmUdevWait               = dmUdevWaitFct
	GetBlockSize             = getBlockSizeFct
	LogWithErrnoInit         = logWithErrnoInitFct
	DmGetLoopbackBackingFile = dmGetLoopbackBackingFileFct
	DmLoopbackSetCapacity    = dmLoopbackSetCapacityFct
)

func free(p *C.char) {
	C.free(unsafe.Pointer(p))
}

func dmTaskDestroyFct(task *CDmTask) {
	C.dm_task_destroy((*C.struct_dm_task)(task))
}

func dmTaskCreateFct(taskType int) *CDmTask {
	return (*CDmTask)(C.dm_task_create(C.int(taskType)))
}

func dmTaskRunFct(task *CDmTask) int {
	ret, _ := C.dm_task_run((*C.struct_dm_task)(task))
	return int(ret)
}

func dmTaskSetNameFct(task *CDmTask, name string) int {
	Cname := C.CString(name)
	defer free(Cname)

	return int(C.dm_task_set_name((*C.struct_dm_task)(task), Cname))
}

func dmTaskSetMessageFct(task *CDmTask, message string) int {
	Cmessage := C.CString(message)
	defer free(Cmessage)

	return int(C.dm_task_set_message((*C.struct_dm_task)(task), Cmessage))
}

func dmTaskSetSectorFct(task *CDmTask, sector uint64) int {
	return int(C.dm_task_set_sector((*C.struct_dm_task)(task), C.uint64_t(sector)))
}

func dmTaskSetCookieFct(task *CDmTask, cookie *uint, flags uint16) int {
	cCookie := C.uint32_t(*cookie)
	defer func() {
		*cookie = uint(cCookie)
	}()
	return int(C.dm_task_set_cookie((*C.struct_dm_task)(task), &cCookie, C.uint16_t(flags)))
}

func dmTaskSetAddNodeFct(task *CDmTask, addNode AddNodeType) int {
	return int(C.dm_task_set_add_node((*C.struct_dm_task)(task), C.dm_add_node_t(addNode)))
}

func dmTaskSetRoFct(task *CDmTask) int {
	return int(C.dm_task_set_ro((*C.struct_dm_task)(task)))
}

func dmTaskAddTargetFct(task *CDmTask,
	start, size uint64, ttype, params string) int {

	Cttype := C.CString(ttype)
	defer free(Cttype)

	Cparams := C.CString(params)
	defer free(Cparams)

	return int(C.dm_task_add_target((*C.struct_dm_task)(task), C.uint64_t(start), C.uint64_t(size), Cttype, Cparams))
}

func dmGetLoopbackBackingFileFct(fd uintptr) (uint64, uint64, sysErrno) {
	var lo64 C.struct_loop_info64
	_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_GET_STATUS64, uintptr(unsafe.Pointer(&lo64)))
	return uint64(lo64.lo_device), uint64(lo64.lo_inode), sysErrno(err)
}

func dmLoopbackSetCapacityFct(fd uintptr) sysErrno {
	_, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_SET_CAPACITY, 0)
	return sysErrno(err)
}

func dmGetBlockSizeFct(fd uintptr) (int64, sysErrno) {
	var size int64
	_, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size)))
	return size, sysErrno(err)
}

func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
	Cinfo := C.struct_dm_info{}
	defer func() {
		info.Exists = int(Cinfo.exists)
		info.Suspended = int(Cinfo.suspended)
		info.LiveTable = int(Cinfo.live_table)
		info.InactiveTable = int(Cinfo.inactive_table)
		info.OpenCount = int32(Cinfo.open_count)
		info.EventNr = uint32(Cinfo.event_nr)
		info.Major = uint32(Cinfo.major)
		info.Minor = uint32(Cinfo.minor)
		info.ReadOnly = int(Cinfo.read_only)
		info.TargetCount = int32(Cinfo.target_count)
	}()
	return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
}

func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
	var (
		Cstart, Clength      C.uint64_t
		CtargetType, Cparams *C.char
	)
	defer func() {
		*start = uint64(Cstart)
		*length = uint64(Clength)
		*target = C.GoString(CtargetType)
		*params = C.GoString(Cparams)
	}()

	nextp := C.dm_get_next_target((*C.struct_dm_task)(task), unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams)
	return uintptr(nextp)
}

func dmAttachLoopDeviceFct(filename string, fd *int) string {
	return ""
	// cFilename := C.CString(filename)
	// defer free(cFilename)

	// var cFd C.int
	// defer func() {
	// 	*fd = int(cFd)
	// }()

	// ret := C.attach_loop_device(cFilename, &cFd)
	// defer free(ret)
	// return C.GoString(ret)
}

func getBlockSizeFct(fd uintptr, size *uint64) sysErrno {
	_, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size)))
	return sysErrno(err)
}

func dmUdevWaitFct(cookie uint) int {
	return int(C.dm_udev_wait(C.uint32_t(cookie)))
}

func dmLogInitVerboseFct(level int) {
	C.dm_log_init_verbose(C.int(level))
}

func logWithErrnoInitFct() {
	C.log_with_errno_init()
}

func dmSetDevDirFct(dir string) int {
	Cdir := C.CString(dir)
	defer free(Cdir)

	return int(C.dm_set_dev_dir(Cdir))
}

func dmGetLibraryVersionFct(version *string) int {
	buffer := C.CString(string(make([]byte, 128)))
	defer free(buffer)
	defer func() {
		*version = C.GoString(buffer)
	}()
	return int(C.dm_get_library_version(buffer, 128))
}