/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 parse

import (
	"fmt"
	"mynewt.apache.org/newt/newt/cfgv"
	"sort"

	"mynewt.apache.org/newt/util"
)

// expr     ::= <unary><expr> | "("<expr>")" |
//              <expr><binary><expr> | <ident> | <literal>
// ident    ::= <printable-char> { <printable-char> }
// literal  ::= """ <printable-char> { <printable-char> } """
// unary    ::= "!"
// binary   ::= "&&" | "^^" | "||" | "==" | "!=" | "<" | "<=" | ">" | ">="

type ParseCode int

const (
	PARSE_NOT_EQUALS ParseCode = iota
	PARSE_NOT
	PARSE_EQUALS
	PARSE_LT
	PARSE_LTE
	PARSE_GT
	PARSE_GTE
	PARSE_AND
	PARSE_OR
	PARSE_XOR
	PARSE_NUMBER
	PARSE_STRING
	PARSE_IDENT
)

type Node struct {
	Code ParseCode
	Data string

	Left  *Node
	Right *Node
}

func (n *Node) String() string {
	if n == nil {
		return ""
	}

	s := ""

	if n.Left != nil {
		s += n.Left.String()
	}

	if n.Left != nil && n.Right != nil {
		// Pad binary operators with spaces on both sides.
		s += " " + n.Data + " "
	} else {
		s += n.Data
	}

	if n.Right != nil {
		s += n.Right.String()
	}

	return s
}

func (n *Node) RpnString() string {
	if n == nil {
		return ""
	}

	s := fmt.Sprintf("<%s>", n.Data)
	if n.Left != nil {
		s += " " + n.Left.RpnString()
	}
	if n.Right != nil {
		s += " " + n.Right.RpnString()
	}

	return s
}

type nodeSorter struct {
	nodes []*Node
}

func (s nodeSorter) Len() int {
	return len(s.nodes)
}
func (s nodeSorter) Swap(i, j int) {
	s.nodes[i], s.nodes[j] = s.nodes[j], s.nodes[i]
}
func (s nodeSorter) Less(i, j int) bool {
	return s.nodes[i].String() < s.nodes[j].String()
}

func SortNodes(nodes []*Node) {
	sort.Sort(nodeSorter{nodes})
}

func NodesEqual(a *Node, b *Node) bool {
	return a.String() == b.String()
}

// Searches a tokenized expression.  The location of the first token that
// matches a member of the supplied token set is returned.  This function does
// not descend into parenthesized expressions.
func findAnyToken(tokens []Token, any []TokenCode) (int, error) {
	pcount := 0

	for _, a := range any {
		for i, t := range tokens {
			if t.Code == TOKEN_LPAREN {
				pcount++
			} else if t.Code == TOKEN_RPAREN {
				pcount--
				if pcount < 0 {
					return -1, fmt.Errorf("imbalanced parenthesis")
				}
			} else if pcount == 0 && t.Code == a {
				return i, nil
			}
		}

	}
	return -1, nil
}

func binTokenToParse(t TokenCode) ParseCode {
	return map[TokenCode]ParseCode{
		TOKEN_NOT_EQUALS: PARSE_NOT_EQUALS,
		TOKEN_EQUALS:     PARSE_EQUALS,
		TOKEN_LT:         PARSE_LT,
		TOKEN_LTE:        PARSE_LTE,
		TOKEN_GT:         PARSE_GT,
		TOKEN_GTE:        PARSE_GTE,
		TOKEN_AND:        PARSE_AND,
		TOKEN_OR:         PARSE_OR,
		TOKEN_XOR:        PARSE_XOR,
	}[t]
}

// Removes the outer layer of parentheses from a tokenized expression.
func stripParens(tokens []Token) ([]Token, error) {
	if tokens[0].Code != TOKEN_LPAREN {
		panic("internal error: stripParens() received unparenthesized string")
	}

	pcount := 1
	for i := 1; i < len(tokens); i++ {
		switch tokens[i].Code {
		case TOKEN_LPAREN:
			pcount++

		case TOKEN_RPAREN:
			pcount--
			if pcount == 0 {
				return tokens[1:i], nil
			}

		default:
		}
	}

	return nil, fmt.Errorf("unterminated parenthesis")
}

