//---------------------------------------------------------------------------

#include "stdafx.h"

#define MAXLINE 10000

using namespace std;

#pragma hdrstop

#include "myExcept.h"
#include "PLA_type.h"
#include "bitvector.h"
#include "Term.h"
#include "utils.h"
#include "TTree.h"

#include "PLA.h"

//---------------------------------------------------------------------------



void PLA::Init(void)
{
    inputs = 0;
    outputs = 0;
    indexed = false;
    uniq = false;
	keep_TT = false;
}

PLA::PLA(void)
{
    Init();
}

PLA::PLA( const PLA *_pla )
{
    Init();
	inputs = _pla->inputs;
	outputs = _pla->outputs;
	type = _pla->type;
	inames = _pla->inames;
	onames = _pla->onames;
}

void PLA::SetType(const PLA_type v)
{
    type = v;
}

void PLA::SetType(const string v)
{
    type = PLA_type(v);
}

PLA::PLA(const unsigned int inp, const unsigned int out, const PLA_type _type)
{
    Init();
    inputs = inp;
    outputs = out;
    type = _type;
}

PLA::PLA(const list<Term>& _data, const PLA_type _type)
//      Data is copied
{
    Init();
    inputs = (*_data.begin()).GetIMSize();
    outputs = (*_data.begin()).GetOMSize();
    this->data = _data;
    this->type = _type;
}

PLA::~PLA(void)
{
    Clear();
}

void PLA::Clear(void)
// Clears all the PLA, deallocates all the structures
{
    data.clear();
    indexed = false;
    F.clear();
    D.clear();
    R.clear();
    inames.clear();
    onames.clear();
    TT.clear();
	indexterm.clear();
}

list<Term>::iterator PLA::AppendTerm( const Term &t )
{
    TTreeNode *node;
	TermList::iterator ti;
	unsigned int i;

	if ( inputs != t.GetIMSize() || outputs != t.GetOMSize() ) throw RuntimeException( "PLA::AppendTerm: incompatible terms" );
	
	if ( !uniq ) {
        data.push_back(t);
		ti = data.end();
		ti--;
		if ( indexed ) {
			indexterm.push_back(ti);
			(*ti).SetIndex( data.size()-1 );
		}
        return ti;
    } else {
        node = TT.InsertTerm( t.GetIM() );
		if ( node->IsTerm() ) {
			ti = node->GetTerm();
			for ( i = 0; i < this->outputs; i++)
				if ( t.GetOM(i) == '1' ) (*ti).SetOM(i, '1');
			(*ti).origin |= t.origin;
			return ti;
		}
        else {
            data.push_back(t);
			ti = data.end();
			ti--;
			if ( indexed ) {
				indexterm.push_back(ti);
				(*ti).SetIndex( data.size()-1 );
			}
            node->SetTerm(ti);
            if ( TT.GetTerms() != data.size() && !keep_TT ) throw RuntimeException( "PLA:AppendTerm: Inconsistency between PLA and TT" );
            return ti;
        }
    }
}

void PLA::AppendPLA(const PLA &_pla)
// appends a given PLA to this
{
	TermList::const_iterator ti;

	for ( ti = _pla.data.begin(); ti != _pla.data.end(); ti++ )
		AppendTerm(*ti);
}

void PLA::AppendTermList( const TermList &terms )
// appends a given list of terms to this
{
	TermList::const_iterator ti;

	for ( ti = terms.begin(); ti != terms.end(); ti++ )
		AppendTerm(*ti);
}

list<Term>::iterator PLA::DeleteTerm(const list<Term>::iterator ti)
{  
	indexed = false;
	if ( uniq )
		if ( !keep_TT ) {
			if ( !TT.DeleteTerm( (*ti).GetIM() ) ) throw RuntimeException( "PLA::DeleteTerm: Inconsistency between PLA and TT. Deleting term which is not in TT." );;
			if ( TT.GetTerms() != data.size()-1 ) throw RuntimeException( "PLA::DeleteTerm: Inconsistency between PLA and TT" );
		}
    return data.erase(ti);
}

