//This file is part of Bertini 2.
//
//node.hpp 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 3 of the License, or
//(at your option) any later version.
//
//node.hpp 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 node.hpp.  If not, see <http://www.gnu.org/licenses/>.
//
// Copyright(C) 2015 - 2021 by Bertini2 Development Team
//
// See <http://www.gnu.org/licenses/> for a copy of the license, 
// as well as COPYING.  Bertini2 is provided with permitted 
// additional terms in the b2/licenses/ directory.

// individual authors of this file include:
//  James Collins
//  West Texas A&M University
//  Spring, Summer 2015
//
//
// silviana amethyst
// University of Wisconsin - Eau Claire
//
//  Created by Collins, James B. on 4/30/15.


/**
\file node.hpp

\brief Defines the abstract Node base class.

*/

#ifndef BERTINI_NODE_BASE_HPP
#define BERTINI_NODE_BASE_HPP




#include <iostream>
#include <string>
#include <tuple>

#include <boost/type_index.hpp>

#include "bertini2/num_traits.hpp"
#include "bertini2/detail/visitable.hpp"
#include "bertini2/function_tree/forward_declares.hpp"

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/tracking.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/nvp.hpp>





namespace bertini {

	namespace node{
		class Variable;
	}

using VariableGroup = std::vector< std::shared_ptr<node::Variable> >;



enum class VariableGroupType
{
	Homogeneous,
	Affine,
	Ungrouped
};


namespace node{

namespace detail{
	template<typename T>
	struct FreshEvalSelector
	{};

	template<>
	struct FreshEvalSelector<dbl>
	{
		
		template<typename N>
		static dbl Run(N const& n, std::shared_ptr<Variable> const& diff_variable)
		{
			return n.FreshEval_d(diff_variable);
		}
		
		
		template<typename N>
		static void RunInPlace(dbl& evaluation_value, N const& n, std::shared_ptr<Variable> const& diff_variable)
		{
			n.FreshEval_d(evaluation_value, diff_variable);
		}

	};

	template<>
	struct FreshEvalSelector<mpfr_complex>
	{
		template<typename N>
		static mpfr_complex Run(N const& n, std::shared_ptr<Variable> const& diff_variable)
		{
			return n.FreshEval_mp(diff_variable);
		}
		
		
		template<typename N>
		static void RunInPlace(mpfr_complex& evaluation_value, N const& n, std::shared_ptr<Variable> const& diff_variable)
		{
			n.FreshEval_mp(evaluation_value, diff_variable);
		}

	};
}
/**
An interface for all nodes in a function tree, and for a function object as well.  Almost all
 methods that will be called on a node must be declared in this class.  The main evaluation method is
 defined in this class.

 Bertini function trees are created using std::shared_ptr's to Node base class, generally. 

 \brief Abstract base class for the Bertini hybrid-precision (double-multiple) expression tree. 
 */
class Node : public virtual VisitableBase<>, public std::enable_shared_from_this<Node>
{
	friend detail::FreshEvalSelector<dbl>;
	friend detail::FreshEvalSelector<mpfr_complex>;
public:
	
	virtual ~Node() = default;
	
	///////// PUBLIC PURE METHODS /////////////////
  


	/**
	 Tells code to run a fresh eval on node next time.
	*/
	virtual void Reset() const = 0;
	
	///////// END PUBLIC PURE METHODS /////////////////
	
	

	public:
	/**
	 Evaluate the node.  If flag false, just return value, if flag true
	 run the specific FreshEval of the node, then set flag to false.

	 Template type is type of value you want returned.

	 \return The value of the node.
	 \tparam T The number type for return.  Must be one of the types stored in the Node class, currently dbl and mpfr_complex.
	 */
	template<typename T>
	T Eval(std::shared_ptr<Variable> const& diff_variable = nullptr) const 
	{
		T result;
		EvalInPlace(result, diff_variable);
		return result;
	}
	

	/**
	 Evaluate the node in place.  If flag false, just return value, if flag true
	 run the specific FreshEval of the node, then set flag to false.
	 
	 Template type is type of value you want returned.
	 
	 \return The value of the node.
	 \tparam T The number type for return.  Must be one of the types stored in the Node class, currently dbl and mpfr_complex.
	 */
	template<typename T>
	void EvalInPlace(T& eval_value, std::shared_ptr<Variable> const& diff_variable = nullptr) const;

	
	///////// PUBLIC PURE METHODS /////////////////


