package sq

import (
	"context"
	"time"
)

type PrePingMeta struct {
	Context context.Context
}

type PreTxBeginMeta struct {
	Context            context.Context
	ConstructorContext context.Context
}

type PreTxCommitMeta struct {
	ConstructorContext context.Context
}

type PreTxRollbackMeta struct {
	ConstructorContext context.Context
}

type PreQueryMeta struct {
	Context                       context.Context
	TransactionConstructorContext context.Context
}

type PreExecMeta struct {
	Context                       context.Context
	TransactionConstructorContext context.Context
}

type PostPingMeta struct {
	Context context.Context
	Init    time.Time
	Start   time.Time
	End     time.Time
}

type PostTxBeginMeta struct {
	Context context.Context
	Init    time.Time
	Start   time.Time
	End     time.Time
}

type PostTxCommitMeta struct {
	ConstructorContext context.Context
	Init               time.Time
	Start              time.Time
	End                time.Time
	ExecCounter        int
	QueryCounter       int
}

type PostTxRollbackMeta struct {
	ConstructorContext context.Context
	Init               time.Time
	Start              time.Time
	End                time.Time
	ExecCounter        int
	QueryCounter       int
}

type PostQueryMeta struct {
	Context                       context.Context
	TransactionConstructorContext context.Context
	Init                          time.Time
	Start                         time.Time
	End                           time.Time
}

type PostExecMeta struct {
	Context                       context.Context
	TransactionConstructorContext context.Context
	Init                          time.Time
	Start                         time.Time
	End                           time.Time
}

type Listener interface {
	PrePing(ctx context.Context, meta PrePingMeta) error
	PreTxBegin(ctx context.Context, txid uint16, meta PreTxBeginMeta) error
	PreTxCommit(txid uint16, meta PreTxCommitMeta) error
	PreTxRollback(txid uint16, meta PreTxRollbackMeta) error
	PreQuery(ctx context.Context, txID *uint16, sql *string, params *PP, meta PreQueryMeta) error
	PreExec(ctx context.Context, txID *uint16, sql *string, params *PP, meta PreExecMeta) error

	PostPing(result error, meta PostPingMeta)
	PostTxBegin(txid uint16, result error, meta PostTxBeginMeta)
	PostTxCommit(txid uint16, result error, meta PostTxCommitMeta)
	PostTxRollback(txid uint16, result error, meta PostTxRollbackMeta)
	PostQuery(txID *uint16, sqlOriginal string, sqlReal string, params PP, result error, meta PostQueryMeta)
	PostExec(txID *uint16, sqlOriginal string, sqlReal string, params PP, result error, meta PostExecMeta)
}

type genListener struct {
	prePing        func(ctx context.Context, meta PrePingMeta) error
	preTxBegin     func(ctx context.Context, txid uint16, meta PreTxBeginMeta) error
	preTxCommit    func(txid uint16, meta PreTxCommitMeta) error
	preTxRollback  func(txid uint16, meta PreTxRollbackMeta) error
	preQuery       func(ctx context.Context, txID *uint16, sql *string, params *PP, meta PreQueryMeta) error
	preExec        func(ctx context.Context, txID *uint16, sql *string, params *PP, meta PreExecMeta) error
	postPing       func(result error, meta PostPingMeta)
	postTxBegin    func(txid uint16, result error, meta PostTxBeginMeta)
	postTxCommit   func(txid uint16, result error, meta PostTxCommitMeta)
	postTxRollback func(txid uint16, result error, meta PostTxRollbackMeta)
	postQuery      func(txID *uint16, sqlOriginal string, sqlReal string, params PP, result error, meta PostQueryMeta)
	postExec       func(txID *uint16, sqlOriginal string, sqlReal string, params PP, result error, meta PostExecMeta)
}

func (g genListener) PrePing(ctx context.Context, meta PrePingMeta) error {
	if g.prePing != nil {
		return g.prePing(ctx, meta)
	} else {
		return nil
	}
}

func (g genListener) PreTxBegin(ctx context.Context, txid uint16, meta PreTxBeginMeta) error {
	if g.preTxBegin != nil {
		return g.preTxBegin(ctx, txid, meta)
	} else {
		return nil
	}
}

func (g genListener) PreTxCommit(txid uint16, meta PreTxCommitMeta) error {
	if g.preTxCommit != nil {
		return g.preTxCommit(txid, meta)
	} else {
		return nil
	}
}

func (g genListener) PreTxRollback(txid uint16, meta PreTxRollbackMeta) error {
	if g.preTxRollback != nil {
		return g.preTxRollback(txid, meta)
	} else {
		return nil
	}
}

