// Copyright (c) 2017 Opsidian Ltd.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package combinator

import (
	"github.com/conflowio/parsley/ast"
	"github.com/conflowio/parsley/data"
	"github.com/conflowio/parsley/parser"
	"github.com/conflowio/parsley/parsley"
)

// Sequence is a recursive and-type combinator
type Sequence struct {
	token         string
	parserLookUp  func(int) parsley.Parser
	lenCheck      func(int) bool
	interpreter   parsley.Interpreter
	customErr     error
	resultHandler SeqResultHandler
}

// Seq tries to apply all parsers after each other and returns with all combinations of the results.
// The parsers should be generated by the parserLookUp function for the given index.
// When there is no parser for the given index then nil should be returned.
// The lenCheck function should return true if the longest possible match is valid
func Seq(token string, parserLookUp func(int) parsley.Parser, lenCheck func(int) bool) *Sequence {
	return &Sequence{
		token:        token,
		parserLookUp: parserLookUp,
		lenCheck:     lenCheck,
	}
}

// Name overrides the returned error if its position is the same as the reader's position
// The error will be: "was expecting <name>"
func (s *Sequence) Name(name string) *Sequence {
	s.customErr = parsley.NotFoundError(name)
	return s
}

// Token sets the result token
func (s *Sequence) Token(token string) *Sequence {
	s.token = token
	return s
}

// HandleResult sets the result handler
// If you use a custom function, make sure to make a copy of the nodes slice
func (s *Sequence) HandleResult(resultHandler SeqResultHandler) *Sequence {
	s.resultHandler = resultHandler
	return s
}

// Bind binds the given interpreter
func (s *Sequence) Bind(interpreter parsley.Interpreter) *Sequence {
	s.interpreter = interpreter
	return s
}

// Parse parses the given input
func (s *Sequence) Parse(ctx *parsley.Context, leftRecCtx data.IntMap, pos parsley.Pos) (parsley.Node, data.IntSet, parsley.Error) {
	p := &sequence{
		token:             s.token,
		parserLookUp:      s.parserLookUp,
		lenCheck:          s.lenCheck,
		interpreter:       s.interpreter,
		curtailingParsers: data.EmptyIntSet,
		nodes:             nil,
	}

	if s.resultHandler != nil {
		p.resultHandler = s.resultHandler
	} else {
		p.resultHandler = seqDefaultResultHandler(false)
	}

	res, cp, err := p.Parse(ctx, leftRecCtx, pos)
	if err != nil && s.customErr != nil && err.Pos() == pos && parsley.IsNotFoundError(err) {
		err = parsley.NewError(pos, s.customErr)
	}

	return res, cp, err
}

type sequence struct {
	token             string
	parserLookUp      func(i int) parsley.Parser
	lenCheck          func(i int) bool
	interpreter       parsley.Interpreter
	curtailingParsers data.IntSet
	result            parsley.Node
	err               parsley.Error
	nodes             []parsley.Node
	resultHandler     SeqResultHandler
}

// Parse runs the recursive parser
func (s *sequence) Parse(ctx *parsley.Context, leftRecCtx data.IntMap, pos parsley.Pos) (parsley.Node, data.IntSet, parsley.Error) {
	s.parse(0, ctx, leftRecCtx, pos, true)
	if s.result == nil {
		return nil, s.curtailingParsers, s.err
	}

	if s.err != nil {
		ctx.SetError(s.err)
	}

	return s.result, s.curtailingParsers, nil
}

func (s *sequence) parse(depth int, ctx *parsley.Context, leftRecCtx data.IntMap, pos parsley.Pos, mergeCurtailingParsers bool) bool {
	var cp data.IntSet
	var res parsley.Node
	var err parsley.Error
	nextParser := s.parserLookUp(depth)
	if nextParser != nil {
		ctx.RegisterCall()
		res, cp, err = nextParser.Parse(ctx, leftRecCtx, pos)
		if err != nil && (s.err == nil || err.Pos() >= s.err.Pos()) {
			s.err = err
		}
	}

	if mergeCurtailingParsers {
		s.curtailingParsers = s.curtailingParsers.Union(cp)
	}

	if res != nil {
		switch rest := res.(type) {
		case ast.NodeList:
			for i, node := range rest {
				if s.parseNext(i, node, depth, ctx, leftRecCtx, pos, mergeCurtailingParsers) {
					return true
				}
			}
		default:
			if s.parseNext(0, rest, depth, ctx, leftRecCtx, pos, mergeCurtailingParsers) {
				return true
			}
		}
	}

	if res == nil {
		if s.lenCheck(depth) {
			if depth > 0 {
				s.result = ast.AppendNode(s.result, s.resultHandler.HandleResult(pos, s.token, s.nodes[0:depth], s.interpreter))

				if s.nodes[depth-1] != nil && s.nodes[depth-1].Token() == parser.EOF {
					return true
				}
			} else { // It's an empty result
				s.result = ast.AppendNode(s.result, s.resultHandler.HandleResult(pos, s.token, nil, s.interpreter))
			}
		}
	}
	return false
}

