#include "graf.h"

     Graf::Graf(char *_filename){
        filename=_filename;

        string s, s1;
        ParserRec pr;
        gates gat;
        char c;
        count_i=0; count_o=0; count_g=0;
  
        pr = InitParser(filename);

        while (pr.fil != NULL){
            s = ParseString(&pr);
            if (s != ""){
                if (s.compare("input") == 0 || s.compare("INPUT") == 0) {
                   if (ParseChar(&pr) != '(') throw string("Chyba načítání vstupu!");
                     Uzel* t_uzeli=new Uzel (ParseString(&pr),INPUT);
                     inputs.push_back(std::pair <string,Uzel*> (t_uzeli->name,t_uzeli) );
                     gateMap.insert(std::pair<string,Uzel*> (t_uzeli->name,t_uzeli));               
                   if (ParseChar(&pr) != ')') throw string("Chyba načítání vstupu!");
                   count_i++;
                } else if (s.compare("output") == 0 || s.compare("OUTPUT") == 0) {
                   if (ParseChar(&pr) != '(') throw string("Chyba načítání vystupu!");
                   outputs.push_back(std::pair <string,Uzel*> (ParseString(&pr),NULL) );
                   if (ParseChar(&pr) != ')') throw string("Chyba načítání vystupu!");
                   count_o++; 
                } else {
                    if (ParseChar(&pr) != '=') break;
                    s1 = ParseString(&pr);
                    if ( s1.compare("and") == 0 || s1.compare("AND") == 0) gat = AND;
                    else if (  s1.compare("not") == 0 || s1.compare("NOT") == 0) gat = NOT;
                    else if (  s1.compare("nand") == 0 || s1.compare("NAND") == 0 ) gat = NAND;
                    else if (  s1.compare("or") == 0 || s1.compare("OR") == 0 ) gat = OR;
                    else if (  s1.compare("nor") == 0 || s1.compare("NOR") == 0 ) gat = NOR;
                    else if (  s1.compare("xor") == 0 || s1.compare("XOR") == 0 ) gat = XOR;
                    else if (  s1.compare("xnor") == 0 || s1.compare("XNOR") == 0 ) gat = XNOR;
                    else throw string("Chyba nacitani obovdu! Nezname hradlo: "+s1);
    
                    count_g++;
                    if (ParseChar(&pr) != '(') throw string("Chyba nacitani obovdu!");
                    map<string,Uzel*>::iterator ww;
                    ww=gateMap.insert(std::pair<string,Uzel*> (s,new Uzel (s,gat))).first;
                    do {
                        string cdx=ParseString(&pr);
                        ww->second->add_input(cdx);
                        c = ParseChar(&pr);
                        if (c != ',' && c != ')') throw string("Chyba nacitani obovdu!");
                    } while (c != ')');
                }
            }
        }
        if (gateMap.size()==0) throw string("Prazdny obvod!");
                
        map<string,Uzel*>::iterator temp1;
        for (map<string,Uzel*>::iterator i=gateMap.begin();i!=gateMap.end();i++){
             for (int y=0; y<i->second->count_i;y++){
                 temp1=gateMap.find(i->second->inputs[y].first);
                 if (temp1!=gateMap.end()){
                 i->second->inputs[y].second=temp1->second;                          
                 temp1->second->add_output(i->second->name,i->second);
                 }else{ throw string("Chyba pospojovani hradel v obvodu!"); }
             }         
        }
        for (int i=0;i<count_o;i++)     {
                 temp1=gateMap.find(outputs[i].first);
                 if (temp1!=gateMap.end()){
                 outputs[i].second=temp1->second;                          
                 outputs[i].second->output=true;
                 }else{ throw string("Chyba pospojovani hradel v obvodu!"); }    
      
        }
     }
      
     Uzel* Graf::getNotSigned(){
          for (map<string,Uzel*>::iterator i=gateMap.begin();i!=gateMap.end();i++){
             if (i->second->forWork!=1) return i->second;
          }         
          return NULL;
     }
      
     string Graf::getType(gates x){
         switch(x){
             case NOT: return "NOT";
             case AND: return "AND";
             case NAND: return "NAND";
             case OR: return "OR";
             case NOR: return "NOR";
             case XOR: return "XOR";
             case XNOR: return "XNOR";
         }
     }
      
     string Graf::convert (unsigned x){
          string h="";
          char c;
          while (x/10>0){
                 c=(x%10+48);
                 h=c+h;
                 x=x/10;
          }
          c=(x%10+48);
          h=c+h;
          x=x/10;    
          return h;
     } 
     
     void Graf::reset(){
          for (map<string,Uzel*>::iterator i=gateMap.begin();i!=gateMap.end();i++){
              i->second->circ=0;
              i->second->prio=0;
              i->second->forWork=0;
          }   
     }              
      
     void Graf::decompose (int count, int maxGates, int kl_runs, int tests){
          vector<SmallC *> maps;
          int minimal_cut=0;
          SmallC *gateM;
          
          for (int test=0;test<tests;test++){
              Front queue;
              map<string,Uzel*>::iterator ix=gateMap.begin();
              
              //vybrani nahodneho uzlu do fronty pro GGGP
              int random_integer=rand(), max=gateMap.size();
              random_integer = random_integer%max;
              for (int i=0;i<random_integer;i++) ix++;
              queue.push(ix->second);
              Uzel* x;
    
              vector <SmallC *> tepm_maps;
              int no_in=0;
              int circ=1;
              gateM=new SmallC(new map<string,Uzel*>(),0,1); tepm_maps.push_back(gateM);
              
              //zacatek GGGP
              bool controler=1;
              while (controler){
                    if ((x=queue.pop(circ))==NULL){
                       if ((x=getNotSigned())==NULL){
                             controler=0;
                       }else{ 
                            if (no_in>count){
                            gateM->no_in=no_in;       
                            gateM=new SmallC(new map<string,Uzel*>(),0,++circ);
                            tepm_maps.push_back(gateM);
                            no_in=0; queue.flush();
                            }
                       }
                    }else{
                       if (no_in>count||(maxGates&&(gateM->gateM->size()>maxGates))){ gateM->no_in=no_in; gateM=new SmallC(new map<string,Uzel*>(),0,++circ); tepm_maps.push_back(gateM); no_in=0; x->circ++; queue.flush(); }
                    }
                    if (controler){
                        x->circ=circ;
                        gateM->gateM->insert(std::pair<string,Uzel*> (x->name,x)); x->forWork=1;
                        if (x->type==INPUT){ no_in++;  }
                        for (int i=0;i<x->count_o;i++){
                             if (x->outputs[i].second->circ==0) queue.push(x->outputs[i].second);
                             if (x->outputs[i].second->circ==circ){ no_in--;  }
                        }
                        for (int i=0;i<x->count_i;i++){
                             if (x->inputs[i].second->circ==0) queue.push(x->inputs[i].second);
                             if (x->inputs[i].second->circ!=circ){ no_in++; }
                        }
                    }else{ gateM->no_in=no_in; }
              }
              //konec GGP
    
              //vypocitani mnozin vstupu a vystupu pro potreby KL
              for (unsigned c=1; c<=tepm_maps.size(); c++){
                  gateM=tepm_maps[(c-1)];
                  set<string> inputsTemp;
                  for (map<string,Uzel*>::iterator i=gateM->gateM->begin();i!=gateM->gateM->end();i++){
                     if (i->second->type==INPUT){
                        inputsTemp.insert(i->second->name);
                     }else{
                        for (int y=0; y<i->second->count_i;y++){
                           if (i->second->inputs[y].second->circ!=i->second->circ){
                               inputsTemp.insert(i->second->inputs[y].second->name);
                           }
                        }
                     }
                  }
                  gateM->no_in=inputsTemp.size();
              } 
              //konec vypocitani
    
              //zacatek KL
              for (int runs=0;runs<kl_runs;runs++){
               for (map<string,Uzel*>::iterator i=gateMap.begin();i!=gateMap.end();i++){
                 Uzel* x=i->second;  
                 map <int,int> gainMap;
                 for (int y=0; y<x->count_o;y++){
                    gainMap[x->outputs[y].second->circ]=1; 
                 }
                 for (int y=0; y<x->count_i;y++){
                    gainMap[x->inputs[y].second->circ]++; 
                 }
                 int maxGain=0;
                 int maxGainSubset=x->circ;
                 int minGain=gainMap[x->circ];
                 int maxGainOld=0,maxGainNew=0;
                 for (map<int,int>::iterator j=gainMap.begin();j!=gainMap.end();j++){
                    if ((j->second>maxGain)&&(j->second>minGain)){
                       map <int,int> gainMapOut, gainMapIn;
                       for (int y=0; y<x->count_o;y++){
                         gainMapOut[x->outputs[y].second->circ]=1; 
                       }
                       for (int y=0; y<x->count_i;y++){
                         gainMapIn[x->inputs[y].second->circ]++; 
                       }
                       int no_in_new=0,no_in_old=0;
                       no_in_old+=gainMapOut[x->circ];
                       no_in_old-=gainMapIn[j->first];
                       no_in_new+=gainMapIn[x->circ];
                       no_in_new-=gainMapOut[j->first];
                       if (((tepm_maps[x->circ-1]->no_in+no_in_old)<=count)&&((tepm_maps[j->first-1]->no_in+no_in_new)<=count)&&(!maxGates||tepm_maps[j->first-1]->gateM->size()<=maxGates)){
                           maxGain=j->second;
                           maxGainSubset=j->first;  
                           maxGainOld=no_in_old;
                           maxGainNew=no_in_new;
                       }
                    }
                 }
                 if (maxGainSubset!=x->circ){
                     tepm_maps[x->circ-1]->gateM->erase(x->name);
                     tepm_maps[maxGainSubset-1]->gateM->operator[](x->name)=x;
                     x->circ=maxGainSubset;
                     tepm_maps[x->circ-1]->no_in+=maxGainOld;
                     tepm_maps[maxGainSubset-1]->no_in+=maxGainNew;
                 }
               }  
              }      
              //konec KL
              int muxs=0;
              
              for (unsigned c=1; c<=tepm_maps.size(); c++){
                  gateM=tepm_maps[(c-1)];
                  for (map<string,Uzel*>::iterator i=gateM->gateM->begin();i!=gateM->gateM->end();i++){
                     for (int y=0; y<i->second->count_o;y++){
                        if (i->second->outputs[y].second->circ!=i->second->circ){
                           muxs++; break;
                        }
                     }
                  }
              }
              cout << "multiplexoru pro test c. " << test << ": " << muxs << endl;
              if ((minimal_cut==0)||(minimal_cut>muxs)){
                  maps=tepm_maps;
                  minimal_cut=muxs;
              }
              if (test!=(tests-1)) reset();
          }
          
          if (maps.size()==1) throw string ("Rozdeleni se nepodarilo - dekompozice cita pouze jeden obvod!");
          
          //oznaceni uzlu pro zapis nejlepsi varianty
          cout << endl;
          for (unsigned c=1; c<=maps.size(); c++){
              gateM=maps[(c-1)];
              for (map<string,Uzel*>::iterator i=gateM->gateM->begin();i!=gateM->gateM->end();i++){
                  i->second->circ=c;
              }    
          }    
         
          //vypocitani vstupu a vystupu pro potreby vystupu do souboru
          int muxs=0, emptySubsets=0;
          for (unsigned c=1; c<=maps.size(); c++){
              gateM=maps[(c-1)];
              for (map<string,Uzel*>::iterator i=gateM->gateM->begin();i!=gateM->gateM->end();i++){
                 bool mux=false;
                 if (i->second->type==INPUT){
                    gateM->inputs.insert(i->second->name);
                    gateM->inputsOnly++;
                 }else{
                    for (int y=0; y<i->second->count_i;y++){
                       if (i->second->inputs[y].second->circ!=i->second->circ){
                           gateM->inputs.insert(i->second->inputs[y].second->name);
                       }
                    }
                 }
                 if (i->second->output==true) gateM->outputs.insert(i->second->name);                       
                 for (int y=0; y<i->second->count_o;y++){
                    if (i->second->outputs[y].second->circ!=i->second->circ){
                       gateM->outputs.insert(i->second->name);
                       if (!mux){ mux=1; muxs++; }
                    }
                 }
              }
              if (gateM->gateM->size()==0) emptySubsets++;        
          } 
          //konec vypocitani

          //vypsani statistiky
          cout << "Pocet vstupu: " << count_i << endl;
          cout << "Pocet vystupu: " << count_o << endl;
          cout << "Pocet hradel: " << count_g << endl;    
          
          cout << endl << "CELKEM PODMNOZIN: " << maps.size()-emptySubsets << endl;
          cout << endl << "CELKEM PRIDANO MULTIPLEXORU: " << muxs << endl;
          //konec vypsani
         
          //zapis v podobe *.bench
          string dir="md ";
          time_t rawtime;
          struct tm * timeinfo;
          char buffer [18];
          time ( &rawtime );
          timeinfo = localtime ( &rawtime );
          strftime (buffer,80,"%Y-%m-%d-%H-%M-%S-",timeinfo);
          dir+=buffer;
          dir+=filename;
          dir=dir.substr(0,dir.find_first_of('.'));
          
          system (dir.c_str());
          dir=dir.substr(3,dir.length());
          
          string cdy=dir+"/a_bist_info.txt";
          ofstream filinfo (cdy.c_str());          

          filinfo << filename << endl << endl;
          
          filinfo << "Pocet vstupu: " << count_i << endl;
          filinfo << "Pocet vystupu: " << count_o << endl;
          filinfo << "Pocet hradel: " << count_g << endl;             

          filinfo << endl << "CELKEM PODMNOZIN: " << maps.size()-emptySubsets << endl;
          filinfo << endl << "CELKEM PRIDANO MULTIPLEXORU: " << muxs << endl;
          
          for (unsigned c=1; c<=maps.size(); c++){
            gateM=maps[(c-1)];
            if (gateM->gateM->size()!=0){
              string cd=dir+"/bd_"+convert(c)+".bench";
              ofstream fil (cd.c_str());
                        
              fil << "#*** CIRCUIT no. " << c << " of " << maps.size()-emptySubsets <<  " ***" <<endl;
              fil << "#*** generated from " << filename << " ***" <<endl;              
              

              fil << "#*** INPUTs " << gateM->inputs.size() << " ***" << endl;
              fil << "#*** OUTPUTs " << gateM->outputs.size() << " ***" << endl;
              fil << "#*** GATEs " << (gateM->gateM->size()-gateM->inputsOnly) << " ***" << endl;

              filinfo  << c << "[G:" << (gateM->gateM->size()-gateM->inputsOnly) << "/I:" << gateM->inputs.size() << "/O:" << gateM->outputs.size() << "]" "+";

              for (set<string>::iterator i=gateM->inputs.begin();i!=gateM->inputs.end();i++){
                 fil << "INPUT (" << *i << ")" << endl;
              }   

              for (set<string>::iterator i=gateM->outputs.begin();i!=gateM->outputs.end();i++){
                 fil << "OUTPUT (" << *i << ")" << endl;
              }   

              for (map<string,Uzel*>::iterator i=gateM->gateM->begin();i!=gateM->gateM->end();i++){
                  if (i->second->type!=INPUT){
                      fil << i->second->name << " = " << getType(i->second->type) << "(";
                      for (int y=0; y<i->second->count_i;y++){
                         if (y!=0)  fil << ", ";
                         fil << i->second->inputs[y].first;
                      }         
                      fil << ")" << endl;
                  }
               }                             
               fil.close();
             }
          }
          filinfo.close();    
          //konec zapisu v podobe *.bench
          
          //zapis VHDL top
          string filName=filename;
          filName=filName.substr(0,filName.find_first_of('.'));
          string vhdl_name=dir+"/top.vhdl";
          ofstream vhdl_file (vhdl_name.c_str()); 
          bool prvni;         
          
          vhdl_file << "library IEEE;" << endl << "use IEEE.Std_Logic_1164.all;" << endl << "use ieee.numeric_std.all;" << endl << endl;          
          vhdl_file << "entity CUT_top is" << endl << "  port (" << endl;
          vhdl_file << "     " << inputs[0].second->name << "_gi";
          for (int i=1; i<count_i;i++) vhdl_file << ", " << inputs[i].second->name << "_gi";
          vhdl_file << ": in std_logic;" << endl;
          vhdl_file << "     " << outputs[0].second->name << "_go";
          for (int i=1; i<count_o;i++) vhdl_file << ", " << outputs[i].second->name << "_go";
          vhdl_file << ": out std_logic" << endl;
          vhdl_file << "    );" << endl;
          vhdl_file << "end CUT_top" << endl << endl;      
          vhdl_file << "architecture CUT_top_arch of CUT is" << endl << endl;    
                          
          for (map<string,Uzel*>::iterator i=gateMap.begin();i!=gateMap.end();i++){
            if (i->second->type!=INPUT){
               for (int y=0; y<i->second->count_o;y++){
                 if (i->second->circ==i->second->outputs[y].second->circ){ vhdl_file << "SIGNAL sig_" << i->second->name << ": std_logic;" << endl; break; }
               }
            }                            
          }
          
          for (unsigned c=1; c<=maps.size(); c++){
             gateM=maps[(c-1)];
             prvni=true;
             if (gateM->gateM->size()!=0){  
             vhdl_file << endl << "component " << "CUT_P" << c << endl << " PORT(" << endl;
             for (set<string>::iterator i=gateM->inputs.begin();i!=gateM->inputs.end();i++){
               if (prvni) prvni=false; else vhdl_file << ";" << endl;
               vhdl_file << "     " << *i << "_li: in std_logic"; 
             }   
             for (set<string>::iterator i=gateM->outputs.begin();i!=gateM->outputs.end();i++){
               if (prvni) prvni=false; else vhdl_file << ";" << endl;                 
               vhdl_file << "     " << *i << "_lo: out std_logic"; 
             }     
             vhdl_file << endl << "      );" << endl;             
             vhdl_file << "end component;" << endl;                          
             }
          }  
          
          vhdl_file << endl << "BEGIN" << endl; 
          for (unsigned c=1; c<=maps.size(); c++){
             gateM=maps[(c-1)];
             prvni=true;             
             if (gateM->gateM->size()!=0){  
             vhdl_file << endl << "U" << c << "_map: CUT_P" << c << endl << " port map(" << endl;
             for (set<string>::iterator i=gateM->inputs.begin();i!=gateM->inputs.end();i++){
               if ((gateMap.operator[](*i))->type==INPUT){
                 if (prvni) prvni=false; else vhdl_file << "," << endl;                                                          
                 vhdl_file << "     " << *i << "_li=>" << *i << "_gi";
               }else{
                 if (prvni) prvni=false; else vhdl_file << "," << endl;                                                                               
                 vhdl_file << "     " << *i << "_li=>sig_" << *i;
               }      
             }   
             for (set<string>::iterator i=gateM->outputs.begin();i!=gateM->outputs.end();i++){
               if (gateMap.operator[](*i)->output){
                 if (prvni) prvni=false; else vhdl_file << "," << endl;                                  
                 vhdl_file << "     " << *i << "_lo=>" << *i << "_go";
               }  
               if (prvni) prvni=false; else vhdl_file << "," << endl;
               vhdl_file << "     " << *i << "_lo=>sig_" << *i;
             }     
             vhdl_file << endl << "      );" << endl;   
             }          
          }
          vhdl_file << endl << "END CUT_top_arch;";                      
          vhdl_file.close();   
          //konec zapisu VHDL top         

          //zapis VHDL bist
          for (unsigned c=1; c<=maps.size(); c++){
            gateM=maps[(c-1)];
            prvni=true;     
            if (gateM->gateM->size()!=0){   
            vhdl_name=dir+"/CUT_P"+convert(c)+".vhdl";
            ofstream vhdl_top (vhdl_name.c_str());
            vhdl_top << "library IEEE;" << endl << "use ieee.Std_Logic_1164.all;" << endl << "use ieee.numeric_std.all;" << endl << endl;          
            
            vhdl_top << "entity CUT_P" << c << endl << " PORT(" << endl;
            for (set<string>::iterator i=gateM->inputs.begin();i!=gateM->inputs.end();i++){
              if (prvni) prvni=false; else vhdl_top << ";" << endl;  
              vhdl_top << "     " << *i << "_li: in std_logic"; 
            }   
            for (set<string>::iterator i=gateM->outputs.begin();i!=gateM->outputs.end();i++){
              if (prvni) prvni=false; else vhdl_top << ";" << endl;                  
              vhdl_top << "     " << *i << "_lo: out std_logic"; 
            }     
            vhdl_top << endl << "      );" << endl;             
            vhdl_top << "end CUT_P" << c << endl << endl;              

            vhdl_top << "ARCHITECTURE CUT_P" << c << "_arch of CUT_P" << c << " IS" << endl << endl;

            for (map<string,Uzel*>::iterator i=gateMap.begin();i!=gateMap.end();i++){
              if ((i->second->type!=INPUT)&&(gateM->outputs.find(i->second->name)==gateM->outputs.end())){
                for (int y=0; y<i->second->count_o;y++){
                  if (i->second->circ==i->second->outputs[y].second->circ){ vhdl_top << "signal " << "sigl_" << i->second->name << ": std_logic;" << endl;  break; }
                }
              }   
            }
            
            vhdl_top << endl << "BEGIN" << endl << endl;     
            
            for (map<string,Uzel*>::iterator i=gateM->gateM->begin();i!=gateM->gateM->end();i++){
               if (i->second->type!=INPUT){
                 string zacatek, konec, typ;
                 switch (i->second->type){
                   case NAND: zacatek="NOT("; konec=")"; typ="AND"; break;
                   case NOR: zacatek="NOT("; konec=")"; typ="OR"; break;
                   case XNOR: zacatek="NOT("; konec=")"; typ="XOR"; break;
                   default: zacatek=""; konec=""; typ=getType(i->second->type);       
                 }                                 
                 if (gateM->outputs.find(i->second->name)!=gateM->outputs.end()){                                                    
                   vhdl_top << "    " << i->second->name << "_lo<="; 
                 }else{
                   vhdl_top << "    sigl_" << i->second->name << "<=";     
                 }
                 if (i->second->count_i==1){                           
                   vhdl_top << typ << " ";
                   if (gateM->inputs.find(i->second->name)!=gateM->inputs.end()){
                     vhdl_top << "sigl_" << i->second->inputs[0].second->name << ";" << endl;                                                            
                   }else{
                     vhdl_top << i->second->inputs[0].second->name << "_li" << ";" << endl;    
                   }
                 }else{
                   vhdl_top << zacatek;
                   if (gateM->inputs.find(i->second->name)!=gateM->inputs.end()){
                     vhdl_top << "sigl_" << i->second->inputs[0].second->name;                                                            
                   }else{
                     vhdl_top << i->second->inputs[0].second->name << "_li";    
                   }                   
                   for (int j=1;j<i->second->count_i;j++){
                     vhdl_top << " " << typ << " ";
                     if (gateM->inputs.find(i->second->name)!=gateM->inputs.end()){
                       vhdl_top << "sigl_" << i->second->inputs[j].second->name;                                                            
                     }else{
                       vhdl_top << i->second->inputs[j].second->name << "_li";    
                     }                        
                   }
                   vhdl_top << konec << ";" << endl;
                 }
               }  
            }
            vhdl_top << endl;           
            vhdl_top << "end CUT_P" << c << "_arch;";
            vhdl_top.close(); 
            }
          }  
          //konec zapisu VHDL top     
     }           