var binaryTokens = []TokenCode{
	// Lowest precedence.
	TOKEN_AND,
	TOKEN_XOR,
	TOKEN_OR,
	TOKEN_EQUALS,
	TOKEN_NOT_EQUALS,
	TOKEN_LT,
	TOKEN_LTE,
	TOKEN_GT,
	TOKEN_GTE,
	// Highest precedence.
}

func FindBinaryToken(tokens []Token) int {
	binIdx, err := findAnyToken(tokens, binaryTokens)
	if err != nil {
		return -1
	}
	return binIdx
}

// Recursively parses a tokenized expression.
//
// @param tokens                The sequence of tokens representing the
//                                  expression to parse.  This is acquired by a
//                                  call to `Lex()`.
//
// @return *Node                The expression parse tree.
func Parse(tokens []Token) (*Node, error) {
	if len(tokens) == 0 {
		return nil, nil
	}

	////// Terminal symbols.

	if len(tokens) == 1 {
		switch tokens[0].Code {
		case TOKEN_NUMBER:
			return &Node{
				Code: PARSE_NUMBER,
				Data: tokens[0].Text,
			}, nil

		case TOKEN_STRING:
			return &Node{
				Code: PARSE_STRING,
				Data: tokens[0].Text,
			}, nil

		case TOKEN_IDENT:
			return &Node{
				Code: PARSE_IDENT,
				Data: tokens[0].Text,
			}, nil

		default:
			return nil, fmt.Errorf("invalid expression: %s", tokens[0].Text)
		}
	}

	////// Nonterminal symbols.

	// <expr><binary><expr>
	binIdx, err := findAnyToken(tokens, binaryTokens)
	if err != nil {
		return nil, err
	}
	if binIdx == 0 || binIdx == len(tokens)-1 {
		return nil, fmt.Errorf("binary operator %s at start or end",
			tokens[binIdx].Text)
	}
	if binIdx != -1 {
		n := &Node{
			Code: binTokenToParse(tokens[binIdx].Code),
			Data: tokens[binIdx].Text,
		}

		l, err := Parse(tokens[0:binIdx])
		if err != nil {
			return nil, err
		}

		r, err := Parse(tokens[binIdx+1 : len(tokens)])
		if err != nil {
			return nil, err
		}

		n.Left = l
		n.Right = r

		return n, nil
	}

	// <unary><expr>
	if tokens[0].Code == TOKEN_NOT {
		n := &Node{
			Code: PARSE_NOT,
			Data: tokens[0].Text,
		}
		r, err := Parse(tokens[1:])
		if err != nil {
			return nil, err
		}
		n.Right = r
		return n, nil
	}

	// "("<expr>")"
	if tokens[0].Code == TOKEN_LPAREN {
		stripped, err := stripParens(tokens)
		if err != nil {
			return nil, err
		}

		return Parse(stripped)
	}

	return nil, fmt.Errorf("invalid expression")
}

// Evaluates two expressions into boolean values.
func evalTwo(expr1 *Node, expr2 *Node,
	settings *cfgv.Settings) (bool, bool, error) {

	v1, err := Eval(expr1, settings)
	if err != nil {
		return false, false, err
	}
	v2, err := Eval(expr2, settings)
	if err != nil {
		return false, false, err
	}

	return v1, v2, nil
}

func coerceToInt(n *Node, settings *cfgv.Settings) (int, error) {
	switch n.Code {
	case PARSE_NUMBER:
		num, ok := util.AtoiNoOctTry(n.Data)
		if !ok {
			return 0,
				util.FmtNewtError("expression contains invalid number: `%s`",
					n.Data)
		}
		return num, nil

	case PARSE_IDENT:
		val := settings.Get(n.Data)
		num, ok := util.AtoiNoOctTry(val)
		if !ok {
			return 0,
				util.FmtNewtError("setting %s has value `%s`, "+
					"which is not a number", n.Data, val)
		}
		return num, nil

	default:
		return 0,
			util.FmtNewtError("expression `%s` is not a valid number",
				n.String())
	}
}

