/** @file parse_binop_rhs.cpp
 *
 *  Code to deal with binary operators. */

/*
 *  GiNaC Copyright (C) 1999-2025 Johannes Gutenberg University Mainz, Germany
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "ex.h"
#include "symbol.h"
#include "mul.h"
#include "add.h"
#include "power.h"
#include "operators.h"
#include "parser.h"
#include "lexer.h"
#include "debug.h"

#include <sstream>
#include <stdexcept>

namespace GiNaC {

/// Make a sum or a product.
static ex make_binop_expr(const int binop, const exvector& args);
/// Check if the token is a binary operator. 
static inline bool is_binop(const int c);
/// Get the precedence of the pending binary operator.
static int get_tok_prec(const int c);

/// binoprhs: ([+*/^-] primary)*
ex parser::parse_binop_rhs(int expr_prec, ex& lhs)
{
	exvector args;
	args.push_back(lhs);
	int binop = -1, orig_binop = -1;
	bool need_sign_flip = false;
	while (1) {
		// check if this is a binop
		if (!is_binop(token)) {
			if (args.size() > 1)
				return make_binop_expr(orig_binop, args);
			else
				return lhs;
		}
		
		// Okay, we know this is a binop.
		if (args.size() == 1)
			orig_binop = token;

		binop = token;

		// If this is a binop that binds at least as tightly as
		// the current binop, consume it, otherwise we are done.
		int tok_prec = get_tok_prec(token);
		if (tok_prec < expr_prec) {
			if (args.size() > 1)
				return make_binop_expr(orig_binop, args);
			else 
				return lhs;
		}

		get_next_tok();  // eat binop

		// Parse the primary expression after the binary operator.
		ex rhs = parse_primary();

		// If binop binds less tightly with rhs than the operator after
		// rhs, let the pending operator take rhs as its lhs.
		int next_prec = get_tok_prec(token);
		if (tok_prec < next_prec)
			rhs = parse_binop_rhs(tok_prec + 1, rhs);

		// previous operator was '+', and current one is '-'
		// (or vice a versa).
		if (need_sign_flip)
			rhs = - rhs;

		args.push_back(rhs);

		// Minimize the number of eval() and ctor calls. This is
		// crucial for a reasonable performance. If the next operator
		// is compatible with the pending one (or the same) don't create
		// the expression and continue collecting operands instead.
		if (binop == token)
			continue;
		else if (binop == '+' && token == '-') {
			need_sign_flip = token != orig_binop;
			continue;
		} else if (binop == '-' && token == '+') {
			need_sign_flip = token != orig_binop;
			continue;
		} else { 
			if (args.size() <= 1)
				bug("binop has " << args.size() << " arguments, expected >= 2");
			lhs = make_binop_expr(orig_binop, args);
			args.clear();
			args.push_back(lhs);
		}
	}
}

/// unary_expr: [+-] expression
ex parser::parse_unary_expr()
{
	// Parse a binary expression with the priority of exponentiation
	// or higher. Ignore the overall sign, because parse_primary()
	// handles it for us.
	get_next_tok(); // Skip [+-]
	ex lhs = parse_primary();
	ex e = parse_binop_rhs(get_tok_prec('^'), lhs);
	return e;
}

extern const numeric* _num_1_p;

static ex make_minus_expr(const exvector& args)
{
	exvector rest_args;
	rest_args.reserve(args.size() - 1);
	std::copy(args.begin() + 1, args.end(), std::back_inserter(rest_args));
	ex rest_base = dynallocate<add>(rest_args);
	ex rest = dynallocate<mul>(rest_base, *_num_1_p);
	ex ret = dynallocate<add>(args[0], rest);
	return ret;
}

static ex make_divide_expr(const exvector& args)
{
	exvector rest_args;
	rest_args.reserve(args.size() - 1);
	std::copy(args.begin() + 1, args.end(), std::back_inserter(rest_args));
	ex rest_base = dynallocate<mul>(rest_args);
	ex rest = pow(rest_base, *_num_1_p);
	return dynallocate<mul>(args[0], rest);
}

static ex make_power_expr(const exvector& args)
{
	size_t n = args.size();
	ex p = pow(args[n - 2], args[n - 1]);
	for (size_t i = n - 2; i > 0; i--) {
		p = pow(args[i - 1], p);
	}
	return p;
}

static ex make_binop_expr(const int binop, const exvector& args)
{
	switch (binop) {
		case '+':
			return dynallocate<add>(args);
		case '-':
			return make_minus_expr(args);
		case '*':
			return dynallocate<mul>(args);
		case '/':
			return make_divide_expr(args);
		case '^':
			return make_power_expr(args);
		default:
			throw std::invalid_argument(
					std::string(__func__) 
					+ ": invalid binary operation: " 
					+ char(binop));
	}
}

static inline bool is_binop(const int c)
{
	switch (c) {
		case '+':
		case '-':
		case '*':
		case '/':
		case '^':
			return true;
		default:
			return false;
	}
}

/// Get the precedence of the pending binary operator.
static int get_tok_prec(const int c)
{
	switch (c) {
		case '+':
		case '-':
			return 20;
		case '*':
		case '/':
			return 40;
		case '^':
			return 60;
		default:
			return -1;
			// means 'this is not a binary operator'
	}
}

} // namespace GiNaC