void PLA::NameVars(void)
{
    unsigned int i;
    ostringstream ss, ss1;
    
    if ( inames.empty() )
      for ( i = 0; i < inputs; i++ ) {
        ss.seekp(0);
        ss << "x" << i;
        inames.push_back(ss.str());
      }
    if ( onames.empty() )
      for ( i = 0; i < outputs; i++ ) {
        ss1.seekp(0);
        ss1 << "y" << i;
        onames.push_back(ss1.str());
      }
}

void PLA::Reindex(void)
{
    list<Term>::iterator ti;
    unsigned int i = 0;

    if ( indexed ) return;
	indexterm.clear();
	for ( ti = data.begin(); ti != data.end(); ti++ ) {
        indexterm.push_back(ti);
		(*ti).SetIndex(i++);
	}
    indexed = true;
}

void PLA::LoadPLA(const char *fn)
{
    unsigned int pos;
    ifstream f;
    string s, s1;
    char line[MAXLINE];
    Term t;
    unsigned int p = 0;
    unsigned int i;

    this->Clear();

    f.open(fn);
    if (!f.is_open()) throw IOException( "Cannot open file: " + string(fn) );

    while ( !f.eof() ) {
        f.getline(line, MAXLINE);
        pos = 0;
        if ( line[0] == '#' ) continue;
        if ( line[0] == '.' ) {
           s = GetToken(line, pos);
           if ( s == ".i" ) {
                s = GetToken(line, pos);
                inputs = atoi(s.c_str());
                if ( inputs == 0 ) throw IOException( "Bad or damaged PLA file: " + string(fn) );
           } else if ( s == ".o" ) {
                s = GetToken(line, pos);
                outputs = atoi(s.c_str());
                if ( inputs == 0 ) throw IOException( "Bad or damaged PLA file: " + string(fn) );
           } else if ( s == ".p" ) {
                s = GetToken(line, pos);
                p = atoi(s.c_str());
                if ( inputs == 0 ) throw IOException( "Bad or damaged PLA file: " + string(fn) );
           } else if ( s == ".ilb" ) {
                s = GetToken(line, pos);
                while ( !s.empty() ) {
                    inames.push_back(s);
                    s = GetToken(line, pos);
                }
           } else if ( s == ".ob" ) {
                s = GetToken(line, pos);
                while ( !s.empty() ) {
                    onames.push_back(s);
                    s = GetToken(line, pos);
                }
           } else if ( s == ".type" ) {
                s = GetToken(line, pos);
                for ( i = 0; i < s.length(); i++ )
                    if ( s[i] == 'f' ) type.f = true;
                    else if ( s[i] == 'd' ) type.d = true;
                    else if ( s[i] == 'r' ) type.r = true;
                    else throw IOException( "Bad or damaged PLA file: " + string(fn) );
                
           } else if ( s == ".e" || s == ".end" ) { break;
           } else throw IOException( "Bad or damaged PLA file: " + string(fn) );
        } else {
            s = GetToken(line, pos);
            if ( s.length() != 0 ) {
              if ( s.length() != inputs ) throw IOException( "Bad or damaged PLA file: " + string(fn) );
              if ( !(type.f || type.d || type.r) ) {
					type.f = true;
					type.d = true;
			  }
			  s1.clear();
              for ( i = 0; i < inputs; i++ )
                  if ( s[i] == '0' || s[i] == '1' ) s1 += s[i];
                  else if ( s[i] == '-' || s[i] == '2' ) s1 += '-';
                  else throw IOException( "Bad or damaged PLA file: " + string(fn) );

              t.SetIM(s1);

              s = GetToken(line, pos);
              if ( s.length() != outputs ) throw IOException( "Bad or damaged PLA file: " + string(fn) );
              s1.clear();
			  for ( i = 0; i < outputs; i++ ) {
				  if ( s[i] == '2' ) s[i] = '-';
				  if ( s[i] == '3' ) s[i] = '~';
				  if ( s[i] == '4' ) s[i] = '1';
				  if ( s[i] == '~' ) s1 += '~';
				  else if ( s[i] == '1' && type.f ) s1 += '1';
				  else if ( s[i] == '0' && type.r ) s1 += '0';
				  else if ( s[i] == '-' && type.d ) s1 += '-';
				  else if ( s[i] == '1' && !type.f ) s1 += '~';
				  else if ( s[i] == '0' && !type.r ) s1 += '~';
				  else if ( s[i] == '-' && !type.d ) s1 += '~';
                  else throw IOException( "Bad or damaged PLA file: " + string(fn) );
			  }
              t.SetOM(s1);
              AppendTerm(t);
            }
        }
    }
    if (p != 0 && p != data.size()) throw IOException( "Bad or damaged PLA file: " + string(fn) );

    f.close();
}