	/**
	\brief A transform function, which eliminates nodes from the tree

	A better implementation of this would use a generic Transform.  If you know how to do this, please rewrite the Nodes so they can transform to our whim, and submit a PR.

	\return The number of nodes eliminated

	\see EliminateZeros
	*/
	virtual unsigned EliminateOnes() = 0;
	
	/**
	\brief A transform function, which eliminates nodes from the tree

	A better implementation of this would use a generic Transform.  If you know how to do this, please rewrite the Nodes so they can transform to our whim, and submit a PR.

	\return The number of nodes eliminated

	\see EliminateOnes
	*/
	virtual unsigned EliminateZeros() = 0;


	/**
	\brief A mutating function which finds ways to reduce the depth of the tree

	\note The default implementation is empty, and does literally nothing.
	*/
	virtual unsigned ReduceDepth();

	/**
	Virtual method for printing Nodes to arbitrary output streams.
	*/
	virtual void print(std::ostream& target) const = 0;
	

	/**
	\brief Compute the derivative with respect to a single variable.

	Virtual method for differentiating the node.  If no variable is passed, produces a Jacobian tree when all is said and done, which is used for evaluating the Jacobian.

	\return The result of differentiation.  Jacobian or regular Node depending on what you passed in.
	*/
	virtual std::shared_ptr<Node> Differentiate(std::shared_ptr<Variable> const& v = nullptr) const = 0;
	
	/**
	Compute the degree, optionally with respect to a single variable.

	\param v Shared pointer to variable with respect to which you want to compute the degree of the Node.
	\return The degree.  Will be negative if the Node is non-polynomial.
	*/
	virtual int Degree(std::shared_ptr<Variable> const& v = nullptr) const = 0;



	/**
	 Compute the multidegree with respect to a variable group.  This is for homogenization, and testing for homogeneity.  

	 \return A vector containing the degrees.  Negative entries indicate non-polynomiality.
	*/
	virtual std::vector<int> MultiDegree(VariableGroup const& vars) const = 0;

	/**
	 Compute the overall degree with respect to a variable group.
	
	\param vars A group of variables.
	 \return The degree.  Will be negative if the Node is non-polynomial.
	*/
	virtual int Degree(VariableGroup const& vars) const = 0;

	/**
	Homogenize a tree, inputting a variable group holding the non-homogeneous variables, and the new homogenizing variable.  The homvar may be an element of the variable group, that's perfectly ok.
	
	\param homvar The homogenizing variable, which is multiplied against terms with degree deficiency with repect to other terms.
	\param vars A group of variables, with respect to which you wish to homogenize.
	*/
	virtual void Homogenize(VariableGroup const& vars, std::shared_ptr<Variable> const& homvar) = 0;

	/**
	Check for homogeneity, absolutely with respect to all variables, including path variables and all other variable types, or with respect to a single varaible, if passed. 
	
	\return True if it is homogeneous, false if not.
	*/
	virtual bool IsHomogeneous(std::shared_ptr<Variable> const& v = nullptr) const = 0;

	/**
	Check for homogeneity, with respect to a variable group.

	\return True if it is homogeneous, false if not.
	*/
	virtual bool IsHomogeneous(VariableGroup const& vars) const = 0;
	
	
	/**
	 Change the precision of this variable-precision tree node.
	 
	 \param prec the number of digits to change precision to.
	 */
	virtual void precision(unsigned int prec) const = 0;

	unsigned precision() const;

	///////// PUBLIC PURE METHODS /////////////////

	/**
	Check if a Node is polynomial -- it has degree at least 0.  Negative degrees indicate non-polynomial.

	\return True if it is polynomial, false if not.
	*/
	bool IsPolynomial(std::shared_ptr<Variable> const&v = nullptr) const;
	

	/**
	Check if a Node is polynomial -- it has degree at least 0.  Negative degrees indicate non-polynomial.

	\return True if it is polynomial, false if not.
	*/
	bool IsPolynomial(VariableGroup const&v) const;


	
	
	

protected:
	//Stores the current value of the node in all required types
	//We must hard code in all types that we want here.
	//TODO: Initialize this to some default value, second = false
	mutable std::tuple< std::pair<dbl,bool>, std::pair<mpfr_complex,bool> > current_value_;
	
	
	