func (g genListener) PreQuery(ctx context.Context, txID *uint16, sql *string, params *PP, meta PreQueryMeta) error {
	if g.preQuery != nil {
		return g.preQuery(ctx, txID, sql, params, meta)
	} else {
		return nil
	}
}

func (g genListener) PreExec(ctx context.Context, txID *uint16, sql *string, params *PP, meta PreExecMeta) error {
	if g.preExec != nil {
		return g.preExec(ctx, txID, sql, params, meta)
	} else {
		return nil
	}
}

func (g genListener) PostPing(result error, meta PostPingMeta) {
	if g.postPing != nil {
		g.postPing(result, meta)
	}
}

func (g genListener) PostTxBegin(txid uint16, result error, meta PostTxBeginMeta) {
	if g.postTxBegin != nil {
		g.postTxBegin(txid, result, meta)
	}
}

func (g genListener) PostTxCommit(txid uint16, result error, meta PostTxCommitMeta) {
	if g.postTxCommit != nil {
		g.postTxCommit(txid, result, meta)
	}
}

func (g genListener) PostTxRollback(txid uint16, result error, meta PostTxRollbackMeta) {
	if g.postTxRollback != nil {
		g.postTxRollback(txid, result, meta)
	}
}

func (g genListener) PostQuery(txID *uint16, sqlOriginal string, sqlReal string, params PP, result error, meta PostQueryMeta) {
	if g.postQuery != nil {
		g.postQuery(txID, sqlOriginal, sqlReal, params, result, meta)
	}
}

func (g genListener) PostExec(txID *uint16, sqlOriginal string, sqlReal string, params PP, result error, meta PostExecMeta) {
	if g.postExec != nil {
		g.postExec(txID, sqlOriginal, sqlReal, params, result, meta)
	}
}

func NewPrePingListener(f func(ctx context.Context, meta PrePingMeta) error) Listener {
	return genListener{prePing: f}
}

func NewPreTxBeginListener(f func(ctx context.Context, txid uint16, meta PreTxBeginMeta) error) Listener {
	return genListener{preTxBegin: f}
}

func NewPreTxCommitListener(f func(txid uint16, meta PreTxCommitMeta) error) Listener {
	return genListener{preTxCommit: f}
}

func NewPreTxRollbackListener(f func(txid uint16, meta PreTxRollbackMeta) error) Listener {
	return genListener{preTxRollback: f}
}

func NewPreQueryListener(f func(ctx context.Context, txID *uint16, sql *string, params *PP, meta PreQueryMeta) error) Listener {
	return genListener{preQuery: f}
}

func NewPreExecListener(f func(ctx context.Context, txID *uint16, sql *string, params *PP, meta PreExecMeta) error) Listener {
	return genListener{preExec: f}
}

func NewPreListener(f func(ctx context.Context, cmdtype string, txID *uint16, sql *string, params *PP) error) Listener {
	return genListener{
		preExec: func(ctx context.Context, txID *uint16, sql *string, params *PP, meta PreExecMeta) error {
			return f(ctx, "EXEC", txID, sql, params)
		},
		preQuery: func(ctx context.Context, txID *uint16, sql *string, params *PP, meta PreQueryMeta) error {
			return f(ctx, "QUERY", txID, sql, params)
		},
	}
}

func NewPostPingListener(f func(result error, meta PostPingMeta)) Listener {
	return genListener{postPing: f}
}

func NewPostTxBeginListener(f func(txid uint16, result error, meta PostTxBeginMeta)) Listener {
	return genListener{postTxBegin: f}
}

func NewPostTxCommitListener(f func(txid uint16, result error, meta PostTxCommitMeta)) Listener {
	return genListener{postTxCommit: f}
}

func NewPostTxRollbackListener(f func(txid uint16, result error, meta PostTxRollbackMeta)) Listener {
	return genListener{postTxRollback: f}
}

func NewPostQueryListener(f func(txID *uint16, sqlOriginal string, sqlReal string, params PP, result error, meta PostQueryMeta)) Listener {
	return genListener{postQuery: f}
}

func NewPostExecListener(f func(txID *uint16, sqlOriginal string, sqlReal string, params PP, result error, meta PostExecMeta)) Listener {
	return genListener{postExec: f}
}

func NewPostListener(f func(cmdtype string, txID *uint16, sqlOriginal string, sqlReal string, result error, params PP)) Listener {
	return genListener{
		postExec: func(txID *uint16, sqlOriginal string, sqlReal string, params PP, result error, meta PostExecMeta) {
			f("EXEC", txID, sqlOriginal, sqlReal, result, params)
		},
		postQuery: func(txID *uint16, sqlOriginal string, sqlReal string, params PP, result error, meta PostQueryMeta) {
			f("QUERY", txID, sqlOriginal, sqlReal, result, params)
		},
	}
}