package muesli

import "fmt"

/*
ParametersPerSignature is the amount of parameters that will be
checked when type checking a signature.	Limited mosty to allow hashability, 
that is, Signature is a map key.
*/
const ParametersPerSignature = 32

/*
ReturnsPerSignature is the amount of parameters that will be
checked when type checking a signature.	Limited mosty to allow hashability, 
that is, Signature is a map key.
*/
const ReturnsPerSignature = 8

/* NamedWithType describes something that is named and that has a type. */
type NamedWithType struct {
	Name WordValue
	Type TypeValue
}

/* Parameter describes the name and type of a parameter of a command. */
type Parameter NamedWithType

/* Returned describes the name and type of a value returned by a command. 
 * It is in essence the same as a Parameter, the type is redefined to 
 * avoid confounding the two.
*/
type Returned NamedWithType 

/* Signature describes the types of the arguments that a callable takes,
as well as the returned values.*/
type Signature struct {
    // Parameters the callable takes.
	Parameters [ParametersPerSignature]Parameter
    // Returns results of the given types and names.
    Returns [ReturnsPerSignature]Returned
    // RequiresAtLeast is the required amount of parameters. 
    // The callable may accept more than this amount, but not less.
    RequiresAtLeast int
    // ReturnsAtLeast is the minimal amount of return values for the 
    // callable. The callable may return more than this amount, but not less.
    ReturnsAtLeast int
}

func (s Signature) String() string {
	res := "["
	sep := ""
	for _, param := range s.Parameters {
        typ := param.Type
		if typ != ZeroType { 
			res = res + sep + typ.String()
			sep = ", "
		}
	}
	res = res + "] -> ["
    sep = ""
    for _, param := range s.Returns {
        typ := param.Type
		if typ != ZeroType { 
			res = res + sep + typ.String()
			sep = ", "
		}
	}
	res = res + "]"    
	return res
}

func (s Signature) StringWithNames() string {
	res := "["
	sep := ""
	for _, param := range s.Parameters {
        typ := param.Type
		if typ != ZeroType { 
			res = res + sep + string(param.Name) + " " + typ.String()
			sep = ", "
		}
	}
	res = res + "] -> ["
    sep = ""
    for _, param := range s.Returns {
        typ := param.Type
		if typ != ZeroType { 
			res = res + sep + string(param.Name) + " " + typ.String()
			sep = ", "
		}
	}
	res = res + "]"    
	return res
}

func (signature * Signature) SetReturns(types ... TypeValue) {
    for i := 0 ; i < len(types) && i < len(signature.Returns) ; i ++ {
        signature.Returns[i].Type = types[i]
        signature.Returns[i].Name = WordValue(fmt.Sprintf("res%d", i))
    }
    signature.ReturnsAtLeast = len(types)
}

func (signature * Signature) SetParameters(types ... TypeValue) {
    for i := 0 ; i < len(types) && i < len(signature.Parameters) ; i ++ {
        signature.Parameters[i].Type = types[i]
        signature.Parameters[i].Name = WordValue(fmt.Sprintf("arg%d", i))
    }
    signature.RequiresAtLeast = len(types)
}

func NewSignature(types ... TypeValue) Signature {
    signature := Signature{}
    signature.SetParameters(types...)
    return signature
}

func NewSignatureWithNames(param ...Value) (Signature, error) {
    sign := Signature{}
	for i, j := 1, 0 ; i < len(param) ; i += 2 {
        name := param[i-1]
		typ  := param[i]
		var ok bool
		var nv WordValue
		var tv TypeValue
		
		if nv, ok = name.(WordValue); !ok {
			return sign, fmt.Errorf("Not a word value: %v", name)
		}
		if tv, ok = typ.(TypeValue); !ok {
			return sign, fmt.Errorf("Not a type value: %v", typ)
		}
		sign.Parameters[j].Type = tv
		sign.Parameters[j].Name = nv
        j++
        sign.RequiresAtLeast++
	}
	return sign, nil
}

// NoSignature is the default signature which means the VM will not do 
// any argument type checking when the callable is called.
func NoSignature() Signature { 
	return Signature{}
}

func CalculateSignature(arguments ...Value) Signature {
	signature := Signature{}
	for i := 0; i < len(signature.Parameters); i++ {
		if i < len(arguments) {
			signature.Parameters[i].Type = arguments[i].Type()
		} else {			
			signature.Parameters[i].Type = AnyType
		}
	}
    signature.RequiresAtLeast = len(arguments)
	return signature
}

func (tv TypeValue) IsMatch(other TypeValue) bool {
	if tv == AnyType || other == AnyType {
		return true
	}
	if tv == ZeroType || other == ZeroType {
		return true
	}
	return tv == other
}

func (signature Signature) IsMatch(other Signature) bool {
    for i, param := range signature.Parameters {
		t1 := param.Type
		t2 := other.Parameters[i].Type
		if !t1.IsMatch(t2) {
			return false
		}
	}
	return true
}

// TypeCheck checks if the arguments match the signature.
// Returns nil if so, or an error if not.
func (signature Signature) TypeCheck(arguments ...Value) error {
    if len(arguments) < signature.RequiresAtLeast {
        return fmt.Errorf("Too few arguments, expected %d: for %d", signature.RequiresAtLeast, len(arguments))
    }
    
	for i , arg := range arguments {
		if i >= len(signature.Parameters) {
			break
		}
		param := signature.Parameters[i]
		expectedType := param.Type
		
		if arg == nil {
			return fmt.Errorf("Nil argument %d, expected %s: %v for %v", i, expectedType, arguments, signature)
		}

        if !expectedType.IsMatch(arg.Type()) {
			return fmt.Errorf("Argument %d type mismatch: %s<->%s", i, expectedType, arg.Type())
		}
	}
    return nil
}