func coerceTwoInts(left *Node, right *Node,
	settings *cfgv.Settings, opStr string) (int, int, error) {

	lnum, err := coerceToInt(left, settings)
	if err != nil {
		return 0, 0, util.FmtNewtError("cannot apply %s to `%s`; "+
			"operand not a number", opStr, left.String())
	}

	rnum, err := coerceToInt(right, settings)
	if err != nil {
		return 0, 0, util.FmtNewtError("cannot apply %s to `%s`; "+
			"operand not a number", opStr, right.String())
	}

	return lnum, rnum, nil
}

type equalsFn func(left *Node, right *Node, settings *cfgv.Settings) bool
type equalsEntry struct {
	LeftCode  ParseCode
	RightCode ParseCode
	Fn        equalsFn
}

var equalsDispatch = []equalsEntry{
	// <ident1> == <ident2>
	// True if both syscfg settings have identical text values.
	equalsEntry{
		LeftCode:  PARSE_IDENT,
		RightCode: PARSE_IDENT,
		Fn: func(left *Node, right *Node, settings *cfgv.Settings) bool {
			return settings.Get(left.Data) == settings.Get(right.Data)
		},
	},

	// <ident> == <number>
	// True if the syscfg setting's value is a valid representation of the
	// number on the right.
	equalsEntry{
		LeftCode:  PARSE_IDENT,
		RightCode: PARSE_NUMBER,
		Fn: func(left *Node, right *Node, settings *cfgv.Settings) bool {
			lnum, ok := util.AtoiNoOctTry(settings.Get(left.Data))
			if !ok {
				return false
			}
			rnum, ok := util.AtoiNoOctTry(right.Data)
			if !ok {
				return false
			}
			return lnum == rnum
		},
	},

	// <ident> == <string>
	// True if the syscfg setting's text value is identical to the string on
	// the right.
	equalsEntry{
		LeftCode:  PARSE_IDENT,
		RightCode: PARSE_STRING,
		Fn: func(left *Node, right *Node, settings *cfgv.Settings) bool {
			return settings.Get(left.Data) == right.Data
		},
	},

	// <number1> == <number2>
	// True if both numbers have the same value.
	equalsEntry{
		LeftCode:  PARSE_NUMBER,
		RightCode: PARSE_NUMBER,
		Fn: func(left *Node, right *Node, settings *cfgv.Settings) bool {
			lnum, ok := util.AtoiNoOctTry(left.Data)
			if !ok {
				return false
			}
			rnum, ok := util.AtoiNoOctTry(right.Data)
			if !ok {
				return false
			}
			return lnum == rnum
		},
	},

	// <number> == <string>
	// True if the string is a valid representation of the number.
	equalsEntry{
		LeftCode:  PARSE_NUMBER,
		RightCode: PARSE_STRING,
		Fn: func(left *Node, right *Node, settings *cfgv.Settings) bool {
			return left.Data == right.Data
		},
	},

	// <string1> == <string2>
	// True if both strings are identical (case-sensitive).
	equalsEntry{
		LeftCode:  PARSE_STRING,
		RightCode: PARSE_STRING,
		Fn: func(left *Node, right *Node, settings *cfgv.Settings) bool {
			return left.Data == right.Data
		},
	},
}

func evalEqualsOnce(
	left *Node, right *Node, settings *cfgv.Settings) (bool, bool) {

	for _, entry := range equalsDispatch {
		if entry.LeftCode == left.Code && entry.RightCode == right.Code {
			return entry.Fn(left, right, settings), true
		}
	}

	return false, false
}

// Evaluates an equals expression (`x == y`)
//
// @param left                  The fully-parsed left operand.
// @param left                  The fully-parsed right operand.
// @param settings              The map of syscfg settings.
//
// @return bool                 Whether the expression evaluates to true.
func evalEquals(
	left *Node, right *Node, settings *cfgv.Settings) (bool, error) {

	// The equals operator has special semantics.  Rather than evaluating both
	// operands as booleans and then comparing, the behavior of this operator
	// varies based on the types of operands.  Perform a table lookup using the
	// operand types, and call the appropriate comparison function if a match
	// is found.
	val, ok := evalEqualsOnce(left, right, settings)
	if ok {
		return val, nil
	}
	val, ok = evalEqualsOnce(right, left, settings)
	if ok {
		return val, nil
	}

	// No special procedure identified.  Fallback to evaluating both operands
	// as booleans and comparing the results.
	booll, boolr, err := evalTwo(left, right, settings)
	if err != nil {
		return false, err
	}
	return booll == boolr, nil
}

