package go_ora

import (
	"bytes"
	"context"
	"database/sql"
	"database/sql/driver"
	"encoding/binary"
	"errors"
	"fmt"
	"reflect"
	"regexp"
	"strings"
	"time"

	"github.com/sijms/go-ora/v2/lazy_init"

	"github.com/sijms/go-ora/v2/configurations"
	"github.com/sijms/go-ora/v2/network"
)

type StmtType int

const (
	SELECT StmtType = 1
	DML    StmtType = 2
	PLSQL  StmtType = 3
	OTHERS StmtType = 4
)

type StmtInterface interface {
	hasMoreRows() bool
	noOfRowsToFetch() int
	fetch(resultSet *ResultSet) error
	hasBLOB() bool
	hasLONG() bool
	read(resultSet *ResultSet) error
	Close() error
	CanAutoClose() bool
}
type defaultStmt struct {
	connection *Connection
	text       string
	// disableCompression bool
	_hasLONG          bool
	_hasBLOB          bool
	_hasMoreRows      bool
	_hasReturnClause  bool
	_noOfRowsToFetch  int
	stmtType          StmtType
	cursorID          int
	queryID           uint64
	Pars              []ParameterInfo
	columns           []ParameterInfo
	scnForSnapshot    []int
	arrayBindCount    int
	containOutputPars bool
	autoClose         bool
	temporaryLobs     [][]byte
	multiSet          []RefCursor
}

func (stmt *defaultStmt) CanAutoClose() bool {
	return stmt.autoClose
}

func (stmt *defaultStmt) hasMoreRows() bool {
	return stmt._hasMoreRows
}

func (stmt *defaultStmt) noOfRowsToFetch() int {
	return stmt._noOfRowsToFetch
}

func (stmt *defaultStmt) hasLONG() bool {
	return stmt._hasLONG
}

func (stmt *defaultStmt) hasBLOB() bool {
	return stmt._hasBLOB
}

// basicWrite this is the default write procedure for the all type of stmt
// through it the stmt data will send to network stream
func (stmt *defaultStmt) basicWrite(exeOp int, parse, define bool) error {
	session := stmt.connection.session
	strConv, _ := stmt.connection.getStrConv(stmt.connection.tcpNego.ServerCharset)
	session.PutBytes(3, 0x5E, 0)
	session.PutUint(exeOp, 4, true, true)
	session.PutUint(stmt.cursorID, 2, true, true)
	if stmt.cursorID == 0 {
		session.PutBytes(1)
	} else {
		session.PutBytes(0)
	}
	if parse {
		session.PutUint(len(strConv.Encode(stmt.text)), 4, true, true)
		// session.PutUint(len(stmt.connection.strConv.Encode(stmt.text)), 4, true, true)
		session.PutBytes(1)
	} else {
		session.PutBytes(0, 1)
	}
	session.PutUint(13, 2, true, true)
	session.PutBytes(0, 0)
	if exeOp&0x40 == 0 && exeOp&0x20 != 0 && exeOp&0x1 != 0 && stmt.stmtType == SELECT {
		session.PutBytes(0)
		session.PutUint(stmt._noOfRowsToFetch, 4, true, true)
	} else {
		session.PutBytes(0, 0)
		// session.PutUint(0, 4, true, true)
		// session.PutUint(0, 4, true, true)
	}
	// switch (longFetchSize)
	// {
	// case -1:
	// 	this.m_marshallingEngine.MarshalUB4((long) int.MaxValue);
	// 	break;
	// case 0:
	// 	this.m_marshallingEngine.MarshalUB4(1L);
	// 	break;
	// default:
	// 	this.m_marshallingEngine.MarshalUB4((long) longFetchSize);
	// 	break;
	// }
	// we use here int.MaxValue
	if stmt.connection.connOption.Lob == configurations.INLINE {
		session.PutInt(0x3FFFFFFF, 4, true, true)
		// session.PutUint(0, 4, true, true)
	} else {
		session.PutUint(0x7FFFFFFF, 4, true, true)
	}

	if len(stmt.Pars) > 0 && !define {
		session.PutBytes(1)
		session.PutUint(len(stmt.Pars), 2, true, true)
	} else {
		session.PutBytes(0, 0)
	}
	session.PutBytes(0, 0, 0, 0, 0)
	if define {
		session.PutBytes(1)
		session.PutUint(len(stmt.columns), 2, true, true)
	} else {
		session.PutBytes(0, 0)
	}
	if session.TTCVersion >= 4 {
		session.PutBytes(0, 0, 1)
	}
	if session.TTCVersion >= 5 {
		session.PutBytes(0, 0, 0, 0, 0)
	}
	if session.TTCVersion >= 7 {
		if stmt.stmtType == DML && stmt.arrayBindCount > 0 {
			session.PutBytes(1)
			session.PutInt(stmt.arrayBindCount, 4, true, true)
			session.PutBytes(1)
		} else {
			session.PutBytes(0, 0, 0)
		}
	}
	if session.TTCVersion >= 8 {
		session.PutBytes(0, 0, 0, 0, 0)
	}
	if session.TTCVersion >= 9 {
		session.PutBytes(0, 0)
	}
	if parse {
		session.PutClr(strConv.Encode(stmt.text))
	}
	al8i4 := make([]int, 13)
	if exeOp&1 <= 0 {
		al8i4[0] = 0
	} else {
		al8i4[0] = 1
	}
	switch stmt.stmtType {
	case DML:
		fallthrough
	case PLSQL:
		if stmt.arrayBindCount > 0 {
			al8i4[1] = stmt.arrayBindCount
			if stmt.stmtType == DML {
				al8i4[9] = 0x4000
			}
		} else {
			al8i4[1] = 1
		}
	case OTHERS:
		al8i4[1] = 1
	default:
		// this.m_al8i4[1] = !fetch ? 0L : noOfRowsToFetch;
		// al8i4[1] = stmt._noOfRowsToFetch
		if stmt.connection.connOption.Lob == configurations.INLINE {
			if parse {
				al8i4[1] = 0
			} else {
				al8i4[1] = stmt._noOfRowsToFetch
			}
		} else {
			al8i4[1] = stmt._noOfRowsToFetch
		}

	}
	if len(stmt.scnForSnapshot) == 2 {
		al8i4[5] = stmt.scnForSnapshot[0]
		al8i4[6] = stmt.scnForSnapshot[1]
	} else {
		al8i4[5] = 0
		al8i4[6] = 0
	}
	if stmt.stmtType == SELECT {
		al8i4[7] = 1
	} else {
		al8i4[7] = 0
	}
	if exeOp&32 != 0 {
		al8i4[9] |= 0x8000
	} else {
		al8i4[9] &= -0x8000
	}
	for x := 0; x < len(al8i4); x++ {
		session.PutUint(al8i4[x], 4, true, true)
	}
	if define {
		err := stmt.writeDefine()
		if err != nil {
			return err
		}
	} else {
		for _, par := range stmt.Pars {
			_ = par.write(session)
		}
	}
	return nil
}

func (stmt *defaultStmt) writeDefine() error {
	session := stmt.connection.session
	num := 0x7FFFFFFF
	for index, col := range stmt.columns {
		// temp := new(ParameterInfo)
		// *temp = col
		col.oaccollid = 0
		col.Precision = 0
		col.Scale = 0
		col.MaxCharLen = 0
		if col.DataType == OCIBlobLocator || col.DataType == OCIClobLocator || col.DataType == VECTOR {
			num = 0
			// temp.ContFlag |= 0x2000000
			if stmt.connection.connOption.Lob == configurations.INLINE && !col.IsJson {
				num = 0x3FFFFFFF
				// col.MaxCharLen = 0
				if col.DataType == OCIBlobLocator || col.DataType == VECTOR {
					col.DataType = LongRaw
					// change data type in the original array
					stmt.columns[index].DataType = LongRaw
				} else {
					col.DataType = LongVarChar
					// change data type in the original array
					stmt.columns[index].DataType = LongVarChar
				}
			} else {
				col.ContFlag |= 0x2000000
				// num = 0x7FFFFFFF
				col.MaxCharLen = 0x8000
			}
		} else {
			col.ContFlag = 0
		}
		col.Flag = 3
		col.MaxLen = num

		err := col.write(session)
		if err != nil {
			return err
		}
	}
	return nil
}

type Stmt struct {
	defaultStmt
	// reExec           bool
	reSendParDef bool
	parse        bool // means parse the command in the server this occurs if the stmt is not cached
	execute      bool
	define       bool
	bulkExec     bool
	// noOfDefCols        int
}

