// Mgmt
// Copyright (C) James Shubin and the project contributors
// Written by James Shubin <james@shubin.ca> and the project contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
// Additional permission under GNU GPL version 3 section 7
//
// If you modify this program, or any covered work, by linking or combining it
// with embedded mcl code and modules (and that the embedded mcl code and
// modules which link with this program, contain a copy of their source code in
// the authoritative form) containing parts covered by the terms of any other
// license, the licensors of this program grant you additional permission to
// convey the resulting work. Furthermore, the licensors of this program grant
// the original author, James Shubin, additional permission to update this
// additional permission if he deems it necessary to achieve the goals of this
// additional permission.

package interfaces

import (
	"fmt"
	"io"
	"net/url"
	"sort"
	"strings"

	"github.com/purpleidea/mgmt/engine"
	"github.com/purpleidea/mgmt/lang/types"
	"github.com/purpleidea/mgmt/pgraph"
	"github.com/purpleidea/mgmt/util/errwrap"
)

// Node represents either a Stmt or an Expr. It contains the minimum set of
// methods that they must both implement. In practice it is not used especially
// often since we usually know which kind of node we want.
type Node interface {
	//fmt.Stringer // already provided by pgraph.Vertex
	pgraph.Vertex // must implement this since we store these in our graphs

	// Apply is a general purpose iterator method that operates on any node.
	Apply(fn func(Node) error) error

	//Parent() Node // TODO: should we implement this?
}

// Stmt represents a statement node in the language. A stmt could be a resource,
// a `bind` statement, or even an `if` statement. (Different from an `if`
// expression.)
type Stmt interface {
	Node

	// Init initializes the populated node and does some basic validation.
	Init(*Data) error

	// Interpolate returns an expanded form of the AST as a new AST. It does
	// a recursive interpolate (copy) of all members in the AST.
	Interpolate() (Stmt, error) // return expanded form of AST as a new AST

	// Copy returns a light copy of the struct. Anything static will not be
	// copied. For a full recursive copy consider using Interpolate instead.
	// TODO: do we need an error in the signature?
	Copy() (Stmt, error)

	// Ordering returns a graph of the scope ordering that represents the
	// data flow. This can be used in SetScope so that it knows the correct
	// order to run it in.
	Ordering(map[string]Node) (*pgraph.Graph, map[Node]string, error)

	// SetScope sets the scope here and propagates it downwards.
	SetScope(*Scope) error

	// TypeCheck returns the list of invariants that this node produces. It
	// does so recursively on any children elements that exist in the AST,
	// and returns the collection to the caller. It calls TypeCheck for
	// child statements, and Infer/Check for child expressions.
	TypeCheck() ([]*UnificationInvariant, error)

	// Graph returns the reactive function graph expressed by this node. It
	// takes in the environment of any functions in scope. Intended to be
	// called only once, creates and returns the graph and also stores it to
	// be used in .Output if needed.
	Graph(env *Env) (*pgraph.Graph, error)

	// Output returns the output that this "program" produces. This output
	// is what is used to build the output graph. It requires the input
	// table of values that are used to populate each function.
	Output(Table) (*Output, error)
}