void PLA::SavePLA(const char *fn)
{
    ofstream f;
    Term t;
    list<Term>::iterator ti;
    unsigned int i;
    string s;

    f.open(fn);
    if (!f.is_open()) throw IOException( "Cannot create a file: " + string(fn) );

	f << ".i " << inputs << endl;
    f << ".o " << outputs << endl;
    f << ".p " << data.size() << endl;
    f << ".type ";
    if ( type.f ) f << "f";
    if ( type.d ) f << "d";
    if ( type.r ) f << "r";
    f << endl;

    if ( !inames.empty() ) {
        f << ".ilb";
        for ( i = 0; i < inputs; i++ ) {
            f << " " << inames[i];
        }
        f << endl;
    }
    if ( !onames.empty() ) {
        f << ".ob";
        for ( i = 0; i < outputs; i++ )
            f << " " << onames[i];
        f << endl;
    }

    for ( ti = data.begin(); ti != data.end(); ti++ )
        f << (*ti).GetIM() << " " << (*ti).GetOM() << endl;
    f << ".e" << endl;

    f.close();
}

void PLA::SaveVHDL(const char *fn)
// Saves the on-set only. Others are ignored
// The entitity name is extracted from the filename
{
    ofstream f;
    Term t;
    list<Term>::iterator ti;
    unsigned int i;
    string s, s1;
    bool beg;

    if ( !type.f ) throw RuntimeException( "On-set is not specified. Cannot export to VHDL." );
    this->NameVars();
    this->Reindex();
    f.open(fn);
    if (!f.is_open()) throw IOException( "Cannot create a file: " + string(fn) );
    f << "library IEEE;" << endl;
    f << "use IEEE.std_logic_1164.all;" << endl << endl;
    s = fn;
    s1 = s.substr(0, s.find(".", 1));
    f << "entity " << s1 << " is" << endl;
    f << "   port( ";
    for ( i = 0; i < inputs; i++ ) {
        if ( i != 0 ) f << ", ";
        f << inames[i];
    }
    f << ": in std_logic;" << endl;
    f << "         ";
    for ( i = 0; i < outputs; i++ ) {
        if ( i != 0 ) f << ", ";
        f << onames[i];
    }
    f << ": out std_logic);" << endl;
    f << "end " << s1 <<";" << endl << endl;

    f << "architecture PLA of " << s1 << " is" << endl;
    f << "   signal ";
    for ( i = 0; i < data.size(); i++ ) {
        if ( i != 0 ) f << ", ";
        f << "t_" << i;
    }
    f << ": std_logic;" << endl;
    f << "   signal ";
    for ( i = 0; i < inputs; i++ ) {
        if ( i != 0 ) f << ", ";
        f << "n_" << inames[i];
    }
    f << ": std_logic);" << endl << endl;

    f << "begin" << endl;
    for ( i = 0; i < inputs; i++ )
        f << "   n_" << inames[i] << " <= not " << inames[i] << ";" << endl;
    f << endl;
    for ( ti = data.begin(); ti != data.end(); ti++ ) {
        f << "   t_" << this->GetIndex(ti) << " <= ";
        beg = true;
        for ( i = 0; i < inputs; i++ )
            if ( (*ti).GetIM(i) != '-' ) {
                if ( !beg ) f << " and ";
                beg = false;
                if ( (*ti).GetIM(i) == '1' ) f << inames[i];
                else f << "n_" << inames[i];
            }
        f << ";" << endl;

    }
    f << endl;
    for ( i = 0; i < outputs; i++ ) {
        f << "   " << onames[i] << " <= ";
        beg = true;
        for ( ti = data.begin(); ti != data.end(); ti++ ) {
            if ( (*ti).GetOM(i) == '1' ) {
               if ( !beg ) f << " or ";
               beg = false;
               f << "t_" << this->GetIndex(ti);
            }
        }
        f << ";" << endl;
    }
    f << "end PLA;" << endl;
    f.close();
}

