// Copyright 2020 CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package subsume

// TODO: structural subsumption has not yet been implemented.

import "cuelang.org/go/internal/core/adt"

func (s *subsumer) subsumes(gt, lt adt.Conjunct) bool {
	if gt == lt {
		return true
	}

	// First try evaluating at the value level.
	x, _ := gt.Expr().(adt.Value)
	y, _ := lt.Expr().(adt.Value)
	if x == nil {
		// Fall back to structural.
		return s.structural(gt, lt)
	}
	if y == nil {
		return false
	}

	return s.values(x, y)
}

func (s *subsumer) conjunct(gt, lt adt.Conjunct) bool {
	return false
}

func (s *subsumer) c(env *adt.Environment, x adt.Expr) adt.Conjunct {
	return adt.MakeRootConjunct(env, x)
}

func isBottomConjunct(c adt.Conjunct) bool {
	b, _ := c.Expr().(*adt.Bottom)
	return b != nil
}

func (s *subsumer) node(env *adt.Environment, up int32) *adt.Vertex {
	for ; up != 0; up-- {
		env = env.Up
	}
	return env.DerefVertex(s.ctx)
}

func (s *subsumer) structural(a, b adt.Conjunct) bool {
	if isBottomConjunct(b) {
		return true
	}

	switch x := a.Expr().(type) {
	case *adt.DisjunctionExpr:

	case *adt.StructLit:
	case *adt.ListLit:

	case *adt.FieldReference:
		if y, ok := b.Elem().(*adt.FieldReference); ok && x.Label == y.Label {
			if s.node(a.Env, x.UpCount) == s.node(b.Env, y.UpCount) {
				return true
			}
		}

	case *adt.LabelReference:
		if y, ok := b.Elem().(*adt.LabelReference); ok {
			if s.node(a.Env, x.UpCount) == s.node(b.Env, y.UpCount) {
				return true
			}
		}

	case *adt.DynamicReference:
		if y, ok := b.Elem().(*adt.FieldReference); ok {
			if s.node(a.Env, x.UpCount) == s.node(b.Env, y.UpCount) {
				return true
			}
		}

	case *adt.ImportReference:
		if y, ok := b.Elem().(*adt.ImportReference); ok &&
			x.ImportPath == y.ImportPath {
			return true
		}

	case *adt.LetReference:
		if y, ok := b.Elem().(*adt.LetReference); ok && x.Label == y.Label {
			if s.node(a.Env, x.UpCount) == s.node(b.Env, y.UpCount) {
				return true
			}
		}

	case *adt.SelectorExpr:
		if y, ok := a.Elem().(*adt.SelectorExpr); ok &&
			x.Sel == y.Sel &&
			s.conjunct(s.c(a.Env, x.X), s.c(b.Env, y.X)) {
			return true
		}

	case *adt.IndexExpr:
		if y, ok := b.Elem().(*adt.IndexExpr); ok &&
			s.conjunct(s.c(a.Env, x.X), s.c(b.Env, y.X)) &&
			s.conjunct(s.c(a.Env, x.Index), s.c(b.Env, y.Index)) {
			return true
		}

	case *adt.SliceExpr:
		if r, ok := b.Elem().(*adt.SliceExpr); ok &&
			s.conjunct(s.c(a.Env, x.X), s.c(b.Env, r.X)) &&
			s.conjunct(s.c(a.Env, x.Lo), s.c(b.Env, r.Lo)) &&
			s.conjunct(s.c(a.Env, x.Hi), s.c(b.Env, r.Hi)) {
			return true
		}

	case *adt.Interpolation:
		switch y := b.Elem().(type) {
		case *adt.String:
			// Be conservative if not ground.
			s.inexact = true

		case *adt.Interpolation:
			// structural equivalence
			if len(x.Parts) != len(y.Parts) {
				return false
			}
			for i, p := range x.Parts {
				if !s.conjunct(s.c(a.Env, p), s.c(b.Env, y.Parts[i])) {
					return false
				}
			}
			return true
		}

	case *adt.BoundExpr:
		if y, ok := b.Elem().(*adt.BoundExpr); ok && x.Op == y.Op {
			return s.conjunct(s.c(a.Env, x.Expr), s.c(b.Env, y.Expr))
		}

	case *adt.UnaryExpr:
		if y, ok := b.Elem().(*adt.UnaryExpr); ok && x.Op == y.Op {
			return s.conjunct(s.c(a.Env, x.X), s.c(b.Env, y.X))
		}

	case *adt.BinaryExpr:
		if y, ok := b.Elem().(*adt.BinaryExpr); ok && x.Op == y.Op {
			return s.conjunct(s.c(a.Env, x.X), s.c(b.Env, y.X)) &&
				s.conjunct(s.c(a.Env, x.Y), s.c(b.Env, y.Y))
		}

	case *adt.CallExpr:
		if y, ok := b.Elem().(*adt.CallExpr); ok {
			if len(x.Args) != len(y.Args) {
				return false
			}
			for i, arg := range x.Args {
				if !s.conjunct(s.c(a.Env, arg), s.c(b.Env, y.Args[i])) {
					return false
				}
			}
			return s.conjunct(s.c(a.Env, x.Fun), s.c(b.Env, y.Fun))
		}
	}
	return false
}