// Expr represents an expression in the language. Expr implementations must have
// their method receivers implemented as pointer receivers so that they can be
// easily copied and moved around. Expr also implements pgraph.Vertex so that
// these can be stored as pointers in our graph data structure.
type Expr interface {
	Node

	// Init initializes the populated node and does some basic validation.
	Init(*Data) error

	// Interpolate returns an expanded form of the AST as a new AST. It does
	// a recursive interpolate (copy) of all members in the AST. For a light
	// copy use Copy.
	Interpolate() (Expr, error)

	// Copy returns a light copy of the struct. Anything static will not be
	// copied. For a full recursive copy consider using Interpolate instead.
	// TODO: do we need an error in the signature?
	Copy() (Expr, error)

	// Ordering returns a graph of the scope ordering that represents the
	// data flow. This can be used in SetScope so that it knows the correct
	// order to run it in.
	Ordering(map[string]Node) (*pgraph.Graph, map[Node]string, error)

	// SetScope sets the scope here and propagates it downwards.
	SetScope(*Scope, map[string]Expr) error

	// SetType sets the type definitively, and errors if it is incompatible.
	SetType(*types.Type) error

	// Type returns the type of this expression. It may speculate if it can
	// determine it statically. This errors if it is not yet known.
	Type() (*types.Type, error)

	// Infer returns the type of itself and a collection of invariants. The
	// returned type may contain unification variables. It collects the
	// invariants by calling Check on its children expressions. In making
	// those calls, it passes in the known type for that child to get it to
	// "Check" it. When the type is not known, it should create a new
	// unification variable to pass in to the child Check calls. Infer
	// usually only calls Check on things inside of it, and often does not
	// call another Infer.
	Infer() (*types.Type, []*UnificationInvariant, error)

	// Check is checking that the input type is equal to the object that
	// Check is running on. In doing so, it adds any invariants that are
	// necessary. Check must always call Infer to produce the invariant. The
	// implementation can be generic for all expressions.
	Check(typ *types.Type) ([]*UnificationInvariant, error)

	// Graph returns the reactive function graph expressed by this node. It
	// takes in the environment of any functions in scope. It also returns
	// the function for this node.
	Graph(env *Env) (*pgraph.Graph, Func, error)

	// SetValue stores the result of the last computation of this expression
	// node.
	SetValue(types.Value) error

	// Value returns the value of this expression in our type system.
	Value() (types.Value, error)
}

// ScopeGrapher adds a method to turn an AST (Expr or Stmt) into a graph so that
// we can debug the SetScope compilation phase.
type ScopeGrapher interface {
	Node

	// ScopeGraph adds nodes and vertices to the supplied graph.
	ScopeGraph(g *pgraph.Graph)
}

// Data provides some data to the node that could be useful during its lifetime.
type Data struct {
	// Fs represents a handle to the filesystem that we're running on. This
	// is necessary for opening files if needed by import statements. The
	// file() paths used to get templates or other files from our deploys
	// come from here, this is *not* used to interact with the host file
	// system to manage file resources or other aspects.
	Fs engine.Fs

	// FsURI is the fs URI of the active filesystem. This is useful to pass
	// to the engine.World API for further consumption.
	FsURI string

	// Base directory (absolute path) that the running code is in. If an
	// import is found, that's a recursive addition, and naturally for that
	// run, this value would be different in the recursion.
	Base string

	// Files is a list of absolute paths seen so far. This includes all
	// previously seen paths, where as the former Offsets parameter did not.
	Files []string

	// Imports stores a graph inside a vertex so we have a current cursor.
	// This means that as we recurse through our import graph (hopefully a
	// DAG) we can know what the parent vertex in our graph is to edge to.
	// If we ever can't topologically sort it, then it has an import loop.
	Imports *pgraph.SelfVertex

	// Metadata is the metadata structure associated with the given parsing.
	// It can be present, which is often the case when importing a module,
	// or it can be nil, which is often the case when parsing a single file.
	// When imports are nested (eg: an imported module imports another one)
	// the metadata structure can recursively point to an earlier structure.
	Metadata *Metadata

	// Modules is an absolute path to a modules directory on the current Fs.
	// It is the directory to use to look for remote modules if we haven't
	// specified an alternative with the metadata Path field. This is
	// usually initialized with the global modules path that can come from
	// the cli or an environment variable, but this only occurs for the
	// initial download/get operation, and obviously not once we're running
	// a deploy, since by then everything in here would have been copied to
	// the runtime fs.
	Modules string

	// Downloader is the interface that must be fulfilled to download
	// modules. If a missing import is found, and this is not nil, then it
	// will be run once in an attempt to get the missing module before it
	// fails outright. In practice, it is recommended to separate this
	// download phase in a separate step from the production running and
	// deploys, however that is not blocked at the level of this interface.
	Downloader Downloader

	// LexParser is a function that needs to get passed in to run the lexer
	// and parser to build the initial AST. This is passed in this way to
	// avoid dependency cycles.
	LexParser func(io.Reader) (Stmt, error)

	// StrInterpolater is a function that needs to get passed in to run the
	// string interpolation. This is passed in this way to avoid dependency
	// cycles.
	StrInterpolater func(string, *Textarea, *Data) (Expr, error)

	//World engine.World // TODO: do we need this?

	// Prefix provides a unique path prefix that we can namespace in. It is
	// currently shared identically across the whole AST. Nodes should be
	// careful to not write on top of other nodes data.
	Prefix string

	// Debug represents if we're running in debug mode or not.
	Debug bool

	// Logf is a logger which should be used.
	Logf func(format string, v ...interface{})
}