unsigned int PLA::GetLiterals(void)
{
    unsigned int l = 0;
    list<Term>::iterator ti;

    for ( ti = data.begin(); ti != data.end(); ti++ )
        l += inputs-(*ti).GetDim();
    return l;
}

unsigned int PLA::GetOC(void) const
{
    unsigned int l = 0;
    list<Term>::const_iterator ti;

    for ( ti = data.begin(); ti != data.end(); ti++ )
        l += (*ti).GetOC();
    return l;
}

unsigned int PLA::GetGEs(void)
// Returns GEs*2
{
	unsigned int ge = 0;
	list<Term>::iterator ti;
	unsigned int i;
	bool flag;
	
	for ( ti = data.begin(); ti != data.end(); ti++ )
		if ( inputs-(*ti).GetDim() > 1 ) ge += inputs-(*ti).GetDim();
	ge += GetOC();
	for ( i = 0; i < outputs; i++ ) {
		flag = false;
		for ( ti = data.begin(); ti != data.end(); ti++ )
			if ( (*ti).GetOM(i) == '1' )
				if ( !flag ) flag = true;
				else break;
		if ( flag && ti == data.end() ) ge--;
	}
	return ge;
}

list<Term> *PLA::GetData(void)
{
    return &data;
}

list<Term> *PLA::GetOnSet(const unsigned int output)
{
    if ( F.empty() ) GenerateOnSet(output);
    else if ( F[output].empty() ) GenerateOnSet(output);
    return &F[output];
}

list<Term> *PLA::GetOffSet(const unsigned int output)
{
    if ( R.empty() ) GenerateOffSet(output);
    else if ( R[output].empty() ) GenerateOffSet(output);
    return &R[output];
}

list<Term> *PLA::GetDCSet(const unsigned int output)
{
    if ( D.empty() ) GenerateDCSet(output);
    else if ( D[output].empty() ) GenerateDCSet(output);
    return &D[output];
}

void PLA::GenerateOnSet(const unsigned int output)
{
    list<Term>::iterator ti;
    Term t;

    if ( !type.f ) throw RuntimeException( "PLA:GenerateOnSet:On-set cannot be computed" );
    if ( F.size() != 0 && F.size() != outputs ) throw RuntimeException( "PLA:GenerateOnSet:Wrong on-set size" );
    if ( F.size() == 0 ) F.resize( outputs );
    F[output].clear();
    for ( ti = data.begin(); ti != data.end(); ti++ )
        if ( (*ti).GetOM(output) == '1' ) {
           t.SetIM((*ti).GetIM());
		   t.SetOM("1");
           F[output].push_back(t);
        }
}

