package gorm

import (
	
	
	
	
)

type Stmt struct {
	*sql.Stmt
	Transaction bool
	prepared    chan struct{}
	prepareErr  error
}

type PreparedStmtDB struct {
	Stmts       map[string]*Stmt
	PreparedSQL []string
	Mux         *sync.RWMutex
	ConnPool
}

func ( ConnPool) *PreparedStmtDB {
	return &PreparedStmtDB{
		ConnPool:    ,
		Stmts:       make(map[string]*Stmt),
		Mux:         &sync.RWMutex{},
		PreparedSQL: make([]string, 0, 100),
	}
}

func ( *PreparedStmtDB) () (*sql.DB, error) {
	if ,  := .ConnPool.(*sql.DB);  {
		return , nil
	}

	if ,  := .ConnPool.(GetDBConnector);  &&  != nil {
		return .GetDBConn()
	}

	return nil, ErrInvalidDB
}

func ( *PreparedStmtDB) () {
	.Mux.Lock()
	defer .Mux.Unlock()

	for ,  := range .PreparedSQL {
		if ,  := .Stmts[];  {
			delete(.Stmts, )
			go .Close()
		}
	}
}

func ( *PreparedStmtDB) () {
	.Mux.Lock()
	defer .Mux.Unlock()

	for ,  := range .Stmts {
		go .Close()
	}
	.PreparedSQL = make([]string, 0, 100)
	.Stmts = make(map[string]*Stmt)
}

func ( *PreparedStmtDB) ( context.Context,  ConnPool,  bool,  string) (Stmt, error) {
	.Mux.RLock()
	if ,  := .Stmts[];  && (!.Transaction || ) {
		.Mux.RUnlock()
		// wait for other goroutines prepared
		<-.prepared
		if .prepareErr != nil {
			return Stmt{}, .prepareErr
		}

		return *, nil
	}
	.Mux.RUnlock()

	.Mux.Lock()
	// double check
	if ,  := .Stmts[];  && (!.Transaction || ) {
		.Mux.Unlock()
		// wait for other goroutines prepared
		<-.prepared
		if .prepareErr != nil {
			return Stmt{}, .prepareErr
		}

		return *, nil
	}

	// cache preparing stmt first
	 := Stmt{Transaction: , prepared: make(chan struct{})}
	.Stmts[] = &
	.Mux.Unlock()

	// prepare completed
	defer close(.prepared)

	// Reason why cannot lock conn.PrepareContext
	// suppose the maxopen is 1, g1 is creating record and g2 is querying record.
	// 1. g1 begin tx, g1 is requeue because of waiting for the system call, now `db.ConnPool` db.numOpen == 1.
	// 2. g2 select lock `conn.PrepareContext(ctx, query)`, now db.numOpen == db.maxOpen , wait for release.
	// 3. g1 tx exec insert, wait for unlock `conn.PrepareContext(ctx, query)` to finish tx and release.
	,  := .PrepareContext(, )
	if  != nil {
		.prepareErr = 
		.Mux.Lock()
		delete(.Stmts, )
		.Mux.Unlock()
		return Stmt{}, 
	}

	.Mux.Lock()
	.Stmt = 
	.PreparedSQL = append(.PreparedSQL, )
	.Mux.Unlock()

	return , nil
}

func ( *PreparedStmtDB) ( context.Context,  *sql.TxOptions) (ConnPool, error) {
	if ,  := .ConnPool.(TxBeginner);  {
		,  := .BeginTx(, )
		return &PreparedStmtTX{PreparedStmtDB: , Tx: }, 
	}

	,  := .ConnPool.(ConnPoolBeginner)
	if ! {
		return nil, ErrInvalidTransaction
	}

	,  := .BeginTx(, )
	if  != nil {
		return nil, 
	}
	if ,  := .(Tx);  {
		return &PreparedStmtTX{PreparedStmtDB: , Tx: }, nil
	}
	return nil, ErrInvalidTransaction
}

func ( *PreparedStmtDB) ( context.Context,  string,  ...interface{}) ( sql.Result,  error) {
	,  := .prepare(, .ConnPool, false, )
	if  == nil {
		,  = .ExecContext(, ...)
		if  != nil {
			.Mux.Lock()
			defer .Mux.Unlock()
			go .Close()
			delete(.Stmts, )
		}
	}
	return , 
}

func ( *PreparedStmtDB) ( context.Context,  string,  ...interface{}) ( *sql.Rows,  error) {
	,  := .prepare(, .ConnPool, false, )
	if  == nil {
		,  = .QueryContext(, ...)
		if  != nil {
			.Mux.Lock()
			defer .Mux.Unlock()

			go .Close()
			delete(.Stmts, )
		}
	}
	return , 
}

func ( *PreparedStmtDB) ( context.Context,  string,  ...interface{}) *sql.Row {
	,  := .prepare(, .ConnPool, false, )
	if  == nil {
		return .QueryRowContext(, ...)
	}
	return &sql.Row{}
}

type PreparedStmtTX struct {
	Tx
	PreparedStmtDB *PreparedStmtDB
}

func ( *PreparedStmtTX) () (*sql.DB, error) {
	return .PreparedStmtDB.GetDBConn()
}

func ( *PreparedStmtTX) () error {
	if .Tx != nil && !reflect.ValueOf(.Tx).IsNil() {
		return .Tx.Commit()
	}
	return ErrInvalidTransaction
}

func ( *PreparedStmtTX) () error {
	if .Tx != nil && !reflect.ValueOf(.Tx).IsNil() {
		return .Tx.Rollback()
	}
	return ErrInvalidTransaction
}

func ( *PreparedStmtTX) ( context.Context,  string,  ...interface{}) ( sql.Result,  error) {
	,  := .PreparedStmtDB.prepare(, .Tx, true, )
	if  == nil {
		,  = .Tx.StmtContext(, .Stmt).ExecContext(, ...)
		if  != nil {
			.PreparedStmtDB.Mux.Lock()
			defer .PreparedStmtDB.Mux.Unlock()

			go .Close()
			delete(.PreparedStmtDB.Stmts, )
		}
	}
	return , 
}

func ( *PreparedStmtTX) ( context.Context,  string,  ...interface{}) ( *sql.Rows,  error) {
	,  := .PreparedStmtDB.prepare(, .Tx, true, )
	if  == nil {
		,  = .Tx.StmtContext(, .Stmt).QueryContext(, ...)
		if  != nil {
			.PreparedStmtDB.Mux.Lock()
			defer .PreparedStmtDB.Mux.Unlock()

			go .Close()
			delete(.PreparedStmtDB.Stmts, )
		}
	}
	return , 
}

func ( *PreparedStmtTX) ( context.Context,  string,  ...interface{}) *sql.Row {
	,  := .PreparedStmtDB.prepare(, .Tx, true, )
	if  == nil {
		return .Tx.StmtContext(, .Stmt).QueryRowContext(, ...)
	}
	return &sql.Row{}
}