type QueryResult struct {
	lastInsertedID int64
	rowsAffected   int64
}

func (rs *QueryResult) LastInsertId() (int64, error) {
	return rs.lastInsertedID, nil
}

func (rs *QueryResult) RowsAffected() (int64, error) {
	return rs.rowsAffected, nil
}

var sqlQueryReturnStatementRegexp = lazy_init.NewLazyInit(func() (interface{}, error) {
	return regexp.Compile(`(\bRETURNING\b|\bRETURN\b)\s+.*\s+\bINTO\b`)
})

// NewStmt create new stmt and set its connection properties
func NewStmt(text string, conn *Connection) *Stmt {
	stmt := &Stmt{
		reSendParDef: false,
		parse:        true,
		execute:      true,
		define:       false,
	}
	stmt.connection = conn
	stmt.text = text
	stmt._hasBLOB = false
	stmt._hasLONG = false
	// stmt.disableCompression = false
	stmt.arrayBindCount = 0
	stmt.scnForSnapshot = make([]int, 2)
	// get stmt type
	uCmdText := strings.ToUpper(refineSqlText(text))
	if strings.HasPrefix(uCmdText, "(") {
		uCmdText = uCmdText[1:]
	}
	if strings.HasPrefix(uCmdText, "SELECT") || strings.HasPrefix(uCmdText, "WITH") {
		stmt.stmtType = SELECT
	} else if strings.HasPrefix(uCmdText, "INSERT") ||
		strings.HasPrefix(uCmdText, "MERGE") ||
		strings.HasPrefix(uCmdText, "UPDATE") ||
		strings.HasPrefix(uCmdText, "DELETE") {
		stmt.stmtType = DML
		stmt.bulkExec = true
	} else if strings.HasPrefix(uCmdText, "DECLARE") || strings.HasPrefix(uCmdText, "BEGIN") {
		stmt.stmtType = PLSQL
	} else {
		stmt.stmtType = OTHERS
	}

	// returning clause
	if stmt.stmtType != PLSQL {
		sqlQueryReturnStatementRegexpAny, err := sqlQueryReturnStatementRegexp.GetValue()
		if err != nil {
			stmt._hasReturnClause = false
			return stmt
		}
		stmt._hasReturnClause = sqlQueryReturnStatementRegexpAny.(*regexp.Regexp).MatchString(uCmdText)
	}
	return stmt
}

func (stmt *Stmt) writePars() error {
	session := stmt.connection.session
	buffer := bytes.Buffer{}
	for _, par := range stmt.Pars {
		if par.Flag == 0x80 {
			continue
		}
		if !stmt.parse && !stmt.reSendParDef && par.Direction == Output {
			continue
		}
		if !par.isLongType() {
			if par.DataType == REFCURSOR {
				session.WriteBytes(&buffer, 1, 0)
			} else if (par.Direction == Input || par.Direction == InOut) && par.isLobType() {
				if len(par.BValue) > 0 {
					session.WriteUint(&buffer, len(par.BValue), 2, true, true)
				}
				session.WriteClr(&buffer, par.BValue)
			} else {
				if par.cusType != nil {
					session.WriteBytes(&buffer, 0, 0, 0, 0)
					size := len(par.BValue)
					session.WriteUint(&buffer, size, 4, true, true)
					session.WriteBytes(&buffer, 1, 1)
					session.WriteClr(&buffer, par.BValue)
				} else {
					if par.MaxNoOfArrayElements > 0 {
						if par.BValue == nil {
							session.WriteBytes(&buffer, 0)
						} else {
							session.WriteBytes(&buffer, par.BValue...)
						}
					} else {
						session.WriteClr(&buffer, par.BValue)
					}
				}
			}
		}
	}
	for _, par := range stmt.Pars {
		if par.isLongType() {
			session.WriteClr(&buffer, par.BValue)
		}
	}
	if buffer.Len() > 0 {
		session.PutBytes(7)
		session.PutBytes(buffer.Bytes()...)
	}
	return nil
}

// write stmt data to network stream
func (stmt *Stmt) write() error {
	// add temporay lobs first
	for _, par := range stmt.Pars {
		stmt.temporaryLobs = append(stmt.temporaryLobs, par.collectLocators()...)
	}
	session := stmt.connection.session
	if !stmt.parse && !stmt.reSendParDef {
		exeOf := 0
		execFlag := 0
		count := 1
		if stmt.arrayBindCount > 0 {
			count = stmt.arrayBindCount
		}
		if stmt.stmtType == SELECT {
			session.PutBytes(3, 0x4E, 0)
			count = stmt._noOfRowsToFetch
			exeOf = 0x20
			if stmt._hasReturnClause || stmt.stmtType == PLSQL /*|| stmt.disableCompression*/ {
				exeOf |= 0x40000
			}
		} else {
			session.PutBytes(3, 4, 0)
		}
		if stmt.connection.autoCommit {
			execFlag = 1
		}

		session.PutUint(stmt.cursorID, 2, true, true)
		session.PutUint(count, 2, true, true)
		session.PutUint(exeOf, 2, true, true)
		session.PutUint(execFlag, 2, true, true)
		// err := stmt.writePars()
		// if err != nil {
		// 	return err
		// }
		var err error
		if stmt.bulkExec {
			// take copy of parameter values
			arrayValues := make([][][]byte, len(stmt.Pars))
			for x := 0; x < len(stmt.Pars); x++ {
				if stmt.Pars[x].Flag == 0x80 {
					continue
				}
				if tempVal, ok := stmt.Pars[x].iPrimValue.([][]byte); ok {
					arrayValues[x] = tempVal
				} else {
					return errors.New("")
				}
			}
			for valueIndex := 0; valueIndex < stmt.arrayBindCount; valueIndex++ {
				for parIndex, arrayValue := range arrayValues {
					if stmt.Pars[parIndex].Flag == 0x80 {
						continue
					}
					stmt.Pars[parIndex].BValue = arrayValue[valueIndex]
				}
				err = stmt.writePars()
				if err != nil {
					return err
				}
			}

			// for valueIndex, values := range arrayValue {
			// 	stmt.Pars[parIndex].BValue = values[valueIndex]
			// }
			//  valueIndex := 0; valueIndex < stmt.arrayBindCount; valueIndex++ {
			//  each value represented an array of []byte
			// }
			// for valueIndex := 0; valueIndex < stmt.arrayBindCount; valueIndex++ {
			// 	for parIndex, arrayValue := range arrayValues {
			// 		tempVal := reflect.ValueOf(arrayValue)
			// 		err = stmt.Pars[parIndex].encodeValue(tempVal.Index(valueIndex).Interface(), 0, stmt.connection)
			// 		if err != nil {
			// 			return err
			// 		}
			// 	}
			// 	err = stmt.writePars()
			// 	if err != nil {
			// 		return err
			// 	}
			// }
		} else {
			err = stmt.writePars()
			if err != nil {
				return err
			}
		}
	} else {
		// stmt.reExec = true
		err := stmt.basicWrite(stmt.getExeOption(), stmt.parse, stmt.define)
		if err != nil {
			return err
		}
		if stmt.bulkExec {

			arrayValues := make([][][]byte, len(stmt.Pars))
			for x := 0; x < len(stmt.Pars); x++ {
				if stmt.Pars[x].Flag == 0x80 {
					continue
				}
				if tempVal, ok := stmt.Pars[x].iPrimValue.([][]byte); ok {
					arrayValues[x] = tempVal
				} else {
					return errors.New("incorrect array type")
				}
			}
			for valueIndex := 0; valueIndex < stmt.arrayBindCount; valueIndex++ {
				for parIndex, arrayValue := range arrayValues {
					if stmt.Pars[parIndex].Flag == 0x80 {
						continue
					}
					stmt.Pars[parIndex].BValue = arrayValue[valueIndex]
				}
				err = stmt.writePars()
				if err != nil {
					return err
				}
			}
			// arrayValues := make([]driver.Value, len(stmt.Pars))
			// for x := 0; x < len(stmt.Pars); x++ {
			// 	if stmt.Pars[x].Flag == 0x80 {
			// 		continue
			// 	}
			// 	arrayValues[x] = stmt.Pars[x].Value
			// }
			// for valueIndex := 0; valueIndex < stmt.arrayBindCount; valueIndex++ {
			// 	for parIndex, arrayValue := range arrayValues {
			// 		if stmt.Pars[parIndex].Flag == 0x80 {
			// 			continue
			// 		}
			// 		tempVal := reflect.ValueOf(arrayValue)
			// 		err = stmt.Pars[parIndex].encodeValue(tempVal.Index(valueIndex).Interface(), 0, stmt.connection)
			// 		if err != nil {
			// 			return err
			// 		}
			// 	}
			// 	err = stmt.writePars()
			// 	if err != nil {
			// 		return err
			// 	}
			// }
		} else {
			err = stmt.writePars()
			if err != nil {
				return err
			}
		}
	}
	return session.Write()
}

