#pragma once

#include "base.h"

struct TPDE : BASE
{
//	int max_factors_common_order=4;

	vct bas;
	int MO;	// maximal order
	int MOindex; // maximal index possible for one set of basis functions (with derivatives up to order MO)
	int MFCO; // max factors common order
	int Nbas;

	bool swapit=true; // use data0 or data1 

	hashmap v_data2D0, v_data2D1;
	hashmap& v_data2D() { return (swapit) ? v_data2D0 : v_data2D1; }
	hashmap& v_prev2D() { return (swapit) ? v_data2D1 : v_data2D0; }

	ex *data1D0=0;
	ex *data1D1=0;
	ex* data1D() { return (swapit) ? data1D0 : data1D1; }
	ex* prev1D() { return (swapit) ? data1D1 : data1D0; }

	ex data0D=0;

	ex *basders=0;
	symbol x,y,z,t;

	lst zero_ders;

	void allocate(vct &ibas, int iMO, int iMFCO, symbol &it, symbol &ix, symbol &iy, symbol &iz);

	inline indx ind(int bindex, int a, int b, int c, int d) { return (indx)(a + b*(MO+1) + c*(MO+1)*(MO+1) + d*(MO+1)*(MO+1)*(MO+1) + bindex*(MO+1)*(MO+1)*(MO+1)*(MO+1)); }
	inline indx ind(int bindex1, int a1, int b1, int c1, int d1, int bindex2, int a2, int b2, int c2, int d2) { return ind(bindex1,a1,b1,c1,d1) + (indx)MOindex*ind(bindex2,a2,b2,c2,d2); }

	bool is_empty();
	bool nonzero_coeff(int bi, int j0, int j1, int j2, int j3);
	bool replace_der_by_expr(int bi, int kt, int kx, int ky, int kz, TPDE &subsexpr);


	// data writting with ordering in place
	inline ex& data_w(int bi, int i0, int i1, int i2, int i3, int bj, int j0, int j1, int j2, int j3)
	{
		// compute unique index (with ordering in mind)
		indx i = (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3)) ? ind(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) : ind(bj,j0,j1,j2,j3, bi,i0,i1,i2,i3);

		// todo: check existence + create new one if not existing in v_data
		auto it = v_data2D().find( i );
		if (it == v_data2D().end()) {
			ex nothing=0;
			v_data2D().insert(std::make_pair(i, nothing));
			it = v_data2D().find( i );
		}
//		vctidx::iterator it = find(v_idx().begin(), v_idx().end(), i);
		if (it == v_data2D().end()) cout << "unexpected error" << endl;
		return it->second;
	}
	inline ex& data_w(int bi, int i0, int i1, int i2, int i3) { return data1D()[ind(bi,i0,i1,i2,i3)]; }
//	inline ex& prev_w(int bi, int i0, int i1, int i2, int i3) { return prev1D()[ind(bi,i0,i1,i2,i3)]; }

	// read data only
// debug
//	inline ex& data(int bi, int i0, int i1, int i2, int i3, int bj, int j0, int j1, int j2, int j3) { return data2D()[ind(bi,i0,i1,i2,i3)][ind(bj,j0,j1,j2,j3)]; }


	inline ex data_r(int bi, int i0, int i1, int i2, int i3, int bj, int j0, int j1, int j2, int j3) 
	{
		auto it = v_data2D().find( ind(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) );
		if (it != v_data2D().end()) return it->second;
		return 0;
	}
	inline ex prev(int bi, int i0, int i1, int i2, int i3, int bj, int j0, int j1, int j2, int j3) 
	{
		auto it = v_prev2D().find( ind(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) );
		if (it != v_prev2D().end()) return it->second;
		return 0;
	}

	inline ex data_r(int bi, int i0, int i1, int i2, int i3) { return data1D()[ind(bi,i0,i1,i2,i3)]; }
	inline ex prev(int bi, int i0, int i1, int i2, int i3) { return prev1D()[ind(bi,i0,i1,i2,i3)]; }


	bool get_der_index(ex der, int &bi, int &i0, int &i1, int &i2, int &i3);

	// decompose expression into the structure
	void eat(ex expression);
	void print() { ostringstream sout; print(sout); cout << sout.str(); }
	void print(ostringstream &sout);
	void empty(ex **d);
	void empty(ex *d);
	inline void empty() 
	{ 
		v_data2D0.clear();
		v_data2D1.clear();
		empty(data1D0); empty(data1D1); data0D=0; 
	}

	void mul(ex e);
	void add(TPDE &A);
	bool compare(TPDE &A);

	void mul(TPDE &A);

	// replace all 'target' with natural orders (polynomials) with expression using pde_struct structure (should be optimal for large systems)
	void subs_polynomial3(ex target, TPDE& expression);
	bool subs_polynomial3_worker(ex target, TPDE& expression, ex &coef);
	// replace all 'target' with natural orders (polynomials) with expression
	void subs_polynomial2(ex target, TPDE& expression);
	// debug - brute force, maybe slow for large systems  ... but who nows
	void subs_polynomial1(ex target, TPDE& expression);

	// choose the best one here
	void subs_polynomial(ex target, TPDE& expression, int version=3)
	{
		int it=0;
		while (it++ < 100 && degree(target) > 0)
		{
			cout << "subs_polynomial it " << it << endl;
			switch (version)
			{
				case 1:	subs_polynomial1(target, expression); break;
				case 2: subs_polynomial2(target, expression); break;
				case 3: subs_polynomial3(target, expression); break;
				default: cout << "subs_polynomial: unknown version " << version << endl;
			}
		}
	}

	void replace_coefs(int &sym_index, vct &bas2, lst  &sym_subs, const char* prefix);

	ex raw();
	void expand();
	void normal();

	void diff_worker(int i);
	void diff(int i, int order);

	string replaceStringForExport(string s);

	string derivative_tex(int bj, int j0, int j1, int j2, int j3);
	string derivative_tex_short(int bj, int j0, int j1, int j2, int j3);
	string derivative_tex_multiindex(int bj, int j0, int j1, int j2, int j3, int dim);
//	void save(const char*dirname, const char*fmt, ...);
	void save(const char*dirname, const char*fmt, ...);
	void save_raw(const char*dirname, const char*subdirname, const char*fmt, ...);
	void load(const char*dirname, symtab &table);
	void save_tex(const char*dirname, const char*subdirname, const char*fmt, ...);
	void save_tex2(const char*dirname, const char*subdirname, const char*fmt, ...);

	void copy(TPDE &A);

	int ldegree(ex &term);
	int degree(ex &term);

	void swap();
	void free();

	TPDE() {}

	~TPDE() { free(); }
};

#include "tpde.hpp"