	///////// PRIVATE PURE METHODS /////////////////
	
	/**
	Overridden code for specific node types, for how to evaluate themselves.  Called from the wrapper Eval<>() call from Node, if so required (by resetting, etc).

	If we had the ability to use template virtual functions, we would have.  However, this is impossible with current C++ without using experimental libraries, so we have two copies -- because there are two number types for Nodes, dbl and mpfr_complex.
	*/
	virtual dbl FreshEval_d(std::shared_ptr<Variable> const&) const = 0;

	/**
	 Overridden code for specific node types, for how to evaluate themselves.  Called from the wrapper EvalInPlace<>() call from Node, if so required (by resetting, etc).
	 
	 If we had the ability to use template virtual functions, we would have.  However, this is impossible with current C++ without using experimental libraries, so we have two copies -- because there are two number types for Nodes, dbl and mpfr_complex.
	 */
	virtual void FreshEval_d(dbl& evaluation_value, std::shared_ptr<Variable> const&) const = 0;

	
	/**
	Overridden code for specific node types, for how to evaluate themselves.  Called from the wrapper Eval<>() call from Node, if so required (by resetting, etc).

	If we had the ability to use template virtual functions, we would have.  However, this is impossible with current C++ without using experimental libraries, so we have two copies -- because there are two number types for Nodes, dbl and mpfr_complex.
	*/
	virtual mpfr_complex FreshEval_mp(std::shared_ptr<Variable> const&) const = 0;
	
	/**
	 Overridden code for specific node types, for how to evaluate themselves.  Called from the wrapper Eval<>() call from Node, if so required (by resetting, etc).
	 
	 If we had the ability to use template virtual functions, we would have.  However, this is impossible with current C++ without using experimental libraries, so we have two copies -- because there are two number types for Nodes, dbl and mpfr_complex.
	 */
	virtual void FreshEval_mp(mpfr_complex& evaluation_value, std::shared_ptr<Variable> const&) const = 0;

	
	
	
	///////// END PRIVATE PURE METHODS /////////////////
	
	
	/**
	Set the stored values for the Node to indicate a fresh eval on the next pass.  This is so that Nodes which are referred to more than once, are only evaluated once.  The first evaluation is fresh, and then the indicator for fresh/stored is set to stored.  Subsequent evaluation calls simply return the stored number.
	*/
	void ResetStoredValues() const;

	Node();

private:
	friend std::ostream& operator<<(std::ostream & out, const Node& N);

	friend class boost::serialization::access;

	template <typename Archive>
	void serialize(Archive& ar, const unsigned version) {
		register_derived_node_types(ar);
	}

}; // re: class node
	
	/**
	 a single layer of indirection, though which to call the overridden virtual print() method which must be defined for each non-abstract Node type.
	 */
	inline std::ostream& operator<<(std::ostream & out, const Node& N)
	{
		N.print(out);
		return out;
	}
	
	inline std::ostream& operator<<(std::ostream & out, const std::shared_ptr<Node>& N)
	{
		N->print(out);
		return out;
	}



	// inherit from this to get a nice method of producing shared pointers to specific type, solving the diamond problem
	//
	// T is a derived type
	//
	// I adapted from:
	// https://stackoverflow.com/questions/16082785/use-of-enable-shared-from-this-with-multiple-inheritance
	//
	template<typename T>
	struct EnableSharedFromThisVirtual: public virtual Node
	{

	public:

	    std::shared_ptr<T> shared_from_this() {
	       return std::dynamic_pointer_cast<T>(Node::shared_from_this());
	    }

	    std::shared_ptr<const T> shared_from_this() const{
	       return std::dynamic_pointer_cast<const T>(Node::shared_from_this());
	    }



		template <class ThisT>
		std::shared_ptr<ThisT> downcast_shared_from_this(){
			return std::dynamic_pointer_cast<ThisT>(Node::shared_from_this());
		}

		template <class ThisT>
		std::shared_ptr<const ThisT> downcast_shared_from_this() const{
			return std::dynamic_pointer_cast<const ThisT>(Node::shared_from_this());
		}

	};



	} // re: namespace node
} // re: namespace bertini



#endif 
/* defined(BERTINI_NODE_BASE_HPP) */