void PLA::GenerateOffSet( const unsigned int output)
{
    list<Term>::iterator ti;
    Term t;

    if ( !type.r ) throw RuntimeException( "PLA:GenerateOffSet: Off-set cannot be computed" );
    if ( R.size() != 0 && R.size() != outputs ) throw RuntimeException( "PLA:GenerateOffSet: Wrong off-set size" );
    if ( R.size() == 0 ) R.resize( outputs );
    R[output].clear();
    for ( ti = data.begin(); ti != data.end(); ti++ )
        if ( (*ti).GetOM(output) == '0' ) {
           t.SetIM((*ti).GetIM());
		   t.SetOM("1");
           R[output].push_back(t);
        }
}

void PLA::GenerateDCSet( const unsigned int output)
{
    list<Term>::iterator ti;
    Term t;

    if ( !type.d ) throw RuntimeException( "PLA:GenerateDCSet: DC-set cannot be computed" );
    if ( D.size() != 0 && D.size() != outputs ) throw RuntimeException( "PLA:GenerateDCSet: Wrong dc-set size" );
    if ( D.size() == 0 ) D.resize( outputs );
    D[output].clear();
    for ( ti = data.begin(); ti != data.end(); ti++ )
        if ( (*ti).GetOM(output) == '-' ) {
           t.SetIM((*ti).GetIM());
		   t.SetOM("1");
           D[output].push_back(t);
        }
}

bool PLA::IsImplicantNoOMCheck( const Term& t, const unsigned int output )
/*
    Returns true if t is an implicant of PLA.
    Off-set must be computable
    Generates off-set, if needed
*/
{
    list<Term>::iterator ti;

    if ( t.GetIMSize() != inputs ) throw RuntimeException( "PLA:IsImplicant: Comparing terms of different sizes" );
    if ( R.empty() ) GenerateOffSet( output );
        else if ( R[output].empty() ) GenerateOffSet( output );
    for ( ti = R[output].begin(); ti != R[output].end(); ti++ )
        if ( (*ti).Intersects(t.GetIM()) ) return false;
    return true;
}

bool PLA::IsImplicant( const Term& t, const unsigned int output )
/*
    Returns true if t is an implicant of PLA.
    Off-set must be computable
    Generates off-set, if needed
    If t.OM[output] is 0, returns false
*/
{
    list<Term>::iterator ti;

    if ( t.GetOM(output) == '0' ) return false;
	return IsImplicantNoOMCheck( t, output );
}

bool PLA::IsImplicant( const Term& t )
/*
    Returns true if t is an implicant of the PLA for all outputs set to '1'.
    Off-set must be computable
    Generates off-set, if needed
    
*/
{
	unsigned int i;

	for ( i = 0; i < outputs; i++ )
		if ( t.GetOM(i) == '1' )
			if ( !IsImplicantNoOMCheck(t, i) ) return false;
	return true;
}

void PLA::SetUnique(void)
// Sets the "unique" attribute. TT is created and checked during term insertion
{
	TermList::iterator ti;

	uniq = true;
    TT.SetInputs( inputs );
	for ( ti = data.begin(); ti != data.end(); ti++ )
		TT.InsertTerm( (*ti).GetIM() );
}

void PLA::SetNonUnique(void)
// Unsets the "unique" attribute. TT is cleared
{
	uniq = false;
	TT.clear();
}

void PLA::ToF(void)
// Converts PLA to F-type. DC-set is discarded
{
	TermList::iterator ti;
	unsigned int i;

	for ( ti = data.begin(); ti != data.end(); ti++ )
		for ( i = 0; i < outputs; i++ )
			if ( (*ti).GetOM(i) != '1' ) (*ti).SetOM( i, '~' );
}

list<Term>::iterator PLA::GetTerm( const unsigned int i )
{
	if ( !indexed ) Reindex();
	return indexterm[i];
}

void PLA::Purge(void)
{
	TermList::iterator ti;

	ti = data.begin();
	while ( ti != data.end() ) 
		if ( (*ti).unwanted ) ti = this->DeleteTerm(ti);
		else ti++;
}