// getExeOption return an integer that act like a flag carry bit value set according
// to stmt properties
func (stmt *Stmt) getExeOption() int {
	op := 0
	if stmt.stmtType == PLSQL || stmt._hasReturnClause {
		op |= 0x40000
	}
	if stmt.arrayBindCount > 1 {
		op |= 0x80000
	}
	if stmt.connection.autoCommit && (stmt.stmtType == DML || stmt.stmtType == PLSQL) {
		op |= 0x100
	}
	if stmt.parse {
		op |= 1
	}
	if stmt.execute {
		op |= 0x20
	}
	if !stmt.parse && !stmt.execute {
		op |= 0x40
	}
	if len(stmt.Pars) > 0 && !stmt.define {
		op |= 0x8
		if stmt.stmtType == PLSQL || (stmt._hasReturnClause && !stmt.reSendParDef) {
			op |= 0x400
		}
	}
	if stmt.stmtType != PLSQL && !stmt._hasReturnClause {
		op |= 0x8000
	}
	if stmt.define {
		op |= 0x10
	}
	return op

	/* HasReturnClause
	if  stmt.PLSQL or cmdText == "" return false
	Regex.IsMatch(cmdText, "\\bRETURNING\\b"
	*/
}

// fetch get more rows from network stream
func (stmt *defaultStmt) fetch(resultSet *ResultSet) error {
	if stmt._noOfRowsToFetch == 25 {
		// m_maxRowSize = m_maxRowSize + m_numOfLOBColumns * Math.Max(86, 86 + (int) lobSize) + m_numOfLONGColumns * Math.Max(2, longSize) + m_numOfBFileColumns * 86;
		maxRowSize := 0
		for _, col := range stmt.columns {
			if col.isLobType() {
				maxRowSize += 86
			} else if col.isLongType() {
				maxRowSize += 2
			} else {
				maxRowSize += col.MaxLen
			}
		}
		if maxRowSize > 0 {
			stmt._noOfRowsToFetch = (0x20000 / maxRowSize) + 1
		}
		stmt.connection.tracer.Printf("Fetch Size Calculated: %d", stmt._noOfRowsToFetch)
	}

	tracer := stmt.connection.tracer
	err := stmt._fetch(resultSet)
	if errors.Is(err, network.ErrConnReset) {
		err = stmt.connection.read()
		session := stmt.connection.session
		if session.Summary != nil {
			stmt.cursorID = session.Summary.CursorID
		}
	}
	if err != nil {
		if isBadConn(err) {
			stmt.connection.setBad()
			tracer.Print("Error: ", err)
			return driver.ErrBadConn
		}
		return err
	}
	// for colIndex, col := range dataSet.Cols {
	// 	if col.DataType == REFCURSOR {
	// 		for rowIndex, row := range dataSet.rows {
	// 			if cursor, ok := row[colIndex].(*RefCursor); ok {
	// 				dataSet.rows[rowIndex][colIndex], err = cursor.Query()
	// 				if err != nil {
	// 					return err
	// 				}
	// 			}
	// 		}
	// 	}
	// }
	return nil
}

func (stmt *defaultStmt) _fetch(resultSet *ResultSet) error {
	session := stmt.connection.session
	// defer func() {
	// 	err := stmt.freeTemporaryLobs()
	// 	if err != nil {
	// 		stmt.connection.tracer.Printf("Error free temporary lobs: %v", err)
	// 	}
	// }()
	session.ResetBuffer()
	session.PutBytes(3, 5, 0)
	session.PutInt(stmt.cursorID, 2, true, true)
	session.PutInt(stmt._noOfRowsToFetch, 2, true, true)
	err := session.Write()
	if err != nil {
		return err
	}
	err = stmt.read(resultSet)
	if err != nil {
		return err
	}
	//if stmt.connection.connOption.Lob > configurations.INLINE {
	//
	//}
	return stmt.decodePrim(resultSet)
	// return nil
}

func (stmt *defaultStmt) queryLobPrefetch(exeOp int, resultSet *ResultSet) error {
	if stmt._noOfRowsToFetch == 25 {
		// m_maxRowSize = m_maxRowSize + m_numOfLOBColumns * Math.Max(86, 86 + (int) lobSize) + m_numOfLONGColumns * Math.Max(2, longSize) + m_numOfBFileColumns * 86;
		maxRowSize := 0
		for _, col := range stmt.columns {
			if col.isLobType() {
				maxRowSize += 86
			} else if col.isLongType() {
				maxRowSize += 2
			} else {
				maxRowSize += col.MaxLen
			}
		}
		if maxRowSize > 0 {
			stmt._noOfRowsToFetch = (0x20000 / maxRowSize) + 1
		}
		stmt.connection.tracer.Printf("Fetch Size Calculated: %d", stmt._noOfRowsToFetch)
	}
	stmt.connection.session.ResetBuffer()
	err := stmt.basicWrite(exeOp, false, true)
	if err != nil {
		return err
	}
	// err = stmt.writePars()
	// if err != nil {
	// 	return err
	// }
	err = stmt.connection.session.Write()
	if err != nil {
		return err
	}
	return stmt.read(resultSet)
}

