package common

import (
	"bytes"
	"encoding/json"
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"text/template"

	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api"
	"git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
	ts "git.eugeniocarvalho.dev/eugeniucarvalho/gg/generators/typescript"
	"github.com/jeremywohl/flatten"
	"github.com/kataras/iris/v12/context"
	"go.mongodb.org/mongo-driver/bson/primitive"
)

const (
	BSON             = "go.mongodb.org/mongo-driver/bson"
	BSONX            = "go.mongodb.org/mongo-driver/x/bsonx"
	MONGO            = "go.mongodb.org/mongo-driver/mongo"
	BSON_PRIMITIVE   = "go.mongodb.org/mongo-driver/bson/primitive"
	IRIS_CTX         = "github.com/kataras/iris/v12/context"
	IRIS             = "github.com/kataras/iris/v12"
	UPDATE_RELATION  = "UpdateRelation"
	BASE_HAS_DEPENDE = "HasDep"
)

var (
	API_URL                   = "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api"
	API_ERROR                 = "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs"
	CODE_GEN_V2_COMMON        = "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common"
	CODE_GEN_V2_AUTHORIZATION = "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/authorization"

	// Variavel de controle de acesso aos models da API.
	Models = &api.Mongo{}

	camelToUnderRegex = regexp.MustCompile(`([^[:lower:]])`)
	//Generic e
	Generic = regexp.MustCompile("(?P<type>[\\w-_]+)<(?P<dtype>[\\w\\*]+)>")
	//GenericPart e
	GenericPart = regexp.MustCompile("<(?P<id>[\\w\\*]+)>")
	//ImportMap e
	importMap = map[string]string{
		"bson":      BSON,
		"primitive": BSON_PRIMITIVE,
	}

	SR = SchemasRelations{
		R: map[string][]*Relation{},
	}
)

type BuildOptions struct {
	Mode                   string
	IgnoreBuildSteps       string
	IgnoreBuildStepsValues map[int]bool
	ProjectDotNotation     map[string]interface{}
}

func (b *BuildOptions) IgnoreStep(step int) bool {
	_, ok := b.IgnoreBuildStepsValues[step]
	return ok
}

func (b *BuildOptions) Parse() error {
	var (
		value int
		err   error
	)
	if b.IgnoreBuildStepsValues == nil {
		b.IgnoreBuildStepsValues = map[int]bool{}
	}

	for _, v := range strings.Split(b.IgnoreBuildSteps, ",") {
		if value, err = strconv.Atoi(v); err != nil {
			return err
		}
		b.IgnoreBuildStepsValues[value] = true
	}

	return nil
}

func ImportMap(base string) string {
	if v, ok := importMap[base]; ok {
		return v
	}
	panic(fmt.Sprintf("Import %s não definido", base))
}

type Project struct {
	OutPath                   string                                                      `json:"outPath"`
	CurrentDirectory          string                                                      `json:"outPath"`
	Package                   string                                                      `json:"package"`
	Kind                      string                                                      `json:"kind"`
	Etag                      string                                                      `json:"etag"`
	Version                   string                                                      `json:"version"`
	BuildVersion              string                                                      `json:"buildVersion"`
	ID                        string                                                      `json:"id"`
	Name                      string                                                      `json:"name"`
	DataBaseSufix             string                                                      `json:"dataBaseSufix"`
	Mode                      string                                                      `json:"mode"`
	Revision                  string                                                      `json:"revision"`
	Title                     string                                                      `json:"title"`
	Description               string                                                      `json:"description"`
	OwnerDomain               string                                                      `json:"ownerDomain"`
	OwnerName                 string                                                      `json:"ownerName"`
	DocumentationLink         string                                                      `json:"documentationLink"`
	Protocol                  string                                                      `json:"protocol"`
	BaseURL                   string                                                      `json:"baseUrl"`
	BasePath                  string                                                      `json:"basePath"`
	Middlewares               []string                                                    `json:"middlewares"`
	ServicePath               string                                                      `json:"servicePath"`
	GitRepository             string                                                      `json:"git.repository"`
	Environment               Environment                                                 `json:"environment"`
	Variables                 map[string]interface{}                                      `json:"variables"`
	Resource                  *Resource                                                   `json:"-"`
	Schemas                   []*Entity                                                   `json:"schemas"`
	SchemasRef                map[string]*Entity                                          `json:"-"`
	Resources                 []*Resource                                                 `json:"resources"`
	Auth                      Auth                                                        `json:"auth"`
	TypeScriptSource          *ts.File                                                    `json:"-"`
	Icons                     map[string]string                                           `json:"icons"`
	ReplaceWhenEmpty          map[string]bool                                             `json:"ReplaceWhenEmpty"`
	OmitEmpty                 map[string]bool                                             `json:"omitempty"`
	Clients                   []*Client                                                   `json:"clients,omitempty"`
	Translators               map[string]TranslationFn                                    `json:"-"`
	FormatMap                 map[string]string                                           `json:"-"`
	Queries                   *QueryDef                                                   `json:"queries"`
	ACL                       *ACL                                                        `json:"acl"`
	Custom                    map[string]interface{}                                      `json:"custom"`
	TranslatorBuildOptionsMap map[string]func(*Project, *BuildOptions) (*BuildSet, error) `json:"-"`
}