// AbsFilename returns the absolute filename path to the code this Data struct
// is running. This is used to pull out a filename for error messages.
func (obj *Data) AbsFilename() string {
	// TODO: is this correct? Do we want to check if Metadata is nil?
	if obj == nil || obj.Metadata == nil { // for tests
		return ""
	}
	return obj.Base + obj.Metadata.Main
}

// SourceFile returns the source file which holds the code that this Data struct
// is running. Every import produces a new Data struct, and as a result, each
// one of these represents exactly one file. This never returns nil.
func (obj *Data) SourceFile() *SourceFile {
	if obj == nil { // for tests
		return &SourceFile{}
	}
	return &SourceFile{
		FS:   obj.Fs,
		Path: obj.AbsFilename(),
	}
}

// Scope represents a mapping between a variables identifier and the
// corresponding expression it is bound to. Local scopes in this language exist
// and are formed by nesting within if statements. Child scopes can shadow
// variables in parent scopes, which is another way of saying they can redefine
// previously used variables as long as the new binding happens within a child
// scope. This is useful so that someone in the top scope can't prevent a child
// module from ever using that variable name again. It might be worth revisiting
// this point in the future if we find it adds even greater code safety. Please
// report any bugs you have written that would have been prevented by this. This
// also contains the currently available functions. They function similarly to
// the variables, and you can add new ones with a function statement definition.
// An interesting note about these is that they exist in a distinct namespace
// from the variables, which could actually contain lambda functions.
type Scope struct {
	// Variables maps the scope of name to Expr.
	Variables map[string]Expr

	// Functions is the scope of functions.
	//
	// The Expr will usually be an *ExprFunc. (Actually it's usually or
	// always an *ExprSingleton, which wraps an *ExprFunc now.)
	Functions map[string]Expr

	// Classes map the name of the class to the class.
	Classes map[string]Stmt

	// Iterated is a flag that is true if this scope is inside of a for
	// loop.
	Iterated bool

	Chain []Node // chain of previously seen node's
}

// EmptyScope returns the zero, empty value for the scope, with all the internal
// maps and lists initialized appropriately.
func EmptyScope() *Scope {
	return &Scope{
		Variables: make(map[string]Expr),
		Functions: make(map[string]Expr),
		Classes:   make(map[string]Stmt),
		Iterated:  false,
		Chain:     []Node{},
	}
}

// Copy makes a copy of the Scope struct. This ensures that if the internal map
// is changed, it doesn't affect other copies of the Scope. It does *not* copy
// or change the Expr pointers contained within, since these are references, and
// we need those to be consistently pointing to the same things after copying.
func (obj *Scope) Copy() *Scope {
	if obj == nil { // allow copying nil scopes
		return EmptyScope()
	}

	variables := make(map[string]Expr)
	functions := make(map[string]Expr)
	classes := make(map[string]Stmt)
	iterated := obj.Iterated
	chain := []Node{}

	for k, v := range obj.Variables { // copy
		variables[k] = v // we don't copy the expr's!
	}
	for k, v := range obj.Functions { // copy
		functions[k] = v // we don't copy the generator func's
	}
	for k, v := range obj.Classes { // copy
		classes[k] = v // we don't copy the StmtClass!
	}
	for _, x := range obj.Chain { // copy
		chain = append(chain, x) // we don't copy the Stmt pointer!
	}

	return &Scope{
		Variables: variables,
		Functions: functions,
		Classes:   classes,
		Iterated:  iterated,
		Chain:     chain,
	}
}

