// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package schema

import (
	
	
	
	
	
)

var errInvalidPath = errors.New("schema: invalid path")

// newCache returns a new cache.
func newCache() *cache {
	 := cache{
		m:       make(map[reflect.Type]*structInfo),
		regconv: make(map[reflect.Type]Converter),
		tag:     "schema",
	}
	return &
}

// cache caches meta-data about a struct.
type cache struct {
	l       sync.RWMutex
	m       map[reflect.Type]*structInfo
	regconv map[reflect.Type]Converter
	tag     string
}

// registerConverter registers a converter function for a custom type.
func ( *cache) ( interface{},  Converter) {
	.regconv[reflect.TypeOf()] = 
}

// parsePath parses a path in dotted notation verifying that it is a valid
// path to a struct field.
//
// It returns "path parts" which contain indices to fields to be used by
// reflect.Value.FieldByString(). Multiple parts are required for slices of
// structs.
func ( *cache) ( string,  reflect.Type) ([]pathPart, error) {
	var  *structInfo
	var  *fieldInfo
	var  int64
	var  error
	 := make([]pathPart, 0)
	 := make([]string, 0)
	 := strings.Split(, ".")
	for  := 0;  < len(); ++ {
		if .Kind() != reflect.Struct {
			return nil, errInvalidPath
		}
		if  = .get();  == nil {
			return nil, errInvalidPath
		}
		if  = .get([]);  == nil {
			return nil, errInvalidPath
		}
		// Valid field. Append index.
		 = append(, .name)
		if .isSliceOfStructs && (!.unmarshalerInfo.IsValid || (.unmarshalerInfo.IsValid && .unmarshalerInfo.IsSliceElement)) {
			// Parse a special case: slices of structs.
			// i+1 must be the slice index.
			//
			// Now that struct can implements TextUnmarshaler interface,
			// we don't need to force the struct's fields to appear in the path.
			// So checking i+2 is not necessary anymore.
			++
			if +1 > len() {
				return nil, errInvalidPath
			}
			if ,  = strconv.ParseInt([], 10, 0);  != nil {
				return nil, errInvalidPath
			}
			 = append(, pathPart{
				path:  ,
				field: ,
				index: int(),
			})
			 = make([]string, 0)

			// Get the next struct type, dropping ptrs.
			if .typ.Kind() == reflect.Ptr {
				 = .typ.Elem()
			} else {
				 = .typ
			}
			if .Kind() == reflect.Slice {
				 = .Elem()
				if .Kind() == reflect.Ptr {
					 = .Elem()
				}
			}
		} else if .typ.Kind() == reflect.Ptr {
			 = .typ.Elem()
		} else {
			 = .typ
		}
	}
	// Add the remaining.
	 = append(, pathPart{
		path:  ,
		field: ,
		index: -1,
	})
	return , nil
}

// get returns a cached structInfo, creating it if necessary.
func ( *cache) ( reflect.Type) *structInfo {
	.l.RLock()
	 := .m[]
	.l.RUnlock()
	if  == nil {
		 = .create(, "")
		.l.Lock()
		.m[] = 
		.l.Unlock()
	}
	return 
}

// create creates a structInfo with meta-data about a struct.
func ( *cache) ( reflect.Type,  string) *structInfo {
	 := &structInfo{}
	var  []*structInfo
	for  := 0;  < .NumField(); ++ {
		if  := .createField(.Field(), );  != nil {
			.fields = append(.fields, )
			if  := indirectType(.typ); .Kind() == reflect.Struct && .isAnonymous {
				 = append(, .(, .canonicalAlias))
			}
		}
	}
	for ,  := range  {
		 := []*structInfo{}
		 = append(, [:]...)
		 = append(, [+1:]...)
		for ,  := range .fields {
			if !containsAlias(, .alias) {
				.fields = append(.fields, )
			}
		}
	}
	return 
}