type ACL struct {
	Roles       []*Role       `json:"roles"`
	Permissions []*Permission `json:"permissions"`
}
type QueryDef struct {
	Blacklistwords map[string][]string `json:"blacklistwords"`
	Queries        map[string]string   `json:"queries"`
	Common         map[string]string   `json:"common"`
}
type Role struct {
	Title       string   `json:"title"`
	Description string   `json:"description"`
	ID          string   `json:"id"`
	AllowRemove bool     `json:"allowRemove,omitempty"`
	Permissions []string `json:"permissions"`
}
type Permission struct {
	Title       string `json:"title"`
	Description string `json:"description"`
	ID          string `json:"id"`
}
type Client struct {
	Id        string `json:"id,omitempty"`
	OutputDir string `json:"outputDir,omitempty"`
}

type Auth struct {
	AuthCookieDomain string `json:"authCookieDomain"`
	AuthTokenID      string `json:"authTokenId"`
	Oauth2           Oauth2 `json:"oauth2"`
}

type Oauth2 struct {
	URI    string       `json:"uri"`
	Client Oauth2Client `json:"client"`
	Scopes []Scope      `json:"scopes"`
}

type Oauth2Client struct {
	RedirectURI  string   `json:"redirect_uri"`
	ClientID     string   `json:"client_id"`
	ClientSecret string   `json:"client_secret"`
	Scope        []string `json:"scope"`
}

type Scope struct {
	ID           string   `json:"id"`
	PromptToUser []string `json:"promptToUser"`
	Description  string   `json:"description"`
}

type EnvironmentVariable struct {
	ID          string `json:"id"`
	CamelID     string `json:"-"`
	Default     string `json:"default"`
	Required    bool   `json:"required,omitempty"`
	Description string `json:"description"`
}

type Environment map[string]*EnvironmentVariable

type Entity struct {
	HasMode         bool                   `json:"hasMode"`
	ID              string                 `json:"id"`
	Type            string                 `json:"type"`
	Description     string                 `json:"description"`
	Collection      string                 `json:"collection"`
	DB              string                 `json:"db"`
	Extends         []string               `json:"extends"`
	Properties      []*Propertie           `json:"properties"`
	Representations map[string][]string    `json:"representations"`
	Custom          map[string]interface{} `json:"custom"`
}

type Resource struct {
	ID           string                 `json:"id"`
	Description  string                 `json:"description"`
	Entity       string                 `json:"entity"`
	Formats      []*Value               `json:"formats"`
	Methods      []*Method              `json:"methods"`
	CommonParams map[string]*Parameter  `json:"commonParams"`
	Custom       map[string]interface{} `json:"custom"`
}

