package xugusql

import (
	"C"
	"database/sql/driver"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"
	"unsafe"
)

// Auxiliary Struct
type __Value struct {
	// Boolean value, if the value is true, it means that the data
	// type of the current field is a large object data type
	islob bool

	// A pointer of * C.char data type, usually
	// pointing to the address of the parameter value to be bound
	value *C.char
	plob  unsafe.Pointer

	// length usually specifies the true length of the parameter
	// data to be bound
	length C.int

	// buff usually specifies the memory buffer
	// size of the parameter data to be bound
	buff C.int

	// When parameter binding, specify the data type of the field in the table
	types int

	// Return code
	rcode C.int
}

type parse struct {
	// bind_type is used to identify the type of parameter binding.
	// Parameter binding types include binding by parameter name
	// and binding by parameter placeholder
	bind_type int

	// param_count is used to specify the number
	// of parameters that need to be bound in the SQL statement
	param_count int

	// When the parameter binding type is binding
	// by parameter name, param_names is a collection of parameter names
	param_names []*C.char

	Val []__Value

	// When the parameter binding type is binding
	// by parameter placeholder, position identifies the parameter position
	position int
}

type ParseParam interface {
	// Conversion parameter data type
	assertParamType(driver.Value, int) error
	// Number of parsing parameters
	assertParamCount(string) int

	// Parse parameter binding type (binding type by parameter name
	// and binding type by parameter position)
	assertBindType(string) int

	// Parse parameter name
	assertParamName(string) error
}

func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
	__Par := make([]driver.Value, len(named))

	for pos, Param := range named {
		if len(Param.Name) > 0 {
			return nil, errors.New("Driver does not support the use of Named Parameters")
		}
		__Par[pos] = Param.Value
	}

	return __Par, nil
}

func (self *parse) assertParamType(dV driver.Value, pos int) error {

	var dest __Value
	switch dV.(type) {

	case int64:
		srcv, ok := dV.(int64)
		if !ok {
			news := errorNews("int64")
			return errors.New(news)
		}

		S := strconv.FormatInt(srcv, 10)
		dest.value = C.CString(S)
		dest.length = C.int(strings.Count(S, "") - 1)
		dest.buff = dest.length + 1
		dest.islob = false
		dest.types = SQL_XG_C_CHAR

	case float32:
		srcv, ok := dV.(float64)
		if !ok {
			news := errorNews("float32")
			return errors.New(news)
		}

		S := strconv.FormatFloat(srcv, 'f', 6, 64)
		dest.value = C.CString(S)
		dest.length = C.int(strings.Count(S, "") - 1)
		dest.buff = dest.length + 1
		dest.islob = false
		dest.types = SQL_XG_C_CHAR

	case float64:
		srcv, ok := dV.(float64)
		if !ok {
			news := errorNews("float64")
			return errors.New(news)
		}

		S := strconv.FormatFloat(srcv, 'f', 15, 64)
		dest.value = C.CString(S)
		dest.length = C.int(strings.Count(S, "") - 1)
		dest.buff = dest.length + 1
		dest.islob = false
		dest.types = SQL_XG_C_CHAR

	case bool:
		srcv, ok := dV.(bool)
		if !ok {
			news := errorNews("bool")
			return errors.New(news)
		}

		S := strconv.FormatBool(srcv)
		dest.value = C.CString(S)
		dest.length = C.int(strings.Count(S, "") - 1)
		dest.buff = dest.length + 1
		dest.islob = false
		dest.types = SQL_XG_C_CHAR

	case string:
		srcv, ok := dV.(string)
		if !ok {
			news := errorNews("string")
			return errors.New(news)
		}

		dest.value = C.CString(srcv)
		dest.length = C.int(strings.Count(srcv, "") - 1)
		dest.buff = dest.length + 1
		dest.islob = false
		dest.types = SQL_XG_C_CHAR

		if dest.length == 0 {
			dest.length = 1
		}

	case time.Time:
		srcv, ok := dV.(time.Time)
		if !ok {
			news := errorNews("time.Time")
			return errors.New(news)
		}

		tm := fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d",
			srcv.Year(), int(srcv.Month()), srcv.Day(),
			srcv.Hour(), srcv.Minute(), srcv.Second())

		dest.value = C.CString(tm)
		dest.length = C.int(strings.Count(tm, "") - 1)
		dest.buff = dest.length + 1
		dest.islob = false
		dest.types = SQL_XG_C_CHAR

	case []byte:

		re := cgo_xgc_new_lob(&dest.plob)
		if re < 0 {
			return errors.New("Cannot create new large object")
		}

		srcv, ok := dV.([]byte)
		if !ok {

			news := errorNews("[]byte")
			return errors.New(news)
		}

		cgo_xgc_put_lob_data(
			&dest.plob,
			unsafe.Pointer((*C.char)(unsafe.Pointer(&srcv[0]))),
			len(srcv))
		cgo_xgc_put_lob_data(&dest.plob, nil, -1)

		dest.value = nil
		dest.length = C.int(8)
		dest.buff = C.int(8)
		dest.islob = true
		dest.types = SQL_XG_C_BLOB

	case nil:
		dest.value = C.CString("xugusql")
		dest.length = 0
		dest.buff = C.int(strings.Count("xugusql", ""))
		dest.islob = false
		dest.types = SQL_XG_C_CHAR

	default:
		/* OTHER DATA TYPE */
		return errors.New("Unknown data type")
	}

	self.position = pos
	self.Val = append(self.Val, dest)

	return nil
}

func errorNews(str string) string {
	return fmt.Sprintf("[%s] asserting data type failed.", str)
}

func (self *parse) assertParamCount(query string) int {

	if self.bind_type == 0 {
		self.bind_type = self.assertBindType(query)
	}

	switch self.bind_type {
	case BIND_PARAM_BY_POS:
		self.param_count = strings.Count(query, "?")
	case BIND_PARAM_BY_NAME:

		self.param_count = 0
		pos := 0
		phead := -1

		for true {
			pos = strings.IndexByte(query[phead+1:], ':')
			if pos == -1 {
				break
			}

			pos += phead + 1
			tmp := pos
			for tmp > phead {
				tmp--
				if query[tmp] == ' ' {
					continue
				}

				if query[tmp] == ',' || query[tmp] == '(' {
					self.param_count++
				}
				break
			}
			phead = pos
		}
	}

	return self.param_count
}

func (self *parse) assertBindType(query string) int {

	self.bind_type = strings.IndexByte(query, '?')
	if self.bind_type != -1 {
		return BIND_PARAM_BY_POS
	}

	return BIND_PARAM_BY_NAME
}

func (self *parse) assertParamName(query string) error {

	if self.param_count <= 0 {
		self.assertParamCount(query)
	}

	pos := 0
	phead := -1

	for true {
		pos = strings.IndexByte(query[phead+1:], ':')
		if pos == -1 {
			break
		}

		pos += phead + 1
		tmp := pos
		for tmp > phead {
			tmp--
			if query[tmp] == ' ' {
				continue
			}

			// Parse parameter positions bound by parameter name
			if query[tmp] == ',' || query[tmp] == '(' {
				parg := pos
				for true {
					parg++
					if query[parg] == ',' || query[parg] == ')' || query[parg] == ' ' {
						self.param_names = append(self.param_names, C.CString(query[pos+1:parg]))
						break
					}
				}
			}
			break
		}

		phead = pos
	}

	return nil
}