// Merge takes an existing scope and merges a scope on top of it. If any
// elements had to be overwritten, then the error result will contain some info.
// Even if this errors, the scope will have been merged successfully. The merge
// runs in a deterministic order so that errors will be consistent. Use Copy if
// you don't want to change this destructively.
// FIXME: this doesn't currently merge Chain's... Should it?
func (obj *Scope) Merge(scope *Scope) error {
	var err error
	// collect names so we can iterate in a deterministic order
	namedVariables := []string{}
	namedFunctions := []string{}
	namedClasses := []string{}
	for name := range scope.Variables {
		namedVariables = append(namedVariables, name)
	}
	for name := range scope.Functions {
		namedFunctions = append(namedFunctions, name)
	}
	for name := range scope.Classes {
		namedClasses = append(namedClasses, name)
	}
	sort.Strings(namedVariables)
	sort.Strings(namedFunctions)
	sort.Strings(namedClasses)

	for _, name := range namedVariables {
		if _, exists := obj.Variables[name]; exists {
			e := fmt.Errorf("variable `%s` was overwritten", name)
			err = errwrap.Append(err, e)
		}
		obj.Variables[name] = scope.Variables[name]
	}
	for _, name := range namedFunctions {
		if _, exists := obj.Functions[name]; exists {
			e := fmt.Errorf("function `%s` was overwritten", name)
			err = errwrap.Append(err, e)
		}
		obj.Functions[name] = scope.Functions[name]
	}
	for _, name := range namedClasses {
		if _, exists := obj.Classes[name]; exists {
			e := fmt.Errorf("class `%s` was overwritten", name)
			err = errwrap.Append(err, e)
		}
		obj.Classes[name] = scope.Classes[name]
	}

	if scope.Iterated { // XXX: how should we merge this?
		obj.Iterated = scope.Iterated
	}

	return err
}

// IsEmpty returns whether or not a scope is empty or not.
// FIXME: this doesn't currently consider Chain's... Should it?
func (obj *Scope) IsEmpty() bool {
	//if obj == nil { // TODO: add me if this turns out to be useful
	//	return true
	//}
	if len(obj.Variables) > 0 {
		return false
	}
	if len(obj.Functions) > 0 {
		return false
	}
	if len(obj.Classes) > 0 {
		return false
	}
	return true
}

// Env is an environment which contains the relevant mappings. This is used at
// the Graph(...) stage of the compiler. It does not contain classes.
type Env struct {
	// Variables map and Expr to a *FuncSingleton which deduplicates the use
	// of a function.
	Variables map[Expr]*FuncSingleton

	// Functions contains the captured environment, because when we're
	// recursing into a StmtFunc which is defined inside a for loop, we can
	// use that to get the right Env.Variables map. As for the function
	// itself, it's the same in each loop iteration, therefore, we find it
	// in obj.expr of ExprCall. (Functions map[string]*Env) But actually,
	// our new version is now this:
	Functions map[Expr]*Env
}

// EmptyEnv returns the zero, empty value for the env, with all the internal
// maps initialized appropriately.
func EmptyEnv() *Env {
	return &Env{
		Variables: make(map[Expr]*FuncSingleton),
		Functions: make(map[Expr]*Env),
	}
}