type Method struct {
	ID                  string                 `json:"id"`
	Entity              string                 `json:"entity"`
	Type                string                 `json:"type"` // Assume valores {one, list, implement}
	Path                string                 `json:"path"`
	Template            string                 `json:"template"`
	BeforePersistAction bool                   `json:"beforePersistAction"`
	HttpMethod          string                 `json:"httpMethod"`
	Description         string                 `json:"description"`
	Response            string                 `json:"response"`
	Request             string                 `json:"request"`
	Scopes              []string               `json:"scopes"`
	Middlewares         []string               `json:"middlewares"`
	Postresponse        []string               `json:"postresponse"`
	ParameterOrder      []string               `json:"parameterOrder"`
	ParametersString    []string               `json:"parameters"`
	Resource            *Resource              `json:"-"`
	Hooks               map[string]bool        `json:"hooks"`
	Parameters          map[string]*Parameter  `json:"parametersmap"`
	Preconditions       []Action               `json:"preconditions"`
	BeforeResponse      []Action               `json:"beforeResponse"`
	BeforeParseRequest  []Action               `json:"beforeParseRequest"`
	Custom              map[string]interface{} `json:"custom"`
	// Parameters     map[string]*Parameter `json:"parameters"`
}

type Action struct {
	ID      string                 `json:"id"`
	Context map[string]interface{} `json:"context"`
}

type Parameter struct {
	ID          string                 `json:"id"`
	Type        string                 `json:"type"`
	Required    bool                   `json:"required"`
	Description string                 `json:"description"`
	Default     string                 `json:"default"`
	Location    string                 `json:"location"`
	ConvertTo   string                 `json:"convertTo"`
	Custom      map[string]interface{} `json:"custom"`
	// Validation  *ValidationRule        `json:"validation"`
	Validation map[string]interface{} `json:"validation"`
}

// type ValidationRule struct {
// 	Accept    []*Value `json:"-"`
// 	AcceptRef []string `json:"accept"`
// 	Reject    []*Value `json:"reject"`
// 	RejectRef []string `json:"-"`
// 	In        []string `json:"in"`
// 	Contains  string   `json:"contains"`
// 	Regex     string   `json:"regex"`
// 	Min       string   `json:"min"`
// 	Max       string   `json:"max"`
// 	Type      string   `json:"type"`
// }

type Value struct {
	Id          string `json:"id"`
	Value       string `json:"value"`
	Default     bool   `json:"default"`
	Fields      string `json:"fields"`
	Description string `json:"description"`
}

type Propertie struct {
	ID                string                 `json:"id"`
	Name              string                 `json:"name"`
	Type              string                 `json:"type"`
	Description       string                 `json:"description"`
	AutogenerateInput string                 `json:"autogenerate"`
	Autogenerate      map[string]AutoGenDef  `json:"-"`
	Targets           string                 `json:"targets"`
	Array             bool                   `json:"array"`
	Required          bool                   `json:"required"`
	Relation          bool                   `json:"relation"`
	TagVisited        bool                   `json:"-"`
	Reference         bool                   `json:"reference"`
	Readonly          bool                   `json:"readonly"`
	Unique            bool                   `json:"uniq"`
	Default           interface{}            `json:"default"`
	Enum              []string               `json:"enum"`
	Values            []interface{}          `json:"values"`
	EnumDescriptions  []string               `json:"enumDescriptions"`
	Tags              map[string]string      `json:"tags"`
	Filter            []*Filter              `json:"filter"`
	Custom            map[string]interface{} `json:"custom"`
}

type AutoGenDef struct {
	Type string
	Args []string
}

type Filter struct {
	Path              string         `json:"path"`
	Type              string         `json:"type"`
	Label             string         `json:"label"`
	UserEnumAsOptions bool           `json:"userEnumAsOptions"`
	Multiples         bool           `json:"multiples"`
	Options           []FilterOption `json:"options,omitempty"`
}

type FilterOption struct {
	Value interface{} `json:"value"`
	Label string      `json:"label"`
}

type ApiFilter struct {
	Id     string    `json:"id"`
	Date   int64     `json:"date"`
	Fields []*Filter `json:"fields"`
}

func NewApiFilter(id string) *ApiFilter {
	return &ApiFilter{
		Id:     id,
		Fields: []*Filter{},
	}
}