// read this is common read for stmt it read much information related to
// columns, dataset information, output parameter information, rows values
// and at the end summary object about this operation
func (stmt *defaultStmt) read(resultSet *ResultSet) (err error) {
	loop := true
	after7 := false
	resultSet.parent = stmt
	resultSet.cols = &stmt.columns
	session := stmt.connection.session
	defer func() {
		if session.Summary != nil {
			stmt.cursorID = session.Summary.CursorID
			if session.Summary.RetCode == 1403 {
				stmt._hasMoreRows = false
			}
		}
	}()
	for loop {
		msg, err := session.GetByte()
		if err != nil {
			return err
		}
		switch msg {
		case 6:
			// _, err = session.GetByte()
			err = resultSet.load(session)
			if err != nil {
				return err
			}
			if !after7 {
				if stmt.stmtType == SELECT {
					// b, _ := session.GetBytes(0x10)
					// fmt.Printf("%#v\n", b)
					// return errors.New("interrupt")
				}
			}
		case 7:
			after7 = true
			if stmt._hasReturnClause && stmt.containOutputPars {
				for x := 0; x < len(stmt.Pars); x++ {
					if stmt.Pars[x].Direction == Output {
						num, err := session.GetInt(4, true, true)
						if err != nil {
							return err
						}
						if num > 1 {
							return errors.New("more than one row affected with return clause")
						}
						if num == 0 {
							stmt.Pars[x].BValue = nil
							stmt.Pars[x].Value = nil
						} else {
							err = stmt.calculateParameterValue(&stmt.Pars[x])
							if err != nil {
								return err
							}
						}
					}
				}
			} else {
				if stmt.containOutputPars {
					for x := 0; x < len(stmt.Pars); x++ {
						if stmt.Pars[x].DataType == REFCURSOR {
							typ := reflect.TypeOf(stmt.Pars[x].Value)
							if typ.Kind() == reflect.Ptr {
								if cursor, ok := stmt.Pars[x].Value.(*RefCursor); ok {
									cursor.connection = stmt.connection
									cursor.parent = stmt
									cursor.autoClose = true
									err = cursor.load()
									if err != nil {
										return err
									}
									if stmt.stmtType == PLSQL {
										_, err = session.GetInt(2, true, true)
										if err != nil {
											return err
										}
									}
								} else {
									return errors.New("RefCursor parameter should contain pointer to  RefCursor struct")
								}
							} else {
								return errors.New("RefCursor parameter should contain pointer to  RefCursor struct")
							}
						} else {
							if stmt.Pars[x].Direction != Input {
								err = stmt.calculateParameterValue(&stmt.Pars[x])
								if err != nil {
									return err
								}
							} else {
								//_, err = session.GetClr()
							}
						}
					}
				} else {
					newRow := make(Row, resultSet.columnCount)
					for index, col := range stmt.columns {
						if col.getDataFromServer {
							err = stmt.calculateColumnValue(&col, false)
							if err != nil {
								return err
							}
							if col.isLongType() {
								_, err = session.GetInt(4, true, true)
								if err != nil {
									return err
								}
								_, err = session.GetInt(4, true, true)
								if err != nil {
									return err
								}
							}
							stmt.columns[index] = col
						}
						newRow[index] = col.oPrimValue
					}
					resultSet.rows = append(resultSet.rows, newRow)
				}
			}
		case 8:
			size, err := session.GetInt(2, true, true)
			if err != nil {
				return err
			}
			for x := 0; x < 2; x++ {
				stmt.scnForSnapshot[x], err = session.GetInt(4, true, true)
				if err != nil {
					return err
				}
			}
			for x := 2; x < size; x++ {
				_, err = session.GetInt(4, true, true)
				if err != nil {
					return err
				}
			}
			_, err = session.GetInt(2, true, true)
			if err != nil {
				return err
			}
			size, err = session.GetInt(2, true, true)
			for x := 0; x < size; x++ {
				_, val, num, err := session.GetKeyVal()
				if err != nil {
					return err
				}
				// fmt.Println(key, val, num)
				if num == 163 {
					session.TimeZone = val
					// fmt.Println("session time zone", session.TimeZone)
				}
			}
			if session.TTCVersion >= 4 {
				// get queryID
				size, err = session.GetInt(4, true, true)
				if err != nil {
					return err
				}
				if size > 0 {
					bty, err := session.GetBytes(size)
					if err != nil {
						return err
					}
					if len(bty) >= 8 {
						stmt.queryID = binary.LittleEndian.Uint64(bty[size-8:])
						fmt.Println("query ID: ", stmt.queryID)
					}
				}
			}
			if session.TTCVersion >= 7 && stmt.stmtType == DML && stmt.arrayBindCount > 0 {
				length, err := session.GetInt(4, true, true)
				if err != nil {
					return err
				}
				// for (int index = 0; index < length3; ++index)
				//	rowsAffectedByArrayBind[index] = this.m_marshallingEngine.UnmarshalSB8();
				for i := 0; i < length; i++ {
					_, err = session.GetInt(8, true, true)
					if err != nil {
						return err
					}
				}
			}
		case 11:
			err = resultSet.load(session)
			if err != nil {
				return err
			}
			// dataSet.BindDirections = make([]byte, dataSet.columnCount)
			for x := 0; x < resultSet.columnCount; x++ {
				direction, err := session.GetByte()
				switch direction {
				case 32:
					stmt.Pars[x].Direction = Input
				case 16:
					stmt.Pars[x].Direction = Output
					stmt.containOutputPars = true
				case 48:
					stmt.Pars[x].Direction = InOut
					stmt.containOutputPars = true
				}
				if err != nil {
					return err
				}
			}
		case 16:
			size, err := session.GetByte()
			if err != nil {
				return err
			}
			_, err = session.GetBytes(int(size))
			if err != nil {
				return err
			}
			resultSet.maxRowSize, err = session.GetInt(4, true, true)
			if err != nil {
				return err
			}
			resultSet.columnCount, err = session.GetInt(4, true, true)
			if err != nil {
				return err
			}
			if resultSet.columnCount > 0 {
				_, err = session.GetByte() // session.GetInt(1, false, false)
			}
			stmt.columns = make([]ParameterInfo, resultSet.columnCount)
			for x := 0; x < resultSet.columnCount; x++ {
				err = stmt.columns[x].load(stmt.connection)
				if err != nil {
					return err
				}
				if stmt.columns[x].isLongType() {
					stmt._hasLONG = true
				}
				if stmt.columns[x].isLobType() {
					stmt._hasBLOB = true
				}
			}
			_, err = session.GetDlc()
			if session.TTCVersion >= 3 {
				_, err = session.GetInt(4, true, true)
				_, err = session.GetInt(4, true, true)
			}
			if session.TTCVersion >= 4 {
				_, err = session.GetInt(4, true, true)
				_, err = session.GetInt(4, true, true)
			}
			if session.TTCVersion >= 5 {
				_, err = session.GetDlc()
			}
		case 19:
			session.ResetBuffer()
			session.PutBytes(19)
			err = session.Write()
			if err != nil {
				return err
			}
			continue
		case 21:
			_, err := session.GetInt(2, true, true) // noOfColumnSent
			if err != nil {
				return err
			}
			bitVectorLen := resultSet.columnCount / 8
			if resultSet.columnCount%8 > 0 {
				bitVectorLen++
			}
			bitVector := make([]byte, bitVectorLen)
			for x := 0; x < bitVectorLen; x++ {
				bitVector[x], err = session.GetByte()
				if err != nil {
					return err
				}
			}
			resultSet.setBitVector(bitVector)
		case 27:
			count, err := session.GetInt(4, true, true)
			if err != nil {
				return err
			}
			stmt.multiSet = make([]RefCursor, 0, count)
			for x := 0; x < count; x++ {
				// refCursorAccessor.UnmarshalOneRow();
				// this function is equal to load cursor so each item is a cursor
				cursor := RefCursor{}
				cursor.connection = stmt.connection
				cursor.parent = stmt
				cursor.autoClose = true
				err = cursor.load()
				if err != nil {
					return err
				}

				stmt.multiSet = append(stmt.multiSet, cursor)
				// what we will do with cursor?
			}
			//for _, cursor := range cursors {
			//	err = cursor.Close()
			//	if err != nil {
			//		return err
			//	}
			//}
			//return errors.New("stmt read facing code: 27 (returning multiple ref cursor) still not implemented")
			// internal List<TTCResultSet> ProcessImplicitResultSet(
			// ref List<TTCResultSet> implicitRSList)
			// {
			// int num = (int) this.m_marshallingEngine.UnmarshalUB4();
			// TTCRefCursorAccessor refCursorAccessor = new TTCRefCursorAccessor((ColumnDescribeInfo) null, this.m_marshallingEngine);
			// for (int index = 0; index < num; ++index)
			// refCursorAccessor.UnmarshalOneRow();
			// if (implicitRSList != null)
			// implicitRSList.AddRange((IEnumerable<TTCResultSet>) refCursorAccessor.m_TTCResultSetList);
			// else
			// implicitRSList = refCursorAccessor.m_TTCResultSetList;
			// return implicitRSList;
			// }
		default:
			err = stmt.connection.readMsg(msg)
			if err != nil {
				return err
			}
			if msg == 4 || msg == 9 {
				loop = false
			}
		}
	}
	// if session.IsBreak() {
	// 	err := (&simpleObject{
	// 		connection: stmt.connection,
	// 	}).read()
	// 	if err != nil {
	// 		return err
	// 	}
	// }
	if stmt.connection.tracer.IsOn() {
		resultSet.Trace(stmt.connection.tracer)
	}
	// return stmt.readLobs(dataSet)
	return nil
}