unsigned int PLA::GetIndex( const list<Term>::iterator ti )
{
	if ( !indexed ) Reindex();
	return (*ti).GetIndex();
}

void PLA::MergeTerms(void)
//      Merges terms having equal input matrices, joins output matrices
{
    list<Term>::iterator ti1, ti2;

	if ( type.r ) throw RuntimeException("PLA::MergeTerms:: r-type PLA cannot be processed");
    for ( ti1 = data.begin(); ti1 != data.end(); ti1++ ) {
       ti2 = ti1;
       do {
            ti2++;
            if ( ti2 == data.end() ) break;
       } while ( (*ti1).GetIM() != (*ti2).GetIM() );
       if ( ti2 != data.end() ) {
            (*ti1).Merge(*ti2);
            DeleteTerm(ti2);
       }
    }
}

void PLA::MergeNeighbors(void)
/*
    Repeatitively joins neighboring terms (terms differing in one variable only and having equal OM)
*/
{
    list<Term>::iterator ti1, ti2;
    int diff;
    unsigned int i;
	bool dirty;

	ti1 = data.begin();
	do {
		dirty = false;
		ti2 = ti1;
		ti2++;
		if ( ti2 == data.end() ) return;
		do { 
			if ( (*ti1).GetOM() == (*ti2).GetOM() ) {
			   diff = -1;
			   for ( i = 0; i < inputs; i++ )
				   if ( (*ti1).GetIM(i) != (*ti2).GetIM(i) && ((*ti1).GetIM(i) == '-' || (*ti2).GetIM(i) == '-' )) break;
				   else if ( (*ti1).GetIM(i) != (*ti2).GetIM(i) &&  diff == -1 ) diff = i;
				   else if ( (*ti1).GetIM(i) != (*ti2).GetIM(i) &&  diff != -1 ) break;
			   if ( i == inputs && diff != -1 ) {
				   (*ti1).SetIM( diff, '-' );
				   DeleteTerm(ti2);
				   ti1 = data.begin();
				   dirty = true;
				   break;
			   } else ti2++;
			} else ti2++;
		} while ( ti2 != data.end() );
		if ( !dirty ) ti1++;
	} while ( ti1 != data.end() );
}

void PLA::DumpOrigin( const char *fn )
// for debug purposes.
// Dumps origins of the terms into a file
{
	ofstream f;
	list<Term>::iterator ti1;

	f.open(fn);
	f << "CD-S; FC-Min; IE; IR" << endl;
	for ( ti1 = data.begin(); ti1 != data.end(); ti1++ )
		f << ((*ti1).origin & 1) << "; " << ((*ti1).origin & 2) << "; " << ((*ti1).origin & 4) << "; " << ((*ti1).origin & 8) << endl;
	f.close();
}

list<Term>::iterator PLA::FindTerm( const string t )
// finds a term with equal IM. If not found, returns data.end();
// first occurence is considered
{
	TermList::iterator ti;

	for ( ti = data.begin(); ti != data.end(); ti++ )
		if ( (*ti).GetIM() == t ) break;
	return ti;
}

string PLA::GetIName(const unsigned int i) const
{
	if ( inames.empty() ) return "";
	else return inames[i];
}

string PLA::GetOName(const unsigned int i) const
{
	if ( onames.empty() ) return "";
	else return onames[i];
}

void PLA::SetIName(const unsigned int i, const string name)
{
	if ( inames.empty() ) inames.resize( inputs, "" );
	if ( i >= inputs ) throw RuntimeException( "PLA::SetIName: Incorrect index" );
	inames[i] = name;
}

void PLA::SetOName(const unsigned int i, const string name)
{
	if ( onames.empty() ) onames.resize( outputs, "" );
	if ( i >= outputs ) throw RuntimeException( "PLA::SetOName: Incorrect index" );
	onames[i] = name;
}