func RequestParams(args string, params map[string]*Parameter) func(ctx context.Context) (resp interface{}, err *errs.Error) {
	argsList := strings.Split(args, ",")

	return func(ctx context.Context) (resp interface{}, err *errs.Error) {
		var (
			values      = ctx.Values()
			id          string
			value       interface{}
			sourceValue interface{}
			param       *Parameter
			paramsMap   = map[string]interface{}{}
		)

		values.Set("$params", paramsMap)

		for _, arg := range argsList {
			param = params[arg]

			switch param.Location {

			case "query":
				id = "query." + arg
				value = api.Q(ctx, arg, param.Default)
			case "path":
				id = "path." + arg
				value = api.P(ctx, arg, param.Default)
			}
			sourceValue = value

			emptyValue := (value == "" || value == nil)

			if param.Required && emptyValue {
				invalidArgument := errs.InvalidArgument()
				invalidArgument.Message = fmt.Sprintf(
					"ParamRequired: param '%s' in '%s'",
					param.ID,
					param.Location,
				)
				return nil, invalidArgument
			}

			if !emptyValue && param.ConvertTo != "" {
				if value, err = convertValueByType(param.ConvertTo, value); err != nil {
					invalidArgument := errs.InvalidArgument()
					invalidArgument.Message = fmt.Sprintf(
						"ParamTypeConversionError: param '%s' in '%s' with value '%v'. Waiting a %s ",
						param.ID,
						param.Location,
						value,
						param.ConvertTo,
					)
					return nil, invalidArgument
				}
			}

			if param.Validation != nil {
				for validator, args := range param.Validation {
					if fn, found := validationParamFunctions[validator]; found {
						ctx.Application().Logger().Info(fmt.Sprintf("validadete[%s][%s][%v]", validator, args, value))
						if err = fn(param, value, args); err != nil {
							return nil, err
						}
					}
				}
			}

			values.Set(fmt.Sprintf("%s_conv", id), value)
			values.Set(id, sourceValue)

			paramsMap[fmt.Sprintf("%s_conv", arg)] = value
			paramsMap[arg] = sourceValue
		}
		ctx.Next()
		return
	}
}

var (
	convertionTypeFunctions = map[string]func(interface{}) (interface{}, *errs.Error){
		"ObjectID":      stringToObjectId,
		"array(string)": stringToArrayString,
		"bool":          stringToBool,
		"int":           stringToInt,
		"number":        stringToFloat,
	}

	validationParamFunctions = map[string]func(*Parameter, interface{}, interface{}) *errs.Error{
		"min": func(param *Parameter, value interface{}, minString interface{}) *errs.Error {
			var input float64

			if v, ok := value.(int64); ok {
				input = float64(v)
			} else if v, ok := value.(float64); ok {
				input = v
			} else if v, ok := value.(string); ok {
				input = float64(len(v))
			} else {
				invalidArgument := errs.InvalidArgument()
				invalidArgument.Message = fmt.Sprintf(
					"[%s] ValueRestriction: mim validation requires (int,float,string)",
					param.ID,
				)
				return invalidArgument
			}

			if min, convert := minString.(float64); !convert || input < min {
				invalidArgument := errs.InvalidArgument()
				invalidArgument.Message = fmt.Sprintf(
					"[%s] ValueRestriction: value > %v. Received (%v)",
					param.ID,
					minString,
					value,
				)
				return invalidArgument
			}
			return nil
		},
		"max": func(param *Parameter, value interface{}, maxString interface{}) *errs.Error {

			var input float64

			if v, ok := value.(int64); ok {
				input = float64(v)
			} else if v, ok := value.(float64); ok {
				input = v
			} else if v, ok := value.(string); ok {
				input = float64(len(v))
			} else {
				invalidArgument := errs.InvalidArgument()
				invalidArgument.Message = fmt.Sprintf(
					"[%s] ValueRestriction: mim validation requires (int,float,string)",
					param.ID,
				)
				return invalidArgument
			}

			if max, convert := maxString.(float64); !convert || input > max {
				invalidArgument := errs.InvalidArgument()
				invalidArgument.Message = fmt.Sprintf(
					"[%s] ValueRestriction: value < %v. Received (%v)",
					param.ID,
					maxString,
					value,
				)
				return invalidArgument
			}

			return nil
		},
		"accept": func(param *Parameter, input interface{}, accept interface{}) *errs.Error {
			var (
				acceptValues       = accept.([]interface{})
				acceptValuesString = []string{}
				value              = fmt.Sprintf("%v", input)
			)

			for _, acceptValue := range acceptValues {
				if value == acceptValue.(string) {
					return nil
				}
				acceptValuesString = append(acceptValuesString, acceptValue.(string))
			}

			invalidArgument := errs.InvalidArgument()
			invalidArgument.Message = fmt.Sprintf(
				"[%s] ValueRestriction: '%s' isn't accept. Accept [%s]",
				param.ID,
				value,
				strings.Join(acceptValuesString, ","),
			)
			return invalidArgument
		},
		"reject": func(param *Parameter, input interface{}, reject interface{}) *errs.Error {
			var (
				rejectValues = reject.([]interface{})
				value        = fmt.Sprintf("%v", input)
			)

			for _, rejectValue := range rejectValues {
				if value == rejectValue.(string) {
					invalidArgument := errs.InvalidArgument()
					invalidArgument.Message = fmt.Sprintf(
						"[%s] ValueRestriction: '%s' isn't accept",
						param.ID,
						value,
					)
					return invalidArgument
				}
			}
			return nil
		},
		"regex": func(param *Parameter, input interface{}, regex interface{}) *errs.Error {
			var (
				regexString = regex.(string)
				value       = input.(string)
			)

			regexInstance := regexp.MustCompile(regexString)

			if !regexInstance.Match([]byte(value)) {

				invalidArgument := errs.InvalidArgument()
				invalidArgument.Message = fmt.Sprintf(
					"[%s] ValueRestriction: '%s' isn't accept",
					param.ID,
					value,
				)

				return invalidArgument
			}

			return nil
		},
	}
)