// Copy makes a copy of the Env struct. This ensures that if the internal maps
// are changed, it doesn't affect other copies of the Env. It does *not* copy or
// change the pointers contained within, since these are references, and we need
// those to be consistently pointing to the same things after copying.
func (obj *Env) Copy() *Env {
	if obj == nil { // allow copying nil envs
		return EmptyEnv()
	}

	variables := make(map[Expr]*FuncSingleton)
	functions := make(map[Expr]*Env)

	for k, v := range obj.Variables { // copy
		variables[k] = v // we don't copy the func's!
	}
	for k, v := range obj.Functions { // copy
		functions[k] = v // we don't copy the generator func's
	}

	return &Env{
		Variables: variables,
		Functions: functions,
	}
}

// FuncSingleton is a singleton system for storing a singleton func and its
// corresponding graph. You must pass in a `MakeFunc` builder method to generate
// these. The graph which is returned from this must contain that Func as a
// node.
type FuncSingleton struct {
	// MakeFunc builds and returns a Func and a graph that it must be
	// contained within.
	// XXX: Add Txn as an input arg?
	MakeFunc func() (*pgraph.Graph, Func, error)

	g *pgraph.Graph
	f Func
}

// GraphFunc returns the previously saved graph and func if they exist. If they
// do not, then it calls the MakeFunc method to get them, and saves a copy for
// next time.
// XXX: Add Txn as an input arg?
func (obj *FuncSingleton) GraphFunc() (*pgraph.Graph, Func, error) {
	// If obj.f already exists, just use that.
	if obj.f != nil { // && obj.g != nil
		return obj.g, obj.f, nil
	}

	var err error
	obj.g, obj.f, err = obj.MakeFunc() // XXX: Add Txn as an input arg?
	if err != nil {
		return nil, nil, err
	}
	if obj.g == nil {
		return nil, nil, fmt.Errorf("unexpected nil graph")
	}
	if obj.f == nil {
		return nil, nil, fmt.Errorf("unexpected nil function")
	}
	return obj.g, obj.f, nil
}

// ValueEnv is a future potential argument to the Value() method on Expr.
// XXX: Consider using this if we want to improve graph shape even more.
type ValueEnv struct {
	Variables map[Expr]types.Value
}

// EmptyValueEnv returns the zero, empty value for the value env, with all the
// internal maps initialized appropriately.
func EmptyValueEnv() *ValueEnv {
	return &ValueEnv{
		Variables: make(map[Expr]types.Value),
	}
}

// Copy makes a copy of the ValueEnv struct. This ensures that if the internal
// maps are changed, it doesn't affect other copies of the ValueEnv. It does
// *not* copy or change the pointers contained within, since these are
// references, and we need those to be consistently pointing to the same things
// after copying.
func (obj *ValueEnv) Copy() *ValueEnv {
	if obj == nil { // allow copying nil envs
		return EmptyValueEnv()
	}

	variables := make(map[Expr]types.Value)

	for k, v := range obj.Variables { // copy
		variables[k] = v // we don't copy the values!
	}

	return &ValueEnv{
		Variables: variables,
	}
}

// Arg represents a name identifier for a func or class argument declaration and
// is sometimes accompanied by a type. This does not satisfy the Expr interface.
// It embeds Textarea so that the parser can store the source code position,
// which tools such as the code formatter use.
type Arg struct {
	Textarea

	Name string
	Type *types.Type // nil if unspecified (needs to be solved for)
}

// String returns a short representation of this arg.
func (obj *Arg) String() string {
	s := obj.Name
	if obj.Type != nil {
		s += fmt.Sprintf(" %s", obj.Type.String())
	}
	return s
}

// Edge is the data structure representing a compiled edge that is used in the
// lang to express a dependency between two resources and optionally send/recv.
type Edge struct {
	Kind1 string // kind of resource
	Name1 string // name of resource
	Send  string // name of field used for send/recv (optional)

	Kind2 string // kind of resource
	Name2 string // name of resource
	Recv  string // name of field used for send/recv (optional)

	Notify bool // is there a notification being sent?
}

// Output is a collection of data returned by a Stmt.
type Output struct { // returned by Stmt
	Resources []engine.Res
	Edges     []*Edge
	//Exported []*Exports // TODO: add exported resources
}

