package main

import (
	"errors"
)

var ErrContinue error = errors.New("Continue...")

func TryParseFrom(input []Token) ([]Token, Statement, error) {

	// if (handoff) ;|\n then do (handoff) else (handoff) fi

	// if (handoff) ;|\n then do (handoff) fi

	if input[0] == "if" {

		remainder, cond, err := TryParseFrom(input[1:])
		if err != nil {
			return input, nil, err
		}

		if len(remainder) == 0 {
			return nil, nil, ErrContinue
		}

		if remainder[0].isLineSeparator() == false || remainder[1] != "then" {
			return input, nil, errors.New("Expected NL THEN after IF (statement)")
		}

		remainder, successBody, err := TryParseFrom(remainder[2:])
		if err != nil {
			return input, nil, err
		}

		if len(remainder) == 0 {
			return nil, nil, ErrContinue
		}

		if remainder[0] == "fi" {
			return remainder[1:], &IfStatement{Test: cond, Positive: successBody}, nil
		}

		if remainder[0] != "else" {
			return input, nil, errors.New("Expected FI or ELSE after IF...THEN (statement)")
		}

		remainder, elseBody, err := TryParseFrom(remainder[1:])
		if err != nil {
			return input, nil, err
		}

		if len(remainder) == 0 {
			return nil, nil, ErrContinue
		}

		if remainder[0] != "fi" {
			return input, nil, errors.New("Expected FI after ELSE...THEN (statement)")
		}

		return remainder[1:], &IfElseStatement{Test: cond, Positive: successBody, Negative: elseBody}, nil

	}

	// Parenthesis

	// ( (handoff) )

	if input[0] == "(" {

		remainder, inner, err := TryParseFrom(input[1:])
		if err != nil {
			return input, nil, err
		}

		if len(remainder) == 0 {
			return nil, nil, ErrContinue
		}

		if remainder[0] != ")" {
			return input, nil, errors.New("Expected ) after (...(statement)")
		}

		return remainder[1:], &SubshellStatement{Inner: inner}, nil

	}

	// Must be a binary

	// Greedy consume tokens
	// If we encounter a ), successful leave without consuming it
	// If we encounter a ; or \n, successful leave with consuming it
	// If we encounter a && / || / |, recurse parse and then convert ourselves

	// FIXME this all allows zero-length command statements

	for i, tok := range input {

		if tok.isLineSeparator() {
			return input[i+1:], &CommandStatement{BinaryArgs: input[:i-1]}, nil // consume it

		} else if tok == ")" {
			return input[i:], &CommandStatement{BinaryArgs: input[:i-1]}, nil // don't consume it

		} else if tok == "&" {
			return input[i+1:], &NonblockingStatement{Inner: &CommandStatement{BinaryArgs: input[:i-1]}}, nil // consume it

		} else if tok == "&&" {
			remain, afterStmt, err := TryParseFrom(input[i+1:])
			if err != nil {
				return nil, nil, err
			}

			return remain, &ConditionalStatement{
				Precondition: &CommandStatement{BinaryArgs: input[:i-1]},
				Postaction:   afterStmt,
			}, nil

		} else if tok == "||" {
			remain, afterStmt, err := TryParseFrom(input[i+1:])
			if err != nil {
				return nil, nil, err
			}

			return remain, &ShortCircuitStatement{
				First:  &CommandStatement{BinaryArgs: input[:i-1]},
				Second: afterStmt,
			}, nil

		} else if tok == "|" {
			remain, afterStmt, err := TryParseFrom(input[i+1:])
			if err != nil {
				return nil, nil, err
			}

			return remain, &PipeStatement{
				Source: &CommandStatement{BinaryArgs: input[:i-1]},
				Dest:   afterStmt,
			}, nil

		} else {
			// continue
		}

	}

	// We read all the tokens, a valid command despite no EOF
	return nil, &CommandStatement{BinaryArgs: input}, nil
}