func stringToObjectId(value interface{}) (interface{}, *errs.Error) {
	var (
		valueString   = value.(string)
		valueObjectID primitive.ObjectID
		err           error
	)

	if valueObjectID, err = primitive.ObjectIDFromHex(valueString); err != nil {
		invalidArgument := errs.InvalidArgument()
		invalidArgument.Message = fmt.Sprintf("The value '%s' is'nt a valid ObjectId", valueString)
		return nil, invalidArgument
	}

	return valueObjectID, nil
}

func stringToBool(value interface{}) (interface{}, *errs.Error) {

	var (
		valueBool bool
		err       error
	)

	if valueBool, err = strconv.ParseBool(value.(string)); err != nil {
		invalidArgument := errs.InvalidArgument()
		invalidArgument.Message = fmt.Sprintf("The value '%s' is'nt a valid boolean. Accept [true,1,T,false,0,F]", valueBool)
		return nil, invalidArgument
	}

	return valueBool, nil
}

func stringToInt(value interface{}) (interface{}, *errs.Error) {

	var (
		valueInt int64
		err      error
	)

	if valueInt, err = strconv.ParseInt(value.(string), 10, 64); err != nil {
		invalidArgument := errs.InvalidArgument()
		invalidArgument.Message = fmt.Sprintf("The value '%s' is'nt a valid int", valueInt)
		return nil, invalidArgument
	}

	return valueInt, nil
}

func stringToFloat(value interface{}) (interface{}, *errs.Error) {

	var (
		valueFloat float64
		err        error
	)

	if valueFloat, err = strconv.ParseFloat(value.(string), 64); err != nil {
		invalidArgument := errs.InvalidArgument()
		invalidArgument.Message = fmt.Sprintf("The value '%s' is'nt a valid number", valueFloat)
		return nil, invalidArgument
	}

	return valueFloat, nil
}

func convertValueByType(typ string, value interface{}) (interface{}, *errs.Error) {
	var err *errs.Error
	if fn, found := convertionTypeFunctions[typ]; found {
		if value, err = fn(value); err != nil {
			return nil, err
		}
	}
	return value, nil
}