// EmptyOutput returns the zero, empty value for the output, with all the
// internal lists initialized appropriately.
func EmptyOutput() *Output {
	return &Output{
		Resources: []engine.Res{},
		Edges:     []*Edge{},
	}
}

// PositionableNode is the interface implemented by AST nodes that store their
// code position. It is implemented by node types that embed Textarea.
type PositionableNode interface {
	// IsSet returns if the position was already set with Locate already.
	IsSet() bool

	// Locate sets the position in zero-based (start line, start column, end
	// line, end column) format.
	Locate(int, int, int, int)

	// Pos returns the zero-based start line and then start column position.
	Pos() (int, int)

	// End returns the zero-based end line and then end column position.
	End() (int, int)

	// String returns a friendly representation of the positions.
	String() string
}

// TextDisplayer is a graph node that is aware of its position in the source
// code, and can emit a textual representation of that part of the source.
type TextDisplayer interface {
	// Byline returns a simple version of the error location.
	Byline() string

	// HighlightText returns a textual representation of this definition
	// for this node in source.
	HighlightText() string
}

// SourceFile is a single file of source code. It pairs the path of that file
// with the filesystem which contains it, because a path on its own is not
// enough to find a file: the same path can exist in more than one filesystem.
// For example, a module which is imported from an embedded filesystem has its
// own root, and so its main file lives at /main.mcl inside of that filesystem,
// and not on the local disk. Pass one of these around instead of a bare path so
// that the two halves can never get separated.
type SourceFile struct {
	// FS is the filesystem which contains this file. It is optional, since
	// some consumers (mainly tests) don't have one, in which case the
	// contents of this file are simply not available.
	FS engine.ReadFS

	// Path is the absolute path of this file within the above filesystem.
	Path string
}

// String returns a friendly representation of this source file. This is the
// path on its own, since that is what a user wants to read. Use the URI method
// instead when it matters which filesystem the file came from.
func (obj *SourceFile) String() string {
	if obj == nil {
		return ""
	}
	return obj.Path
}

// Filename returns the printable name of this file. This is almost always just
// the path, since that is what the user knows the file as. The exception is a
// file which comes from an embedded filesystem, because each of those is rooted
// at its own module, and as a result the path on its own is ambiguous: every
// embedded module has its own /main.mcl file. Those get the full URI instead,
// which names the module, and which can't be confused with a file on disk,
// since a path never starts with a scheme.
func (obj *SourceFile) Filename() string {
	if obj == nil {
		return ""
	}
	if obj.FS == nil {
		return obj.Path
	}
	u, err := url.Parse(obj.FS.URI())
	if err != nil || u.Scheme != EmbeddedScheme {
		return obj.Path // the common case, don't dress it up
	}
	return obj.URI() // eg: embeddedfs:///embedded/provisioner/main.mcl
}

// URI returns a unique handle for this file. It is the URI of the filesystem
// which contains it, joined with the path of the file inside of that. We need
// the filesystem portion to tell two files apart, since two distinct embedded
// modules can each contain their own /main.mcl file.
func (obj *SourceFile) URI() string {
	if obj == nil {
		return ""
	}
	if obj.FS == nil {
		return obj.Path
	}
	// The fs URI ends with the root path of that fs, and our path always
	// starts with a slash, so drop one of the two adjacent slashes here.
	return strings.TrimSuffix(obj.FS.URI(), "/") + obj.Path
}

// Source returns the contents of this file. It reads it from the filesystem
// which contains it, since that is the only place it is guaranteed to exist.
// This data is used to annotate error messages with some context from the
// source, and as a result it is optional and it can error.
func (obj *SourceFile) Source() ([]byte, error) {
	if obj == nil {
		return nil, fmt.Errorf("nil source file")
	}
	if obj.FS == nil {
		return nil, fmt.Errorf("no filesystem for: %s", obj.Path)
	}
	return obj.FS.ReadFile(obj.Path)
}
