#pragma once
#pragma warning(disable: 4312 4311)

#include <stdio.h>
//#include <tchar.h>
#include <string>
#include <time.h>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <list>
#include <hash_map>
#include <algorithm>
#include <functional>      // For greater<int>( )

#include "defs.h"

#include "pla.h"
#include "node.h"

using namespace std;
using namespace stdext;

#define MBD_ERROR___OK				0
#define MBD_ERROR___CANT_LOAD_FILE	1

#define MBD_NODE_START_ID	(TERM_X+1)
#define MBD_LOC_FROM_ID(v) (v-MBD_NODE_START_ID)
#define MBD_ID_FROM_LOC(v) (v+MBD_NODE_START_ID)

enum MBD_OP_Type{OP_OR,OP_AND,OP_XOR};

class NodeKey
{
public:
	uint lo_node_id, hi_node_id, var_id;
	NodeKey(uint ln_id, uint hn_id, uint v_id):lo_node_id(ln_id),hi_node_id(hn_id),var_id(v_id){};
};

class NodeVal
{
public:
	Node* node;
	NodeVal(Node* n):node(n){};
};

//class NodeCompare
//{
//	public:
//		bool operator( )(const NodeKey& k1, const NodeKey& k2)
//		{
//			return ((k1.lo_node_id == k2.lo_node_id) && (k1.hi_node_id==k2.hi_node_id) && (k1.var_id==k2.var_id));
//		}
//};

class NodeHasher : public hash_compare <NodeKey>
{
public:
  size_t operator() (const NodeKey& k) const
  {
	  return (k.var_id*k.hi_node_id*k.lo_node_id) % 1024L;
  }

  bool operator() (const NodeKey& k1, const NodeKey& k2) const
  {
    //return k1.var_id < k2.var_id;
	  return !((k1.lo_node_id == k2.lo_node_id) && (k1.hi_node_id==k2.hi_node_id) && (k1.var_id==k2.var_id));
  }
};


//struct NodeLess
//{
//   bool operator()(const NodeKey & x, const NodeKey & y) const
//   {
//	   if ( x.var_id <  y.var_id)
//         return true;
//
//      return false;
//   }
//};

typedef hash_map<NodeKey,NodeVal,NodeHasher> NodeMap;
typedef pair<NodeKey,NodeVal> NodeMapPair;

class MBD
{
public:
	Node* root;
	Node* term0,*term1,*termX;
	NodeMap map;
	MBD_VAR_ORDERING order2var,var2order;
	uint in_num;

	Node* apply(Node* d1,Node* d2, MBD_OP_Type op);
	Node* apply_OR(Node* d1, Node* d2);
	Node* add_var_to_minterm(PLA_Row& row,uint vpos);
	uint check_integrity();
	uint swap_vars(uint v1, uint v2);
	bool is_in_map(uint nid);

	uint load(char* filename);
	uint load_pla_row(PLA_Row& row);
	uint save(char* filename, Node* n=NULL);
	void set_root(Node* r){root=r;};
	void get_var2order_table(MBD_VAR_ORDERING& o);
	void set_ordering(MBD_VAR_ORDERING& o);
	void set_in_num(uint i){in_num=i;};
	void switch_parent_links(Node* n, Node* new_n);
	void change_node_sons(Node* n, Node* new_lo, Node* new_hi);
	Node* change_node(Node* n, Node* new_lo, Node* new_hi);

	uint reduce(uint);
	Node* get_root(){return root;};
	bool rem_parent(uint n_id);
	MBD(uint);
	MBD(uint , MBD_VAR_ORDERING);
	~MBD();
	uint get_output(Node* d,PLA_Row& row);
	Node* get_node(uint vid, Node* lo, Node* hi);
	uint get_node_count(){return (uint)map.size();};

	uint get_different_parent_count(Node* n, uint eq_vid);

	void save_to_dot(char* filename);
	void get_pla(PLA& pla);
	void get_pla(PLA& pla, Node* n);

	uint cleanup(Node* d);
	uint cleanup();

private:
	Node* reduce(uint,Node*);
};

#define MBD_INITIAL_ORDERING_METHOD___NOMETHOD 0
#define MBD_INITIAL_ORDERING_METHOD___FORCE 0

struct order_for_var_from_cog
{
	uint var_id;
	float cog_val;
	
	bool operator < (const order_for_var_from_cog& x)
	{
		return cog_val < x.cog_val;
	}
};


#define MBD_STATIC_ORDERING_METHOD___NONE		0
#define MBD_STATIC_ORDERING_METHOD___FORCE		1
#define MBD_STATIC_ORDERING_METHOD___STAT		2
#define MBD_STATIC_ORDERING_METHOD___RANDOM		3

#define MBD_DYNAMIC_ORDERING_METHOD___NONE		0
#define MBD_DYNAMIC_ORDERING_METHOD___SWAPPING	1

#define MBD_MIN_X_SUBST_METHOD___NONE		0
#define MBD_MIN_X_SUBST_METHOD___BROTHER	1
#define MBD_MIN_X_SUBST_METHOD___RAND_NODE	2

class MBDLIB
{
	uint static_ordering_method;
	uint x_subst_method;
	bool order_vars_after_load;

	public:
	MBDLIB();

	void set_static_ordering_method(uint pom){static_ordering_method=pom;}
	void set_x_subst_method(uint m){x_subst_method = m;}
	uint get_x_subst_method(){ return x_subst_method;}
	uint get_static_ordering_method(){return static_ordering_method;}
	MBD_VAR_ORDERING order_vars_static(PLA* pla, MBD_VAR_ORDERING* io=NULL);
	uint order_vars_dynamic(MBD* io);

	uint get_span(PLA* pt, MBD_VAR_ORDERING& o);
	uint get_cut(PLA* pt, MBD_VAR_ORDERING& o);

	MBD_VAR_ORDERING ord_histogram(PLA* pla);
	MBD_VAR_ORDERING ord_force(PLA* pla, MBD_VAR_ORDERING* io=NULL);
	MBD_VAR_ORDERING ord_random(PLA* pla);
	
	void print_ordering(MBD_VAR_ORDERING o);

	uint load_from_pla(char* filename, MBD** mbd, PLA** pla);
	uint load_from_pla(PLA* pla, MBD** mbd);
	uint load_from_pla(PLA* pla, MBD** mbd, MBD_VAR_ORDERING& o);
	void apply(MBD** dest, MBD* s1, MBD* s2, MBD_OP_Type op);
	uint get_terms_count(MBD* s);
	uint get_terms_count(Node* n);
	uint copy(MBD** dst, MBD* src);
	uint verify_pla(PLA* pla, MBD* mbd);
	void save_to_dot(MBD* mbd, char* filename);
	
	Node* apply_OR(MBD* dest, Node* d1, Node* d2);
	uint minimize_X(MBD** mbd);
	uint reduce(MBD** mbd_dst, MBD* mbd_src);
	uint get_output(Node* d, PLA_Row& row);
};