func stringToArrayString(value interface{}) (stringArray interface{}, err *errs.Error) {
	var (
		valueString string
		ok          bool
	)
	if valueString, ok = value.(string); !ok {
		err = errs.InvalidArgument().Details(&errs.Detail{
			Reason: "StringToArrayString expect a string value",
		})
		return
	}
	stringArray = strings.Split(valueString, ",")
	return
}

// func validateParam(param *Parameter, value interface{}) (interface{}, *errs.Error) {
// 	var err *errs.Error

// 	return value, nil
// }

func (t *Method) Hook(id string) bool {
	// active := t.Hooks[id]
	// return active
	return t.Hooks[id]
}

func (t *Propertie) ParseAutogenerate() error {
	if t.AutogenerateInput != "" {
		parts := strings.Split(t.AutogenerateInput, ":")
		if len(parts) < 2 {
			return fmt.Errorf("Invalid autogenerate input  '%s' in attribute '%s'.", t.AutogenerateInput, t.ID)
		}

		if t.Autogenerate == nil {
			t.Autogenerate = map[string]AutoGenDef{}
		}

		args := strings.Split(parts[1], "#")

		for _, k := range strings.Split(parts[0], ",") {
			t.Autogenerate[k] = AutoGenDef{
				Type: args[0],
				Args: args[1:],
			}
		}
	}
	return nil
}

type SchemasRelations struct {
	R map[string][]*Relation
}

type Relation struct {
	Source     string
	Target     string
	Attr       string
	Collection string
	DB         string
	IsArray    bool
}

type EntityInfo struct {
	Name          string
	Origin        string
	NewName       string
	DynamicType   string
	DynamicTypeId string
	IsGeneric     bool
}

type TranslationFn func(p *Project) error

func (project *Project) Build(options *BuildOptions) (err error) {
	var (
		variablesJson string
		out           []byte
	)

	options.ProjectDotNotation = map[string]interface{}{}

	if out, err = json.Marshal(project); err != nil {
		return
	}

	if variablesJson, err = flatten.FlattenString(
		string(out),
		"project.",
		flatten.DotStyle,
	); err != nil {
		return
	}

	if err = json.Unmarshal(
		[]byte(variablesJson),
		&options.ProjectDotNotation,
	); err != nil {
		return
	}

	for _, client := range project.Clients {
		if fn, found := project.Translators[client.Id]; found {

			if err = fn(project); err != nil {
				return err
			}

			if err = runBuildCommads(project, client, options); err != nil {
				return
			}
		} else {
			err = fmt.Errorf("compiler translation model '%s' not defined", client.Id)
			return
		}
	}

	return
}

func (p *Project) OutDirectory(path string) {
	p.OutPath = path
}

func (p *Project) Client(id string) *Client {
	for _, c := range p.Clients {
		if c.Id == id {
			return c
		}
	}
	return nil
}

func (p *Project) Save(path string) error {
	data, err := json.MarshalIndent(p, "", "    ")
	if err == nil {
		err = FilePutContentsBytes(path, data, 0777)
	}
	return err
}

func (p *Project) GetCollection(entity string) string {
	for _, e := range p.Schemas {
		if e.ID == entity {
			return e.Collection
		}
	}
	return "undefined"
}

func getCustom(options map[string]interface{}, path string) (resp interface{}) {
	if options != nil {
		resp = options[path]
	}
	return
}

func (p *Project) GetEntityDB(entity string) string {
	if en, found := p.SchemasRef[entity]; found {
		return en.DB + p.DataBaseSufix
	}
	panic(fmt.Sprintf("DB attribute is empty in entity '%s'", entity))
}

func (p *Project) EntityDesc(ID string) *Entity {
	if _, y := p.SchemasRef[ID]; !y {
		fmt.Println("EntityDesc(ID)", ID)
		return nil
	}
	return p.SchemasRef[ID]
}

func (m *Method) HasPathParams() bool {
	return len(m.ParameterOrder) > 0
}

func (m *Method) HasFormatParam() (bool, *Parameter) {

	for id, param := range m.Parameters {
		// param = m.Parameters[id]
		// fmt.Println("param:", param.ID)
		if id == "format" {
			return true, param
		}
	}
	return false, nil
}