func (stmt *defaultStmt) freeTemporaryLobs() error {
	// var locators = collectLocators(stmt.Pars)
	if len(stmt.temporaryLobs) == 0 {
		return nil
	}
	stmt.connection.tracer.Printf("Free %d Temporary Lobs", len(stmt.temporaryLobs))
	session := stmt.connection.session
	// defer func(input *[][]byte) {
	// 	*input = nil
	// }(&stmt.temporaryLobs)
	freeTemp := func(locators [][]byte) {
		totalLen := 0
		for _, locator := range locators {
			totalLen += len(locator)
		}
		session.PutBytes(0x11, 0x60, 0, 1)
		session.PutUint(totalLen, 4, true, true)
		session.PutBytes(0, 0, 0, 0, 0, 0, 0)
		session.PutUint(0x80111, 4, true, true)
		session.PutBytes(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
		for _, locator := range locators {
			session.PutBytes(locator...)
		}
	}
	start := 0
	end := 0
	session.ResetBuffer()
	for start < len(stmt.temporaryLobs) {
		end = start + 25000
		// end = start + 25
		if end > len(stmt.temporaryLobs) {
			end = len(stmt.temporaryLobs)
		}
		freeTemp(stmt.temporaryLobs[start:end])
		start += end
	}
	session.PutBytes(0x3, 0x93, 0x0)
	err := session.Write()
	if err != nil {
		return err
	}
	return stmt.connection.read()
}

// requestCustomTypeInfo an experimental function to ask for UDT information
func (stmt *defaultStmt) requestCustomTypeInfo(typeName string) error {
	session := stmt.connection.session
	session.SaveState(nil)
	session.PutBytes(0x3, 0x5c, 0)
	session.PutInt(3, 4, true, true)
	// session.PutInt(0x5C0003, 4, true, true)
	// session.PutBytes(bytes.Repeat([]byte{0}, 79)...)

	session.PutBytes(bytes.Repeat([]byte{0}, 19)...)
	session.PutInt(2, 4, true, true)
	// session.PutBytes(2)
	session.PutInt(len(stmt.connection.connOption.UserID), 4, true, true)
	// session.PutBytes(0, 0, 0)
	session.PutClr(stmt.connection.sStrConv.Encode(stmt.connection.connOption.UserID))
	session.PutInt(len(typeName), 4, true, true)
	// session.PutBytes(0, 0, 0)
	session.PutClr(stmt.connection.sStrConv.Encode(typeName))
	// session.PutBytes(0, 0, 0)
	// if session.TTCVersion >= 4 {
	// 	session.PutBytes(0, 0, 1)
	// }
	// if session.TTCVersion >= 5 {
	// 	session.PutBytes(0, 0, 0, 0, 0)
	// }
	// if session.TTCVersion >= 7 {
	// 	if stmt.stmtType == DML && stmt.arrayBindCount > 0 {
	// 		session.PutBytes(1)
	// 		session.PutInt(stmt.arrayBindCount, 4, true, true)
	// 		session.PutBytes(1)
	// 	} else {
	// 		session.PutBytes(0, 0, 0)
	// 	}
	// }
	// if session.TTCVersion >= 8 {
	// 	session.PutBytes(0, 0, 0, 0, 0)
	// }
	// if session.TTCVersion >= 9 {
	// 	session.PutBytes(0, 0)
	// }
	// session.PutBytes(0, 0)
	// session.PutInt(1, 4, true, true)
	// session.PutBytes(0)
	session.PutBytes(0, 0, 0, 0, 0, 1, 0, 0, 0, 0)
	session.PutBytes(bytes.Repeat([]byte{0}, 50)...)
	// session.PutBytes(0)
	// session.PutInt(0x10000, 4, true, true)
	// session.PutBytes(0, 0)
	err := session.Write()
	if err != nil {
		return err
	}
	data, err := session.GetBytes(0x10)
	if err != nil {
		return err
	}
	fmt.Printf("%#v\n", data)
	session.LoadState()
	return nil
}

func (stmt *defaultStmt) calculateColumnValue(col *ParameterInfo, udt bool) error {
	session := stmt.connection.session
	// if col.DataType == OCIBlobLocator || col.DataType == OCIClobLocator {
	// 	stmt._hasBLOB = true
	// }
	if col.DataType == REFCURSOR {
		cursor := new(RefCursor)
		cursor.connection = stmt.connection
		cursor.parent = stmt
		cursor.autoClose = true
		err := cursor.load()
		if err != nil {
			return err
		}
		if stmt.stmtType == PLSQL {
			_, err = session.GetInt(2, true, true)
			if err != nil {
				return err
			}
		}
		// col.Value = cursor
		col.oPrimValue = cursor
		return nil
	}

	return col.decodeColumnValue(stmt.connection, &stmt.temporaryLobs, udt)
}

// get values of rows and output parameter according to DataType and binary value (bValue)
func (stmt *defaultStmt) calculateParameterValue(param *ParameterInfo) error {
	if param.isLobType() {
		stmt._hasBLOB = true
	}
	err := param.decodeParameterValue(stmt.connection, &stmt.temporaryLobs)
	if err != nil {
		return err
	}
	if param.DataType == XMLType && param.IsNull {
		return nil
	}
	if param.DataType != XMLType && param.MaxNoOfArrayElements > 0 {
		return nil
	}
	_, err = stmt.connection.session.GetInt(2, true, true)
	if err != nil {
		return err
	}
	return nil
}

// Close stmt cursor in the server
func (stmt *defaultStmt) Close() error {
	if stmt.connection.State != Opened {
		stmt.connection.setBad()
		return driver.ErrBadConn
	}
	err := stmt.freeTemporaryLobs()
	if err != nil {
		stmt.connection.tracer.Printf("Error free temporary lobs: %v", err)
	}
	if stmt.cursorID != 0 {
		session := stmt.connection.session
		session.ResetBuffer()
		session.PutBytes(0x11, 0x69, 0, 1, 1, 1)
		session.PutInt(stmt.cursorID, 4, true, true)
		return (&simpleObject{
			connection:  stmt.connection,
			operationID: 0x93,
			data:        nil,
			err:         nil,
		}).exec()
	}
	return err
}

func (stmt *Stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
	if stmt.connection.State != Opened {
		stmt.connection.setBad()
		return nil, driver.ErrBadConn
	}
	tracer := stmt.connection.tracer
	tracer.Printf("Exec With Context:")
	done := stmt.connection.session.StartContext(ctx)
	defer stmt.connection.session.EndContext(done)
	tracer.Printf("Exec:\n%s", stmt.text)
	stmt.arrayBindCount = 0
	result, err := stmt._exec(args)
	if errors.Is(err, network.ErrConnReset) {
		if stmt._hasReturnClause {
			dataSet := &DataSet{}
			err = stmt.read(dataSet.currentResultSet())
			if !errors.Is(err, network.ErrConnReset) {
				if isBadConn(err) {
					stmt.connection.setBad()
				}
				return nil, err
			}
		}
		err = stmt.connection.read()
		session := stmt.connection.session
		if session.Summary != nil {
			stmt.cursorID = session.Summary.CursorID
		}
	}
	if err != nil {
		if isBadConn(err) {
			// tracer.Print("Error: ", err)
			stmt.connection.setBad()
		}
		return nil, err
	}
	return result, nil
}

func (stmt *Stmt) fillStructPar(parValue driver.Value) error {
	structType := reflect.TypeOf(parValue)
	structVal := reflect.ValueOf(parValue)
	if parValue != nil && structType.Kind() == reflect.Ptr && structVal.Elem().Kind() == reflect.Struct {
		structType = structType.Elem()
		structVal = structVal.Elem()
		structFieldCount := structType.NumField()
		for i := 0; i < structFieldCount; i++ {
			name, _type, _, dir := extractTag(structType.Field(i).Tag.Get("db"))
			var err error
			if len(name) > 0 && dir != Input && len(_type) > 0 {
				for _, par := range stmt.Pars {
					if par.Name == name {
						fieldValue := structVal.Field(i)
						fieldType := structVal.Field(i).Type()
						switch tempVal := par.Value.(type) {
						case *Number:
							if tempVal == nil {
								fieldValue.Set(reflect.Zero(fieldType))
							} else {
								err = setNumber(fieldValue, tempVal)
							}
						// case *sql.NullFloat64:
						//	if tempVal.Valid {
						//		err = setNumber(fieldValue, tempVal.Float64)
						//	} else {
						//		fieldValue.Set(reflect.Zero(fieldType))
						//	}
						case *sql.NullString:
							if tempVal.Valid {
								err = setString(fieldValue, tempVal.String)
							} else {
								fieldValue.Set(reflect.Zero(fieldType))
							}
						case *NullNVarChar:
							if tempVal.Valid {
								err = setString(fieldValue, string(tempVal.NVarChar))
							} else {
								fieldValue.Set(reflect.Zero(fieldType))
							}
						case *sql.NullTime:
							if tempVal.Valid {
								err = setTime(fieldValue, tempVal.Time)
							} else {
								fieldValue.Set(reflect.Zero(fieldType))
							}
						case *NullTimeStamp:
							if tempVal.Valid {
								err = setTime(fieldValue, time.Time(tempVal.TimeStamp))
							} else {
								fieldValue.Set(reflect.Zero(fieldType))
							}
						case *NullTimeStampTZ:
							if tempVal.Valid {
								err = setTime(fieldValue, time.Time(tempVal.TimeStampTZ))
							} else {
								fieldValue.Set(reflect.Zero(fieldType))
							}
						case *[]byte:
							if tempVal == nil {
								fieldValue.Set(reflect.Zero(fieldType))
							} else {
								err = setBytes(fieldValue, *tempVal)
							}
						case *Clob:
							if tempVal.Valid {
								err = setString(fieldValue, tempVal.String)
							} else {
								fieldValue.Set(reflect.Zero(fieldType))
							}
						case *NClob:
							if tempVal.Valid {
								err = setString(fieldValue, tempVal.String)
							} else {
								fieldValue.Set(reflect.Zero(fieldType))
							}
						case *Blob:
							err = setBytes(fieldValue, tempVal.Data)
						default:
							return errors.New("unknown go type associated with " + _type)
						}
						if err != nil {
							return err
						}
					}
				}
			}
		}
	}
	return nil
}

func (stmt *Stmt) structPar(parValue driver.Value, parIndex int) (processedPars int, err error) {
	tempType := reflect.TypeOf(parValue)
	structValue := reflect.ValueOf(parValue)
	addOutputField := func(name, _type string, size int, dir ParameterDirection, fieldIndex int) (tempPar *ParameterInfo, err error) {
		field := structValue.Field(fieldIndex)
		fieldValue := field.Interface()
		fieldType := field.Type()
		hasNullValue := false
		if fieldType.Kind() == reflect.Ptr {
			if structValue.Field(fieldIndex).IsNil() {
				hasNullValue = true
				fieldType = fieldType.Elem()
			}
		}

		// if type mentioned so driver should create a temporary type and then update the current value
		typeErr := fmt.Errorf("error passing filed %s as type %s", tempType.Field(fieldIndex).Name, _type)
		switch _type {
		case "number":
			var fieldVal *Number
			if hasNullValue {
				fieldVal = &Number{}
			} else {
				fieldVal, err = NewNumber(fieldValue)
				if err != nil {
					err = typeErr
					return
				}
			}
			tempPar, err = stmt.NewParam(name, fieldVal, size, dir)
		case "varchar":
			fieldVal := &sql.NullString{}
			if !hasNullValue {
				fieldVal.String, fieldVal.Valid = getString(fieldValue), true
			}
			tempPar, err = stmt.NewParam(name, fieldVal, size, dir)
		case "nvarchar":
			fieldVal := &NullNVarChar{}
			if !hasNullValue {
				fieldVal.NVarChar, fieldVal.Valid = NVarChar(getString(fieldValue)), true
			}
			tempPar, err = stmt.NewParam(name, fieldVal, size, dir)
		case "date":
			fieldVal := &sql.NullTime{}
			if !hasNullValue {
				fieldVal.Time, err = getDate(fieldValue)
				if err != nil {
					err = typeErr
					return
				}
				fieldVal.Valid = true
			}
			tempPar, err = stmt.NewParam(name, fieldVal, size, dir)
		case "timestamp":
			fieldVal := &NullTimeStamp{}
			if !hasNullValue {
				var tempDate time.Time
				tempDate, err = getDate(fieldValue)
				if err != nil {
					err = typeErr
					return
				}
				fieldVal.TimeStamp = TimeStamp(tempDate)
				fieldVal.Valid = true
			}
			tempPar, err = stmt.NewParam(name, fieldVal, size, dir)
		case "timestamptz":
			fieldVal := &NullTimeStampTZ{}
			if !hasNullValue {
				var tempDate time.Time
				tempDate, err = getDate(fieldValue)
				if err != nil {
					err = typeErr
					return
				}
				fieldVal.TimeStampTZ = TimeStampTZ(tempDate)
				fieldVal.Valid = true
			}
			tempPar, err = stmt.NewParam(name, fieldVal, size, dir)
		case "raw":
			var fieldVal []byte
			if !hasNullValue {
				fieldVal, err = getBytes(fieldValue)
				if err != nil {
					err = typeErr
					return
				}
			}
			tempPar, err = stmt.NewParam(name, &fieldVal, size, dir)
		case "clob":
			fieldVal := &Clob{}
			if !hasNullValue {
				fieldVal.String, fieldVal.Valid = getString(fieldValue), true
			}
			tempPar, err = stmt.NewParam(name, fieldVal, size, dir)
		case "nclob":
			fieldVal := &NClob{}
			if !hasNullValue {
				fieldVal.String, fieldVal.Valid = getString(fieldValue), true
			}
			tempPar, err = stmt.NewParam(name, fieldVal, size, dir)
		case "blob":
			fieldVal := &Blob{}
			if !hasNullValue {
				fieldVal.Data, err = getBytes(fieldValue)
				if err != nil {
					err = typeErr
					return
				}
			}
			tempPar, err = stmt.NewParam(name, fieldVal, size, dir)
		case "":
			if field.Kind() == reflect.Ptr {
				if field.IsNil() {
					field.Set(reflect.New(fieldType))
				}
				tempPar, err = stmt.NewParam(name, field.Interface(), size, dir)
			} else {
				if field.CanAddr() {
					tempPar, err = stmt.NewParam(name, field.Addr().Interface(), size, dir)
				} else {
					err = fmt.Errorf("can't take address for field: %s", name)
				}
			}
		default:
			err = fmt.Errorf("unknown type: %s for parameter: %s", _type, name)
		}
		return
	}
	if parValue != nil && tempType.Kind() == reflect.Struct {
		structFieldCount := tempType.NumField()

		for i := 0; i < structFieldCount; i++ {
			name, _type, _, _ := extractTag(tempType.Field(i).Tag.Get("db"))
			if name != "" {
				var tempPar *ParameterInfo
				tempPar, err = parseInputField(structValue, name, _type, i)
				if err != nil {
					return
				}
				err = tempPar.encodeValue(0, stmt.connection)
				if err != nil {
					return
				}
				stmt.setParam(parIndex, *tempPar)
				processedPars++
				parIndex++
			}
		}
	}

	// deal with Ptr struct types
	if parValue != nil && tempType.Kind() == reflect.Ptr && structValue.Elem().Kind() == reflect.Struct {
		tempType = tempType.Elem()
		structValue = structValue.Elem()
		structFieldCount := tempType.NumField()
		for i := 0; i < structFieldCount; i++ {
			name, _type, size, dir := extractTag(tempType.Field(i).Tag.Get("db"))
			if dir == 0 {
				dir = Input
			}
			if name != "" {
				var tempPar *ParameterInfo
				if dir == Input {
					tempPar, err = parseInputField(structValue, name, _type, i)
					if err != nil {
						return
					}
					err = tempPar.encodeValue(0, stmt.connection)
				} else {
					tempPar, err = addOutputField(name, _type, size, dir, i)
				}
				if err != nil {
					return
				}
				stmt.setParam(parIndex, *tempPar)
				processedPars++
				parIndex++
			}
		}
	}
	return
}

func (stmt *Stmt) _exec(args []driver.NamedValue) (*QueryResult, error) {
	var err error
	useNamedPars := len(args) > 0
	parIndex := 0
	structPars := make([]driver.Value, 0, 2)
	for x := 0; x < len(args); x++ {
		var par *ParameterInfo
		switch tempOut := args[x].Value.(type) {
		case sql.Out:
			stmt.bulkExec = false
			direction := Output
			if tempOut.In {
				direction = InOut
			}
			par, err = stmt.NewParam(args[x].Name, tempOut.Dest, 0, direction)
			if err != nil {
				return nil, err
			}
		case *sql.Out:
			stmt.bulkExec = false
			direction := Output
			if tempOut.In {
				direction = InOut
			}
			par, err = stmt.NewParam(args[x].Name, tempOut.Dest, 0, direction)
			if err != nil {
				return nil, err
			}
		case Out:
			stmt.bulkExec = false
			direction := Output
			if tempOut.In {
				direction = InOut
			}
			par, err = stmt.NewParam(args[x].Name, tempOut.Dest, tempOut.Size, direction)
			if err != nil {
				return nil, err
			}
		case *Out:
			stmt.bulkExec = false
			direction := Output
			if tempOut.In {
				direction = InOut
			}
			par, err = stmt.NewParam(args[x].Name, tempOut.Dest, tempOut.Size, direction)
			if err != nil {
				return nil, err
			}
		case *batch:
			stmt.bulkExec = true
			args[x].Value = tempOut.array
		default:
			processedPars := 0
			processedPars, err = stmt.structPar(args[x].Value, parIndex)
			if err != nil {
				return nil, err
			}
			if processedPars > 0 {
				stmt.bulkExec = false
				stmt.connection.tracer.Printf("    %d:\n%v", x, args[x])
				parIndex += processedPars
				structPars = append(structPars, args[x].Value)
				continue
			}
		}
		if stmt.bulkExec {
			tempType := reflect.TypeOf(args[x].Value)
			tempVal := reflect.ValueOf(args[x].Value)
			if args[x].Value != nil && tempType != reflect.TypeOf([]byte{}) && (tempType.Kind() == reflect.Array || tempType.Kind() == reflect.Slice) {
				// setup array count
				if stmt.arrayBindCount == 0 {
					stmt.arrayBindCount = tempVal.Len()
				} else {
					if stmt.arrayBindCount > tempVal.Len() {
						stmt.arrayBindCount = tempVal.Len()
					}
				}
				// see if first item is struct
				firstItem := tempVal.Index(0)
				// lobData := make([]*Lob, stmt.arrayBindCount)
				if firstItem.Kind() == reflect.Struct {
					fieldCount := firstItem.NumField()
					structArrayAsNamedPars := make([]driver.NamedValue, 0, fieldCount)
					for fieldIndex := 0; fieldIndex < fieldCount; fieldIndex++ {
						name, _type, _, _ := extractTag(firstItem.Type().Field(fieldIndex).Tag.Get("db"))
						if name != "" {
							arrayValues := make([]driver.Value, stmt.arrayBindCount)
							for arrayIndex := 0; arrayIndex < stmt.arrayBindCount; arrayIndex++ {
								var tempPar *ParameterInfo
								tempPar, err = parseInputField(tempVal.Index(arrayIndex), name, _type, fieldIndex)
								if err != nil {
									return nil, err
								}
								arrayValues[arrayIndex] = tempPar.Value
							}
							structArrayAsNamedPars = append(structArrayAsNamedPars, driver.NamedValue{Name: name, Value: arrayValues})
						}
					}
					if len(structArrayAsNamedPars) > 0 {
						return stmt._exec(structArrayAsNamedPars)
					}
				}
				par = &ParameterInfo{
					Name:      args[x].Name,
					Direction: Input,
				}
				maxLen := 0
				maxCharLen := 0
				dataType := TNSType(0)
				arrayValues := make([][]byte, stmt.arrayBindCount)
				for y := 0; y < stmt.arrayBindCount; y++ {
					par.Value = tempVal.Index(y).Interface()
					err = par.encodeValue(0, stmt.connection)
					if err != nil {
						return nil, err
					}
					stmt.temporaryLobs = append(stmt.temporaryLobs, par.collectLocators()...)
					if maxLen < par.MaxLen {
						maxLen = par.MaxLen
					}
					if maxCharLen < par.MaxCharLen {
						maxCharLen = par.MaxCharLen
					}
					// here I can take the binary value and store it into array
					arrayValues[y] = par.BValue
					if len(par.BValue) == 0 && par.DataType == NCHAR {
						continue
					}
					dataType = par.DataType
				}
				// save arrayValues into primitive
				par.iPrimValue = arrayValues
				//_ = par.encodeValue(tempVal.Index(0).Interface(), 0, stmt.connection)
				par.MaxLen = maxLen
				par.MaxCharLen = maxCharLen
				if int(dataType) == 0 {
					dataType = NCHAR
				}
				par.DataType = dataType
			} else {
				if stmt.arrayBindCount > 0 {
					return nil, errors.New("to activate bulk insert/merge all parameters should be arrays")
				}
				stmt.bulkExec = false
			}
		}
		if par == nil {
			par, err = stmt.NewParam(args[x].Name, args[x].Value, 0, Input)
			if err != nil {
				return nil, err
			}
		}
		if len(par.Name) == 0 && useNamedPars {
			useNamedPars = false
		}
		stmt.setParam(parIndex, *par)
		parIndex++
		stmt.connection.tracer.Printf("    %d:\n%v", x, args[x])
	}
	if useNamedPars {
		err = stmt.useNamedParameters()
		if err != nil {
			return nil, err
		}
	}
	session := stmt.connection.session
	session.ResetBuffer()
	err = stmt.write()
	if err != nil {
		stmt.connection.setBad()
		return nil, err
	}
	dataSet := new(DataSet)
	err = stmt.read(dataSet.currentResultSet())
	if err != nil {
		return nil, err
	}
	// before release results decode parameters
	for _, par := range stmt.Pars {
		if par.Direction != Input && par.DataType != REFCURSOR {
			fieldValue := reflect.ValueOf(par.Value)
			if fieldValue.Kind() != reflect.Ptr {
				return nil, errors.New("output parameter should be pointer type")
			}
			fieldValue = fieldValue.Elem()
			if par.MaxNoOfArrayElements > 0 {
				if pars, ok := par.oPrimValue.([]ParameterInfo); ok {
					err = setArray(fieldValue, pars)
					if err != nil {
						return nil, err
					}
				}
			} else {
				err = setFieldValue(fieldValue, par.cusType, par.oPrimValue)
				if err != nil {
					return nil, err
				}
			}
		}
	}
	result := new(QueryResult)
	if session.Summary != nil {
		result.rowsAffected = int64(session.Summary.CurRowNumber)
	}
	for _, par := range structPars {
		err = stmt.fillStructPar(par)
		if err != nil {
			return nil, err
		}
	}
	stmt.parse = false
	stmt.define = false
	stmt.reSendParDef = false
	return result, nil
}

// useNamedParameters: re-arrange parameters according parameter defined in sql text
func (stmt *Stmt) useNamedParameters() error {
	names, err := parseQueryParametersNames(stmt.text)
	if err != nil {
		return err
	}
	parCollection := make([]ParameterInfo, 0, len(names))
	if stmt.stmtType == SELECT || stmt.stmtType == DML {
		for x := 0; x < len(names); x++ {
			found := false
			for _, par := range stmt.Pars {
				if strings.ToLower(par.Name) == strings.ToLower(names[x]) {
					parCollection = append(parCollection, par)
					found = true
					break
				}
			}
			if !found {
				return fmt.Errorf("parameter %s is not defined in parameter list", names[x])
			}
			for y := x - 1; y >= 0; y-- {
				if names[y] == names[x] {
					parCollection[x].Flag = 0x80
					break
				}
			}
		}
	} else {
		for x := 0; x < len(names); x++ {
			// search if name is repeated
			repeated := false
			for y := x - 1; y >= 0; y-- {
				if names[y] == names[x] {
					repeated = true
					// parCollection[x].Flag = 0x80
					break
				}
			}
			found := false
			for _, par := range stmt.Pars {
				if strings.ToLower(par.Name) == strings.ToLower(names[x]) {
					if !repeated {
						parCollection = append(parCollection, par)
					}
					found = true
					break
				}
			}
			if !found {
				return fmt.Errorf("parameter %s is not defined in parameter list", names[x])
			}

		}
	}

	stmt.Pars = parCollection
	return nil
}

// Exec execute stmt (INSERT, UPDATE, DELETE, DML, PLSQL) and return driver.Result object
func (stmt *Stmt) Exec(args []driver.Value) (driver.Result, error) {
	if stmt.connection.State != Opened {
		stmt.connection.setBad()
		return nil, driver.ErrBadConn
	}
	tracer := stmt.connection.tracer
	tracer.Printf("Exec:\n%s", stmt.text)
	var result *QueryResult
	var err error
	stmt.arrayBindCount = 0
	if len(args) == 0 {
		result, err = stmt._exec(nil)
	} else {
		namedArgs := make([]driver.NamedValue, len(args))
		for x := 0; x < len(args); x++ {
			namedArgs[x].Value = args[x]
		}
		result, err = stmt._exec(namedArgs)
	}
	if errors.Is(err, network.ErrConnReset) {
		err = stmt.connection.read()
		session := stmt.connection.session
		if session.Summary != nil {
			stmt.cursorID = session.Summary.CursorID
		}
	}
	if err != nil {
		if isBadConn(err) {
			stmt.connection.setBad()
			tracer.Print("Error: ", err)
			return nil, driver.ErrBadConn
		}
		return nil, err
	}
	return result, err
}

func (stmt *Stmt) CheckNamedValue(nv *driver.NamedValue) error {
	if _, ok := nv.Value.(driver.Valuer); ok {
		return driver.ErrSkip
	}
	return nil
}

func (stmt *Stmt) NewParam(name string, val driver.Value, size int, direction ParameterDirection) (*ParameterInfo, error) {
	if stmt.connection.State != Opened {
		stmt.connection.setBad()
		return nil, driver.ErrBadConn
	}
	param := &ParameterInfo{
		Name:      name,
		Direction: direction,
		Value:     val,
	}
	// initialize bfile
	if file, ok := val.(*BFile); ok {
		if !file.isInit() {
			err := file.init(stmt.connection)
			if err != nil {
				return nil, err
			}
		}
	}
	err := param.encodeValue(size, stmt.connection)
	if err != nil {
		return nil, err
	}
	return param, err
}

func (stmt *Stmt) setParam(pos int, par ParameterInfo) {
	if pos >= 0 && pos < len(stmt.Pars) {
		originalPar := stmt.Pars[pos]
		if par.MaxLen > originalPar.MaxLen {
			stmt.reSendParDef = true
		}
		// resend parameter definition when string/[]byte length change from long to short and vice versa
		switch par.DataType {
		case NCHAR:
			if originalPar.DataType == LONG || originalPar.DataType == LongVarChar {
				stmt.reSendParDef = true
			}
		case RAW:
			if originalPar.DataType == LongRaw {
				stmt.reSendParDef = true
			}
		case LONG:
			fallthrough
		case LongVarChar:
			if originalPar.DataType == NCHAR {
				stmt.reSendParDef = true
			}
		case LongVarRaw:
			if originalPar.DataType == RAW {
				stmt.reSendParDef = true
			}
		}
		stmt.Pars[pos] = par
	} else {
		stmt.Pars = append(stmt.Pars, par)
	}
}

// Query_ execute a query command and return oracle dataset object
//
// args is an array of values that corresponding to parameters in sql
func (stmt *Stmt) Query_(namedArgs []driver.NamedValue) (*DataSet, error) {
	if stmt.connection.State != Opened {
		stmt.connection.setBad()
		return nil, driver.ErrBadConn
	}
	tracer := stmt.connection.tracer
	stmt._noOfRowsToFetch = stmt.connection.connOption.PrefetchRows
	stmt._hasMoreRows = true
	useNamedPars := len(namedArgs) > 0
	for x := 0; x < len(namedArgs); x++ {
		par, err := stmt.NewParam(namedArgs[x].Name, namedArgs[x].Value, 0, Input)
		if err != nil {
			return nil, err
		}
		if len(par.Name) == 0 && useNamedPars {
			useNamedPars = false
		}
		stmt.setParam(x, *par)
		tracer.Printf("    %d:\n%v", x, namedArgs[x])
	}

	if useNamedPars {
		err := stmt.useNamedParameters()
		if err != nil {
			return nil, err
		}
	}

	dataSet, err := stmt._query()
	if errors.Is(err, network.ErrConnReset) {
		err = stmt.connection.read()
		session := stmt.connection.session
		if session.Summary != nil {
			stmt.cursorID = session.Summary.CursorID
		}
	}
	if err != nil {
		if isBadConn(err) {
			stmt.connection.setBad()
			tracer.Print("Error: ", err)
			return nil, driver.ErrBadConn
		}
		return nil, err
	}
	return dataSet, nil
}

func (stmt *Stmt) QueryContext(ctx context.Context, namedArgs []driver.NamedValue) (driver.Rows, error) {
	if stmt.connection.State != Opened {
		stmt.connection.setBad()
		return nil, driver.ErrBadConn
	}
	tracer := stmt.connection.tracer
	tracer.Print("Query With Context:", stmt.text)

	done := stmt.connection.session.StartContext(ctx)
	defer stmt.connection.session.EndContext(done)
	return stmt.Query_(namedArgs)
}

// func (stmt *Stmt) reset() {
// 	stmt.reSendParDef = false
// 	stmt.parse = true
// 	stmt.execute = true
// 	stmt.define = false
// 	stmt._hasBLOB = false
// 	stmt._hasLONG = false
// 	stmt.bulkExec = false
// 	// stmt.disableCompression = false
// 	stmt.arrayBindCount = 0
// 	stmt.columns = nil
// }

func (stmt *Stmt) _query() (*DataSet, error) {
	var err error
	var dataSet *DataSet
	stmt.connection.session.ResetBuffer()
	err = stmt.write()
	if err != nil {
		return nil, err
	}
	dataSet = new(DataSet)
	err = stmt.read(dataSet.currentResultSet())
	if err != nil {
		return nil, err
	}
	// deal with multiSet
	if len(stmt.multiSet) > 0 {
		for _, cursor := range stmt.multiSet {
			tempSet, err := cursor.Query()
			if err != nil {
				return nil, err
			}
			dataSet.resultSets = append(dataSet.resultSets, tempSet.resultSets[0])
		}
		// bypass first resultset
		dataSet.index++
	}
	// deal with lobs
	if stmt.parse && (stmt._hasBLOB || stmt._hasLONG) && stmt.connection.connOption.Lob == configurations.INLINE {
		stmt.define = true
		stmt.execute = false
		stmt.parse = false
		stmt.reSendParDef = false
		err = stmt.queryLobPrefetch(stmt.getExeOption(), dataSet.currentResultSet())
		if err != nil {
			return nil, err
		}
	}
	err = stmt.decodePrim(dataSet.currentResultSet())
	if err != nil {
		return nil, err
	}
	stmt.parse = false
	stmt.define = false
	stmt.reSendParDef = false
	return dataSet, err
}

func (stmt *defaultStmt) decodePrim(resultSet *ResultSet) error {
	var err error
	// convert from go-ora primitives to sql primitives
	for rowIndex, row := range resultSet.rows {
		for colIndex, col := range stmt.columns {
			if row == nil {
				continue
			}
			switch val := row[colIndex].(type) {
			case *RefCursor:
				resultSet.rows[rowIndex][colIndex], err = val.Query()
				if err != nil {
					return err
				}
			case Vector:
				// read data from lob
				err = val.load()
				if err != nil {
					return err
				}
				resultSet.rows[rowIndex][colIndex] = val.Data
			case Lob:
				if col.DataType == OCIClobLocator {
					tempString := sql.NullString{String: "", Valid: false}
					err = setLob(reflect.ValueOf(&tempString).Elem(), val)
					if err != nil {
						return err
					}
					if tempString.Valid {
						resultSet.rows[rowIndex][colIndex] = tempString.String
					} else {
						resultSet.rows[rowIndex][colIndex] = nil
					}
				} else {
					var tempByte []byte
					err = setLob(reflect.ValueOf(&tempByte).Elem(), val)
					if err != nil {
						return err
					}
					resultSet.rows[rowIndex][colIndex] = tempByte
				}
			case []ParameterInfo:
				if col.cusType != nil {
					tempObject := reflect.New(col.cusType.typ)
					err = setUDTObject(tempObject.Elem(), col.cusType, val)
					if err != nil {
						return err
					}
					resultSet.rows[rowIndex][colIndex] = tempObject.Elem().Interface()
				}
			}
		}
	}
	return nil
}

// Query execute a query command and return dataset object in form of driver.Rows interface
//
// args is an array of values that corresponding to parameters in sql
func (stmt *Stmt) Query(args []driver.Value) (driver.Rows, error) {
	if stmt.connection.State != Opened {
		stmt.connection.setBad()
		return nil, driver.ErrBadConn
	}
	tracer := stmt.connection.tracer
	tracer.Printf("Query:\n%s", stmt.text)
	var dataSet *DataSet
	var err error
	if len(args) == 0 {
		dataSet, err = stmt.Query_(nil)
	} else {
		namedArgs := make([]driver.NamedValue, len(args))
		for x := 0; x < len(args); x++ {
			namedArgs[x].Value = args[x]
		}
		dataSet, err = stmt.Query_(namedArgs)
	}
	return dataSet, err
}

func (stmt *Stmt) NumInput() int {
	return -1
}

/*
parse = true
execute = true
fetch = true if hasReturn or PLSQL
define = false
*/

// func ReadFromExternalBuffer(buffer []byte) error {
// 	connOption := &network.ConnectionOption{
// 		Port:                  0,
// 		TransportConnectTo:    0,
// 		SSLVersion:            "",
// 		WalletDict:            "",
// 		TransportDataUnitSize: 0,
// 		SessionDataUnitSize:   0,
// 		Protocol:              "",
// 		Host:                  "",
// 		UserID:                "",
// 		SID:                   "",
// 		ServiceName:           "",
// 		InstanceName:          "",
// 		DomainName:            "",
// 		DBName:                "",
// 		ClientData:            network.ClientData{},
// 		Tracer:                trace.NilTracer(),
// 		SNOConfig:             nil,
// 	}
// 	conn := &Connection {
// 		State:             Opened,
// 		LogonMode:         0,
// 		SessionProperties: nil,
// 		connOption: connOption,
// 	}
// 	conn.session = &network.Session{
// 		Context:         nil,
// 		Summary:         nil,
// 		UseBigClrChunks: true,
// 		ClrChunkSize:    0x40,
// 	}
// 	conn.strConv = converters.NewStringConverter(871)
// 	conn.session.StrConv = conn.strConv
// 	conn.session.FillInBuffer(buffer)
// 	conn.session.TTCVersion = 11
// 	stmt := &Stmt{
// 		defaultStmt:  defaultStmt{
// 			connection: conn,
// 			scnForSnapshot: make([]int, 2),
// 		},
// 		reSendParDef: false,
// 		parse:        true,
// 		execute:      true,
// 		define:       false,
// 	}
// 	dataSet := new(DataSet)
// 	err := stmt.read(dataSet)
// 	return err
// }