func (s *sequence) parseNext(i int, node parsley.Node, depth int, ctx *parsley.Context, leftRecCtx data.IntMap, pos parsley.Pos, mergeCurtailingParsers bool) bool {
	if len(s.nodes) < depth+1 {
		s.nodes = append(s.nodes, node)
	} else {
		s.nodes[depth] = node
	}
	if i > 0 || node.ReaderPos() > pos {
		leftRecCtx = data.EmptyIntMap
		mergeCurtailingParsers = false
	}
	if s.parse(depth+1, ctx, leftRecCtx, node.ReaderPos(), mergeCurtailingParsers) {
		return true
	}
	return false
}

// SeqOf tries to apply all parsers after each other and returns with all combinations of the results.
// Only matches are returned where all parsers were applied successfully.
func SeqOf(parsers ...parsley.Parser) *Sequence {
	l := len(parsers)
	lookup := func(i int) parsley.Parser {
		if i < l {
			return parsers[i]
		}
		return nil
	}
	lenCheck := func(len int) bool {
		return len == l
	}
	return Seq("SEQ", lookup, lenCheck)
}

// SeqTry tries to apply all parsers after each other and returns with all combinations of the results.
// It needs to match the first parser at least
func SeqTry(parsers ...parsley.Parser) *Sequence {
	l := len(parsers)
	lookup := func(i int) parsley.Parser {
		if i < l {
			return parsers[i]
		}
		return nil
	}
	lenCheck := func(len int) bool {
		return len > 0 && len <= l
	}
	return Seq("SEQ", lookup, lenCheck)
}

// SeqFirstOrAll tries to apply all parsers after each other and returns with all combinations of the results.
// If it can't match all parsers, but it can match the first one it will return with the result of the first one.
func SeqFirstOrAll(parsers ...parsley.Parser) *Sequence {
	l := len(parsers)
	lookup := func(i int) parsley.Parser {
		if i < l {
			return parsers[i]
		}
		return nil
	}
	lenCheck := func(len int) bool {
		return len == 1 || len == l
	}
	return Seq("SEQ", lookup, lenCheck)
}

// SeqResultHandler is an interface to handle the result of a Sequence parser
// Make sure to make a copy of the nodes slice
type SeqResultHandler interface {
	HandleResult(pos parsley.Pos, token string, nodes []parsley.Node, interpreter parsley.Interpreter) parsley.Node
}

// SeqResultHandlerFunc is a function to handle the result of a Sequence parser
// Make sure to make a copy of the nodes slice
type SeqResultHandlerFunc func(pos parsley.Pos, token string, nodes []parsley.Node, interpreter parsley.Interpreter) parsley.Node

func (f SeqResultHandlerFunc) HandleResult(pos parsley.Pos, token string, nodes []parsley.Node, interpreter parsley.Interpreter) parsley.Node {
	return f(pos, token, nodes, interpreter)
}

// ReturnSingle returns the node, if the sequence parser only matched a single node
func ReturnSingle() SeqResultHandlerFunc {
	return seqDefaultResultHandler(true)
}

func seqDefaultResultHandler(returnSingle bool) SeqResultHandlerFunc {
	return func(pos parsley.Pos, token string, nodes []parsley.Node, interpreter parsley.Interpreter) parsley.Node {
		l := len(nodes)
		switch l {
		case 0:
			return ast.NewEmptyNonTerminalNode(token, pos, interpreter)
		case 1:
			if returnSingle {
				return nodes[0]
			}
		}

		nodesCopy := make([]parsley.Node, l)
		copy(nodesCopy, nodes)

		return ast.NewNonTerminalNode(token, nodesCopy, interpreter)
	}
}
