package httprule

import (
	"github.com/grpc-ecosystem/grpc-gateway/utilities"
)

const (
	opcodeVersion = 1
)

// Template is a compiled representation of path templates.
type Template struct {
	// Version is the version number of the format.
	Version int
	// OpCodes is a sequence of operations.
	OpCodes []int
	// Pool is a constant pool
	Pool []string
	// Verb is a VERB part in the template.
	Verb string
	// Fields is a list of field paths bound in this template.
	Fields []string
	// Original template (example: /v1/a_bit_of_everything)
	Template string
}

// Compiler compiles utilities representation of path templates into marshallable operations.
// They can be unmarshalled by runtime.NewPattern.
type Compiler interface {
	Compile() Template
}

type op struct {
	// code is the opcode of the operation
	code utilities.OpCode

	// str is a string operand of the code.
	// num is ignored if str is not empty.
	str string

	// num is a numeric operand of the code.
	num int
}

func (w wildcard) compile() []op {
	return []op{
		{code: utilities.OpPush},
	}
}

func (w deepWildcard) compile() []op {
	return []op{
		{code: utilities.OpPushM},
	}
}

func (l literal) compile() []op {
	return []op{
		{
			code: utilities.OpLitPush,
			str:  string(l),
		},
	}
}

func (v variable) compile() []op {
	var ops []op
	for _, s := range v.segments {
		ops = append(ops, s.compile()...)
	}
	ops = append(ops, op{
		code: utilities.OpConcatN,
		num:  len(v.segments),
	}, op{
		code: utilities.OpCapture,
		str:  v.path,
	})

	return ops
}

func (t template) Compile() Template {
	var rawOps []op
	for _, s := range t.segments {
		rawOps = append(rawOps, s.compile()...)
	}

	var (
		ops    []int
		pool   []string
		fields []string
	)
	consts := make(map[string]int)
	for _, op := range rawOps {
		ops = append(ops, int(op.code))
		if op.str == "" {
			ops = append(ops, op.num)
		} else {
			if _, ok := consts[op.str]; !ok {
				consts[op.str] = len(pool)
				pool = append(pool, op.str)
			}
			ops = append(ops, consts[op.str])
		}
		if op.code == utilities.OpCapture {
			fields = append(fields, op.str)
		}
	}
	return Template{
		Version:  opcodeVersion,
		OpCodes:  ops,
		Pool:     pool,
		Verb:     t.verb,
		Fields:   fields,
		Template: t.template,
	}
}