// createField creates a fieldInfo for the given field.
func ( *cache) ( reflect.StructField,  string) *fieldInfo {
	,  := fieldAlias(, .tag)
	if  == "-" {
		// Ignore this field.
		return nil
	}
	 := 
	if  != "" {
		 =  + "." + 
	}
	// Check if the type is supported and don't cache it if not.
	// First let's get the basic type.
	,  := false, false
	 := .Type
	 := isTextUnmarshaler(reflect.Zero())
	if .Kind() == reflect.Ptr {
		 = .Elem()
	}
	if  = .Kind() == reflect.Slice;  {
		 = .Elem()
		if .Kind() == reflect.Ptr {
			 = .Elem()
		}
	}
	if .Kind() == reflect.Array {
		 = .Elem()
		if .Kind() == reflect.Ptr {
			 = .Elem()
		}
	}
	if  = .Kind() == reflect.Struct; ! {
		if .converter() == nil && builtinConverters[.Kind()] == nil {
			// Type is not supported.
			return nil
		}
	}

	return &fieldInfo{
		typ:              .Type,
		name:             .Name,
		alias:            ,
		canonicalAlias:   ,
		unmarshalerInfo:  ,
		isSliceOfStructs:  && ,
		isAnonymous:      .Anonymous,
		isRequired:       .Contains("required"),
	}
}

// converter returns the converter for a type.
func ( *cache) ( reflect.Type) Converter {
	return .regconv[]
}

// ----------------------------------------------------------------------------

type structInfo struct {
	fields []*fieldInfo
}

func ( *structInfo) ( string) *fieldInfo {
	for ,  := range .fields {
		if strings.EqualFold(.alias, ) {
			return 
		}
	}
	return nil
}

func containsAlias( []*structInfo,  string) bool {
	for ,  := range  {
		if .get() != nil {
			return true
		}
	}
	return false
}

type fieldInfo struct {
	typ reflect.Type
	// name is the field name in the struct.
	name  string
	alias string
	// canonicalAlias is almost the same as the alias, but is prefixed with
	// an embedded struct field alias in dotted notation if this field is
	// promoted from the struct.
	// For instance, if the alias is "N" and this field is an embedded field
	// in a struct "X", canonicalAlias will be "X.N".
	canonicalAlias string
	// unmarshalerInfo contains information regarding the
	// encoding.TextUnmarshaler implementation of the field type.
	unmarshalerInfo unmarshaler
	// isSliceOfStructs indicates if the field type is a slice of structs.
	isSliceOfStructs bool
	// isAnonymous indicates whether the field is embedded in the struct.
	isAnonymous bool
	isRequired  bool
}

func ( *fieldInfo) ( string) []string {
	if .alias == .canonicalAlias {
		return []string{ + .alias}
	}
	return []string{ + .alias,  + .canonicalAlias}
}

type pathPart struct {
	field *fieldInfo
	path  []string // path to the field: walks structs using field names.
	index int      // struct index in slices of structs.
}

// ----------------------------------------------------------------------------

func indirectType( reflect.Type) reflect.Type {
	if .Kind() == reflect.Ptr {
		return .Elem()
	}
	return 
}

// fieldAlias parses a field tag to get a field alias.
func fieldAlias( reflect.StructField,  string) ( string,  tagOptions) {
	if  := .Tag.Get();  != "" {
		,  = parseTag()
	}
	if  == "" {
		 = .Name
	}
	return , 
}

// tagOptions is the string following a comma in a struct field's tag, or
// the empty string. It does not include the leading comma.
type tagOptions []string

// parseTag splits a struct field's url tag into its name and comma-separated
// options.
func parseTag( string) (string, tagOptions) {
	 := strings.Split(, ",")
	return [0], [1:]
}

// Contains checks whether the tagOptions contains the specified option.
func ( tagOptions) ( string) bool {
	for ,  := range  {
		if  ==  {
			return true
		}
	}
	return false
}