func toSlash(path string) string {
	if separator == '/' {
		return path
	}
	return strings.Replace(path, string(separator), "/", -1)
}
toSlash is an identical implementation of filepath.ToSlash. Is only
implemented so we can change the separator on runtime for testing purposes,
since filepath.Separator is a constant.
Parts of the code using filepath.ToSlash that need cross-platform tests
should use this instead.
package generator
import (
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/token"
	"go/types"
	"path/filepath"
	"reflect"
	"strings"
	parseutil "gopkg.in/src-d/go-parse-utils.v1"
)
const (
	BaseModel = "gopkg.in/src-d/go-kallax.v1.Model"
	URL = "url.URL"
)
type Processor struct {
	Path string
	Ignore map[string]struct{}
	Package *types.Package
	silent  bool
}
func NewProcessor(path string, ignore []string) *Processor {
	i := make(map[string]struct{})
	for _, file := range ignore {
		i[file] = struct{}{}
	}
	return &Processor{
		Path:   path,
		Ignore: i,
	}
}
func (p *Processor) Silent() {
	p.silent = true
}
func (p *Processor) write(msg string, args ...interface{}) {
	if !p.silent {
		fmt.Println(fmt.Sprintf(msg, args...))
	}
}
func (p *Processor) Do() (*Package, error) {
	files, err := p.getSourceFiles()
	if err != nil {
		return nil, err
	}
	p.Package, err = p.parseSourceFiles(files)
	if err != nil {
		return nil, err
	}
	return p.processPackage()
}
func (p *Processor) getSourceFiles() ([]string, error) {
	pkg, err := build.Default.ImportDir(p.Path, 0)
	if err != nil {
		return nil, fmt.Errorf("kallax: cannot process directory %s: %s", p.Path, err)
	}
	var files []string
	files = append(files, pkg.GoFiles...)
	files = append(files, pkg.CgoFiles...)
	if len(files) == 0 {
		return nil, fmt.Errorf("kallax: %s: no buildable Go files", p.Path)
	}
	return joinDirectory(p.Path, p.removeIgnoredFiles(files)), nil
}
func (p *Processor) removeIgnoredFiles(filenames []string) []string {
	var output []string
	for _, filename := range filenames {
		if _, ok := p.Ignore[filename]; ok {
			continue
		}
		output = append(output, filename)
	}
	return output
}
func (p *Processor) parseSourceFiles(filenames []string) (*types.Package, error) {
	var files []*ast.File
	fs := token.NewFileSet()
	for _, filename := range filenames {
		file, err := parser.ParseFile(fs, filename, nil, 0)
		if err != nil {
			return nil, fmt.Errorf("kallax: parsing package: %s: %s", filename, err)
		}
		files = append(files, file)
	}
	config := types.Config{
		FakeImportC: true,
		Error:       func(error) {},
		Importer:    parseutil.NewImporter(),
	}
	return config.Check(p.Path, fs, files, new(types.Info))
}
func (p *Processor) processPackage() (*Package, error) {
	pkg := NewPackage(p.Package)
	var ctors []*types.Func
	p.write("Package: %s", pkg.Name)
	s := p.Package.Scope()
	var models []*Model
	for _, name := range s.Names() {
		obj := s.Lookup(name)
		switch t := obj.Type().(type) {
		case *types.Signature:
			if strings.HasPrefix(name, "new") {
				ctors = append(ctors, obj.(*types.Func))
			}
		case *types.Named:
			if str, ok := t.Underlying().(*types.Struct); ok {
				if m, err := p.processModel(name, str, t); err != nil {
					return nil, err
				} else if m != nil {
					p.write("Model: %s", m)
					if err := m.Validate(); err != nil {
						return nil, err
					}
					models = append(models, m)
					m.Node = t
					m.Package = p.Package
				}
			}
		}
	}
	pkg.SetModels(models)
	if err := pkg.addMissingRelationships(); err != nil {
		return nil, err
	}
	for _, ctor := range ctors {
		p.tryMatchConstructor(pkg, ctor)
	}
	return pkg, nil
}
func (p *Processor) tryMatchConstructor(pkg *Package, fun *types.Func) {
	if !strings.HasPrefix(fun.Name(), "new") {
		return
	}
	if m := pkg.FindModel(fun.Name()[3:]); m != nil {
		sig := fun.Type().(*types.Signature)
		if sig.Recv() != nil {
			return
		}
		res := sig.Results()
		if res.Len() > 0 {
			for i := 0; i < res.Len(); i++ {
				if isTypeOrPtrTo(res.At(i).Type(), m.Node) {
					m.CtorFunc = fun
					return
				}
			}
		}
	}
}
func (p *Processor) processModel(name string, s *types.Struct, t *types.Named) (*Model, error) {
	m := NewModel(name)
	m.Events = p.findEvents(t)
	var base int
	var fields []*Field
	if base, fields = p.processFields(s, nil, true); base == -1 {
		return nil, nil
	}
	p.processBaseField(m, fields[base])
	if err := m.SetFields(fields); err != nil {
		return nil, err
	}
	return m, nil
}
var allEvents = Events{
	BeforeInsert,
	AfterInsert,
	BeforeUpdate,
	AfterUpdate,
	BeforeSave,
	AfterSave,
	BeforeDelete,
	AfterDelete,
}
func (p *Processor) findEvents(node *types.Named) []Event {
	var events []Event
	for _, e := range allEvents {
		if p.isEventPresent(node, e) {
			events = append(events, e)
		}
	}
	return events
}
func (p *Processor) isEventPresent(node *types.Named, e Event) bool {
	signature := getMethodSignature(p.Package, types.NewPointer(node), string(e))
	return signatureMatches(signature, nil, typeCheckers{isBuiltinError})
}
func (p *Processor) processFields(s *types.Struct, done []*types.Struct, root bool) (base int, fields []*Field) {
	base = -1
	for i := 0; i < s.NumFields(); i++ {
		f := s.Field(i)
		if !f.Exported() || isIgnoredField(s, i) {
			continue
		}
		field := NewField(
			f.Name(),
			typeName(f.Type().Underlying()),
			reflect.StructTag(s.Tag(i)),
		)
		field.Node = f
		if typeName(f.Type()) == BaseModel {
			base = i
			field.Type = BaseModel
		}
		if f.Anonymous() {
			field.IsEmbedded = true
		}
		p.processField(field, f.Type(), done, root)
		if field.Kind == Invalid {
			p.write("WARNING: arrays of relationships are not supported. Field %s will be ignored.", field.Name)
			continue
		}
		fields = append(fields, field)
	}
	return base, fields
}
func (p *Processor) processField(field *Field, typ types.Type, done []*types.Struct, root bool) {
	switch typ := typ.(type) {
	case *types.Basic:
		field.Type = typ.String()
		field.Kind = Basic
	case *types.Pointer:
		field.IsPtr = true
		p.processField(field, typ.Elem(), done, root)
	case *types.Named:
		if field.Type == BaseModel {
			p.processField(field, typ.Underlying(), done, root)
			return
		}
		if isModel(typ, true) && root {
			field.Kind = Relationship
			field.Type = typ.String()
			return
		}
		if !field.IsEmbedded && isSQLType(p.Package, types.NewPointer(typ)) {
			field.Kind = Interface
			return
		}
		if t, ok := specialTypes[typeName(typ)]; ok {
			field.Type = t
			return
		}
		p.processField(field, typ.Underlying(), done, root)
		field.IsAlias = !field.IsJSON
	case *types.Array:
		var underlying Field
		p.processField(&underlying, typ.Elem(), done, root)
		if underlying.Kind == Relationship {
			field.Kind = Invalid
			return
		}
		if underlying.Kind != Basic {
			field.IsJSON = true
		}
		field.Kind = Array
		field.SetFields(underlying.Fields)
	case *types.Slice:
		var underlying Field
		p.processField(&underlying, typ.Elem(), done, root)
		if underlying.Kind == Relationship {
			field.Kind = Relationship
			return
		}
		if underlying.Kind != Basic {
			field.IsJSON = true
		}
		field.Kind = Slice
		field.SetFields(underlying.Fields)
	case *types.Map:
		field.Kind = Map
		field.IsJSON = true
	case *types.Interface:
		field.Kind = Interface
		field.IsJSON = true
	case *types.Struct:
		field.Kind = Struct
		field.IsJSON = true
		d := false
		for _, v := range done {
			if v == typ {
				d = true
				break
			}
		}
		if !d {
			_, subfs := p.processFields(typ, append(done, typ), false)
			field.SetFields(subfs)
		}
	default:
		p.write("WARNING: Ignored field %s of type %s.", field.Name, field.Type)
	}
}
func isSQLType(pkg *types.Package, typ types.Type) bool {
	scan := getMethodSignature(pkg, typ, "Scan")
	if !signatureMatches(scan, typeCheckers{isEmptyInterface}, typeCheckers{isBuiltinError}) {
		return false
	}
	value := getMethodSignature(pkg, typ, "Value")
	if !signatureMatches(value, nil, typeCheckers{isDriverValue, isBuiltinError}) {
		return false
	}
	return true
}
func signatureMatches(s *types.Signature, params typeCheckers, results typeCheckers) bool {
	return s != nil &&
		s.Params().Len() == len(params) &&
		s.Results().Len() == len(results) &&
		params.check(s.Params()) &&
		results.check(s.Results())
}
type typeCheckers []typeChecker
func (c typeCheckers) check(tuple *types.Tuple) bool {
	for i, checker := range c {
		if !checker(tuple.At(i).Type()) {
			return false
		}
	}
	return true
}
type typeChecker func(types.Type) bool
func getMethodSignature(pkg *types.Package, typ types.Type, name string) *types.Signature {
	ms := types.NewMethodSet(typ)
	method := ms.Lookup(pkg, name)
	if method == nil {
		return nil
	}
	return method.Obj().(*types.Func).Type().(*types.Signature)
}
func isEmptyInterface(typ types.Type) bool {
	switch typ := typ.(type) {
	case *types.Interface:
		return typ.NumMethods() == 0
	}
	return false
}
func isDriverValue(typ types.Type) bool {
	switch typ := typ.(type) {
	case *types.Named:
		return typ.String() == "database/sql/driver.Value"
	}
	return false
}
func isModel(typ types.Type, dive bool) bool {
	switch typ := typ.(type) {
	case *types.Named:
		if typeName(typ) == BaseModel {
			return true
		}
		return isModel(typ.Underlying(), true && dive)
	case *types.Pointer:
		return isModel(typ.Elem(), true && dive)
	case *types.Struct:
		if !dive {
			return false
		}
		for i := 0; i < typ.NumFields(); i++ {
			if isModel(typ.Field(i).Type(), false) {
				return true
			}
		}
	}
	return false
}
func (p *Processor) processBaseField(m *Model, f *Field) {
	m.Table = f.Tag.Get("table")
	if m.Table == "" {
		m.Table = toLowerSnakeCase(m.Name)
	}
}
func joinDirectory(directory string, files []string) []string {
	result := make([]string, len(files))
	for i, file := range files {
		result[i] = filepath.Join(directory, file)
	}
	return result
}
func typeName(typ types.Type) string {
	return removeGoPath(typ.String())
}
var separator = filepath.Separator