func (s *subsumer) structLit(
	ea *adt.Environment, sa *adt.StructLit,
	eb *adt.Environment, sb *adt.StructLit) bool {

	// Create index of instance fields.
	ca := newCollatedDecls()
	ca.collate(ea, sa)

	if ca.yielders != nil || ca.dynamic != nil {
		// TODO: we could do structural comparison of comprehensions
		// in many cases. For instance, an if clause would subsume
		// structurally if it subsumes any of the if clauses in sb.
		s.inexact = true
		return false
	}

	cb := newCollatedDecls()
	cb.collate(eb, sb)

	if ca.hasOptional && !s.IgnoreOptional {
		// TODO: same argument here as for comprehensions. This could
		// be made to work.
		if ca.pattern != nil || ca.dynamic != nil {
			s.inexact = true
			return false
		}

		// for f, b := range cb.fields {
		// 	if !b.required || f.IsDef() {
		// 		continue
		// 	}
		// 	name := ctx.LabelStr(b.Label)
		// 	arg := &stringLit{x.baseValue, name, nil}
		// 	u, _ := x.optionals.constraint(ctx, arg)
		// 	if u != nil && !s.subsumes(u, b.v) {
		// 		return false
		// 	}
		// }

	}

	return false

}

// collatedDecls is used to compute the structural subsumption of two
// struct literals.
type collatedDecls struct {
	fields      map[adt.Feature]field
	yielders    []adt.Yielder
	pattern     []*adt.BulkOptionalField
	dynamic     []*adt.DynamicField
	values      []adt.Expr
	additional  []*adt.Ellipsis
	isOpen      bool
	hasOptional bool
}

func newCollatedDecls() *collatedDecls {
	return &collatedDecls{fields: map[adt.Feature]field{}}
}

type field struct {
	required  bool
	conjuncts []adt.Conjunct
}

func (c *collatedDecls) collate(env *adt.Environment, s *adt.StructLit) {
	for _, d := range s.Decls {
		switch x := d.(type) {
		case *adt.Field:
			e := c.fields[x.Label]
			e.required = true
			e.conjuncts = append(e.conjuncts, adt.MakeRootConjunct(env, x))
			c.fields[x.Label] = e
			if x.ArcType != adt.ArcMember {
				c.hasOptional = true
			}

		case *adt.BulkOptionalField:
			c.pattern = append(c.pattern, x)
			c.hasOptional = true

		case *adt.DynamicField:
			c.dynamic = append(c.dynamic, x)
			c.hasOptional = true

		case *adt.Ellipsis:
			c.isOpen = true
			c.additional = append(c.additional, x)

		case *adt.Comprehension:
			c.yielders = append(c.yielders, x.Clauses...)

		case *adt.LetClause:
			c.yielders = append(c.yielders, x)
		}
	}
}