func (p *Project) GetUrlFromMethod(method *Method) string {
	return p.BaseURL + method.Path
}

func (p *Project) ResponseEntity(property string) *EntityInfo {
	var (
		pi = &EntityInfo{
			Origin: property,
		}
	)
	match := Generic.FindStringSubmatch(property)

	if len(match) == 0 {
		return pi
	}

	for i, name := range Generic.SubexpNames() {
		switch name {
		case "type":
			pi.Name = match[i]
		case "dtype":
			pi.DynamicType = match[i]
			pi.IsGeneric = true
		}
	}

	if pi.IsGeneric {
		entity := p.GetSchema(pi.Name)

		match = GenericPart.FindStringSubmatch(entity.ID)

		for i, name := range GenericPart.SubexpNames() {
			switch name {
			case "id":
				pi.DynamicTypeId = match[i]
			}
		}
	}
	pi.NewName = pi.Name + UpFirst(strings.Replace(pi.DynamicType, "*", "", -1))

	return pi
}

func (p *Project) GetPath(m *Method) string {

	path := []byte(p.BasePath + m.Path)

	for attr, param := range m.Parameters {
		path = regexp.MustCompile("{"+attr+"}").ReplaceAll(path, []byte("{"+attr+":"+param.Type+"}"))
	}

	return string(path)
}
func (p *Project) GetSchema(id string) *Entity {
	id = strings.Replace(id, "*", "", -1)
	if model, ok := p.SchemasRef[id]; ok {
		return model
	}
	panic(fmt.Sprintf("Entity '%s' not defined!", id))
}

// Metodos das propriedades

func (p *Propertie) FillTags(project *Project, propName string) {
	if p.TagVisited {
		return
	}
	if propName == "Id" {

	}
	if p.Tags != nil {
		for k, v := range p.Tags {

			if _, found := project.ReplaceWhenEmpty[k]; found && v == "" {
				p.Tags[k] = LcFirst(p.ID)
			}

			if _, found := project.OmitEmpty[k]; found {
				if p.Tags[k] != "-" {
					p.Tags[k] += ",omitempty"
				}
			}
		}
	}

	p.TagVisited = true
}

func (p *Propertie) GetType() string {
	return strings.Replace(p.Type, "*", "", 1)
}

// Metodos das informacoes da entidade

func (p *EntityInfo) TranslateType(typ string) string {

	if typ == p.DynamicTypeId {
		return p.DynamicType
	}
	return typ
}

// Metodos do esquema de relacoes

// Add adiciona uma relação ao esquema
func (s *SchemasRelations) Has(entity string) bool {
	// spew.Dump(s)
	_, found := s.R[entity]
	return found
}

// Add adiciona uma relação ao esquema
func (s *SchemasRelations) Get(entity string) []*Relation {
	if e, found := s.R[entity]; found {
		return e
	}
	return []*Relation{}
}

// Add adiciona uma relação ao esquema
func (s *SchemasRelations) Add(r *Relation) {
	if _, found := s.R[r.Source]; !found {
		s.R[r.Source] = []*Relation{}
	}

	s.R[r.Source] = append(s.R[r.Source], r)
}

func ParseTemplate(input string, name ...string) (*template.Template, error) {
	var tmpl, err = template.New(strings.Join(name, "")).Parse(input)
	return tmpl, err
}

func TemplateToString(template *template.Template, data interface{}) (string, error) {
	var result bytes.Buffer

	if err := template.Execute(&result, data); err != nil {
		return "", err
	}

	return result.String(), nil
}

func NewProject() *Project {
	return &Project{
		Mode:             "",
		SchemasRef:       map[string]*Entity{},
		Icons:            map[string]string{},
		ReplaceWhenEmpty: map[string]bool{},
		OmitEmpty:        map[string]bool{},
		FormatMap:        map[string]string{},
		Queries:          &QueryDef{},
		Schemas:          []*Entity{},
		Resources:        []*Resource{},
		Translators:      map[string]TranslationFn{},
	}
}
