package orm

import (
	"fmt"
	"reflect"

	"github.com/go-pg/pg/internal/iszero"
	"github.com/go-pg/pg/types"
)

const (
	PrimaryKeyFlag = uint8(1) << iota
	ForeignKeyFlag
	NotNullFlag
	UniqueFlag
	ArrayFlag
	customTypeFlag
)

type Field struct {
	Field reflect.StructField
	Type  reflect.Type

	GoName   string  // struct field name, e.g. Id
	SQLName  string  // SQL name, .e.g. id
	Column   types.Q // escaped SQL name, e.g. "id"
	SQLType  string
	Index    []int
	Default  types.Q
	OnDelete string
	OnUpdate string

	flags uint8

	append types.AppenderFunc
	scan   types.ScannerFunc

	isZero iszero.Func
}

func indexEqual(ind1, ind2 []int) bool {
	if len(ind1) != len(ind2) {
		return false
	}
	for i, ind := range ind1 {
		if ind != ind2[i] {
			return false
		}
	}
	return true
}

func (f *Field) Copy() *Field {
	cp := *f
	cp.Index = cp.Index[:len(f.Index):len(f.Index)]
	return &cp
}

func (f *Field) SetFlag(flag uint8) {
	f.flags |= flag
}

func (f *Field) HasFlag(flag uint8) bool {
	return f.flags&flag != 0
}

func (f *Field) Value(strct reflect.Value) reflect.Value {
	return fieldByIndex(strct, f.Index)
}

func (f *Field) IsZeroValue(strct reflect.Value) bool {
	return f.isZero(f.Value(strct))
}

func (f *Field) OmitZero() bool {
	return !f.HasFlag(NotNullFlag)
}

func (f *Field) AppendValue(b []byte, strct reflect.Value, quote int) []byte {
	fv := f.Value(strct)
	if f.OmitZero() && f.isZero(fv) {
		return types.AppendNull(b, quote)
	}
	if f.append == nil {
		panic(fmt.Errorf("pg: AppendValue(unsupported %s)", fv.Type()))
	}
	return f.append(b, fv, quote)
}

func (f *Field) ScanValue(strct reflect.Value, rd types.Reader, n int) error {
	fv := fieldByIndex(strct, f.Index)
	if f.scan == nil {
		return fmt.Errorf("pg: ScanValue(unsupported %s)", fv.Type())
	}
	return f.scan(fv, rd, n)
}

type Method struct {
	Index int

	flags int8

	appender func([]byte, reflect.Value, int) []byte
}

func (m *Method) Has(flag int8) bool {
	return m.flags&flag != 0
}

func (m *Method) Value(strct reflect.Value) reflect.Value {
	return strct.Method(m.Index).Call(nil)[0]
}

func (m *Method) AppendValue(dst []byte, strct reflect.Value, quote int) []byte {
	mv := m.Value(strct)
	return m.appender(dst, mv, quote)
}