// Evaluates a fully-parsed expression.
//
// @param node                  The root of the expression to evaluate.
// @param settings              The map of syscfg settings.
//
// @return bool                 Whether the expression evaluates to true.
func Eval(expr *Node, settings *cfgv.Settings) (bool, error) {
	// The null expression is true by default.
	if expr == nil {
		return true, nil
	}

	switch expr.Code {
	case PARSE_NOT:
		r, err := Eval(expr.Right, settings)
		if err != nil {
			return false, err
		}
		return !r, nil

	case PARSE_EQUALS:
		return evalEquals(expr.Left, expr.Right, settings)

	case PARSE_NOT_EQUALS:
		v, err := evalEquals(expr.Left, expr.Right, settings)
		if err != nil {
			return false, err
		}
		return !v, nil

	case PARSE_LT:
		l, r, err := coerceTwoInts(expr.Left, expr.Right, settings, "<")
		if err != nil {
			return false, err
		}
		return l < r, nil

	case PARSE_LTE:
		l, r, err := coerceTwoInts(expr.Left, expr.Right, settings, "<=")
		if err != nil {
			return false, err
		}
		return l <= r, nil

	case PARSE_GT:
		l, r, err := coerceTwoInts(expr.Left, expr.Right, settings, ">")
		if err != nil {
			return false, err
		}
		return l > r, nil

	case PARSE_GTE:
		l, r, err := coerceTwoInts(expr.Left, expr.Right, settings, ">=")
		if err != nil {
			return false, err
		}
		return l >= r, nil

	case PARSE_AND:
		l, r, err := evalTwo(expr.Left, expr.Right, settings)
		if err != nil {
			return false, err
		}
		return l && r, nil

	case PARSE_OR:
		l, r, err := evalTwo(expr.Left, expr.Right, settings)
		if err != nil {
			return false, err
		}
		return l || r, nil

	case PARSE_XOR:
		l, r, err := evalTwo(expr.Left, expr.Right, settings)
		if err != nil {
			return false, err
		}
		return (l && !r) || (!l && r), nil

	case PARSE_NUMBER:
		num, ok := util.AtoiNoOctTry(expr.Data)
		return ok && num != 0, nil

	case PARSE_STRING:
		return ValueIsTrue(expr.Data), nil

	case PARSE_IDENT:
		val := settings.Get(expr.Data)
		return ValueIsTrue(val), nil

	default:
		return false, fmt.Errorf("invalid parse code: %d", expr.Code)
	}
}

func LexAndParse(expr string) (*Node, error) {
	tokens, err := Lex(expr)
	if err != nil {
		return nil, err
	}

	n, err := Parse(tokens)
	if err != nil {
		return nil, util.FmtNewtError("error parsing [%s]: %s",
			expr, err.Error())
	}

	return n, nil
}

// Parses and evaluates string containing a syscfg expression.
//
// @param expr                  The expression to parse.
// @param settings              The map of syscfg settings.
//
// @return bool                 Whether the expression evaluates to true.
func ParseAndEval(expr string, settings *cfgv.Settings) (bool, error) {
	n, err := LexAndParse(expr)
	if err != nil {
		return false, err
	}

	v, err := Eval(n, settings)
	return v, err
}

// Parses an expression and converts it to its normalized text form.
func NormalizeExpr(expr string) (string, error) {
	n, err := LexAndParse(expr)
	if err != nil {
		return "", err
	}

	if n == nil {
		return "", nil
	}

	return n.String(), nil
}

// Evaluates the truthfulness of a text expression.
func ValueIsTrue(val string) bool {
	if val == "" {
		return false
	}

	i, ok := util.AtoiNoOctTry(val)
	if ok && i == 0 {
		return false
	}

	return true
}
