#ifndef TFTiH
#define TFTiH
//---------------------------------------------------------------------------
#include <fstream>
#include <cmath>
#include <iomanip>
//#include <typeinfo.h>

#include "tft.h"
//---------------------------------------------------------------------------

template<class I, class Ipass, class O, class Opass>
class TFTi : public TFT<I,Ipass,O,Opass,TTrans_i<I,Ipass,O,Opass> >
{
public:
  TFTi() : TFT<I,Ipass,O,Opass,TTrans_i<I,Ipass,O,Opass> >() {};
  TFTi(const char* filename)
  : TFT<I,Ipass,O,Opass,TTrans_i<I,Ipass,O,Opass> >() { load(filename); };

  void read(const char* filename);
  void read(istream& is=cin);
  void write(const char* filename);
  void write(ostream& os=cout);
  void load(const char* filename);
  void load(FILE* f=stdin);
  void save(const char* filename);
  void save(FILE* f=stdout);
  void clear();
  using TFT<I,Ipass,O,Opass,TTrans_i<I,Ipass,O,Opass> >::ttn;
  using TFT<I,Ipass,O,Opass,TTrans_i<I,Ipass,O,Opass> >::states;
  using TFT<I,Ipass,O,Opass,TTrans_i<I,Ipass,O,Opass> >::transitions;
  using TFT<I,Ipass,O,Opass,TTrans_i<I,Ipass,O,Opass> >::itype;
  using TFT<I,Ipass,O,Opass,TTrans_i<I,Ipass,O,Opass> >::ftTYPELEN;
  using TFT<I,Ipass,O,Opass,TTrans_i<I,Ipass,O,Opass> >::otype;
  using TFT<I,Ipass,O,Opass,TTrans_i<I,Ipass,O,Opass> >::tt;
  using TFT<I,Ipass,O,Opass,TTrans_i<I,Ipass,O,Opass> >::copy_default;
  using TFT<I,Ipass,O,Opass,TTrans_i<I,Ipass,O,Opass> >::print_mode;


//  friend istream& operator>>(istream&, TFTi<I,Ipass,O,Opass>&);
//  friend ostream& operator<<(ostream&, const TFTi<I,Ipass,O,Opass>&);
};

//---------------------------------------------------------------------------

template<class I, class Ipass, class O, class Opass>
void TFTi<I,Ipass,O,Opass>::read(const char* filename)
{
  ifstream is(filename);
  if(!is) { fprintf(stderr,"Failed to open input file."); exit(1); }
  read(is);
}

template<class I, class Ipass, class O, class Opass>
void TFTi<I,Ipass,O,Opass>::read(istream& is)
{
  long *si;                             // state-index relation
  long ci=0;                            // current index
  char ch;                              // character read;
  int empty=0;                          // no of states with 0 trans?
  char intype[FT::ftTYPELEN];
  char outtype[FT::ftTYPELEN];

  clear();

  is >> states >> transitions >> intype >> outtype;

//  if(strcmp(intype,itype)!=0 ||
//     strcmp(outtype,otype)!=0 && strcmp(outtype,"void")!=0)
//    { is.clear(ios::badbit); goto end; };

  while(is.peek()==' ' || is.peek()=='\t') is.get(ch);
  while(is.peek()!='\n')
  {
    char s[20];
    is >> s;
    if(strcmp(s,"COPY")==0 && strcmp(intype,outtype)==0) copy_default=true;
    else if(strcmp(s,"NOCOPY")==0) copy_default=false;
    else if(strcmp(s,"II")==0) print_mode=FT::II;
    else if(strcmp(s,"OO")==0) print_mode=FT::OO;
    else if(strcmp(s,"IOIO")==0) print_mode=FT::IOIO;
    else if(strcmp(s,"OIOI")==0) print_mode=FT::OIOI;
    else if(strcmp(s,"IIOO")==0) print_mode=FT::IIOO;
    else if(strcmp(s,"OIOI")==0) print_mode=FT::OIOI;
    while(is.peek()==' ' || is.peek()=='\t') is.get(ch);
  }

  ttn=transitions+2;      // 1 state without trans., 1 additional
  si=new long[states];
  tt=new TTrans_i<I,Ipass,O,Opass>[ttn];

  for(long cs=0;cs<states;cs++)
  {
    long tc;                             // transition counter
    si[cs]=ci;
    long cscheck;

    if(!is) goto end;
    while(is.peek()==' ' || is.peek()=='\t') is.get(ch);
    is >> cscheck;
    if(cs!=cscheck) goto end;

    while(is.peek()==' ' || is.peek()=='\t') is.get(ch);

    is.get(ch);
    if(!is) goto end;
    switch(ch)
    {
      case '-': tt[ci].final(false); break;
      case '+': tt[ci].final(true); break;
      default: goto end;
    }
    tc=0, tt[ci].continued(false);

    while(is.peek()==' ' || is.peek()=='\t') is.get(ch);
    while(is && is.peek()!='\n')
    {
      switch(is.peek())
      {
        case '~': tt[ci].epsi(true); tt[ci].defi(true); is.get(ch);
                  break;
        case '@': tt[ci].epsi(false); tt[ci].defi(true); is.get(ch);
                  break;
        default : tt[ci].geti(is);
      }
      if(!is) goto end;
      if(is.peek()=='/')
      {
        is.get(ch);
        switch(is.peek())
        {
          case '~': tt[ci].epso(true); tt[ci].defo(true); is.get(ch);
                    break;
          case '@': tt[ci].epso(false); tt[ci].defo(true); is.get(ch);
                    break;
          default : tt[ci].geto(is);
        }
      }
      else
      {
        tt[ci].defo(true);
        if(copy_default) tt[ci].epso(false); else tt[ci].epso(true);
      }
      if(!is) goto end;

      unsigned long transition;
      is >> transition;
      tt[ci].next(transition);

      tt[ci].continued(false);
      tt[ci].empty(false);

      if(tc>0) tt[ci-1].continued(true);
      tc++,ci++;
    }
    if(tc==0)
    {
      if(++empty>2) { fprintf(stderr, "Nondeterministic automaton."); exit(1); }
      tt[ci].empty(true);
      ci++;
    }
    is.get(ch);
    if(ch!='\n') { is.clear(ios::badbit); goto end; }
  }

  ttn=transitions+empty;
  if(ttn!=ci) { is.clear(ios::badbit); goto end; };
  for(long i=0;i<ttn;i++)
    tt[i].next(si[tt[i].next()]);
  delete[] si;
  sort();

end:
  if(is.bad()) { fprintf(stderr,"Input error."); exit(1); }
}

//---------------------------------------------------------------------------

template<class I, class Ipass, class O, class Opass>
void TFTi<I,Ipass,O,Opass>::write(const char* filename)
{
  ofstream os(filename);
  if(!os) err("Failed to open output file.");
  write(os);
}

template<class I, class Ipass, class O, class Opass>
void TFTi<I,Ipass,O,Opass>::write(ostream& os)
{
  os << states << ' ' << transitions << ' ';
//  os << itype << ' ' << otype << ' ';
  os << "char void";
//  os << (copy_default ? "COPY" : "NOCOPY") << ' ';
//  switch(print_mode)
//  {
//    case FT::II  : os << "II"; break;
//    case FT::OO  : os << "OO"; break;
//    case FT::IOIO: os << "IOIO"; break;
//    case FT::OIOI: os << "OIOI"; break;
//    case FT::IIOO: os << "IIOO"; break;
//    case FT::OOII: os << "OOII";
//  }
  os << '\n';

  long* si=new long[ttn];
  long cs=0;
  for(long i=0;i<ttn;i++)
  {
    si[i]=cs;
    if(continued(i)==false) cs++;
  }

  int statefieldwidth=log10(cs+1);

  bool first=true;
  for(long i=0;i<ttn;i++)
  {
    if(first)
    {
      os << setw(statefieldwidth) << si[i] << " ";
      if(final(i)) os << '+'; else os << '-';
    }


    if(!empty(i))
    {
      os << ' ';
      if(epsi(i))
        os << FT::ftEPSILON;
      else
      if(defi(i))
        os << FT::ftDEFAULT;
      else
        os << input(i);

      if(epso(i))
      { if(copy_default) os << '/' << FT::ftEPSILON; }
      else
      if(defo(i))
      { if(!copy_default) os << '/' << FT::ftDEFAULT; }
      else
      { os << '/' << output(i); }

      if(strcmp(itype,"char")!=0 || strcmp(otype,"char")!=0)
        os << ' ';
      os << si[next(i)];
    }
    if(continued(i))
      first=false;
    else
    { os << '\n'; first=true; }
  }
}

//---------------------------------------------------------------------------

template<class I, class Ipass, class O, class Opass>
void TFTi<I,Ipass,O,Opass>::load(const char* filename)
{
  FILE* f;
  if(*filename)
    f=fopen(filename,"rb");
  else
    f=stdin;
  if(!f) { fprintf(stderr, "Cannot open automaton file."); return; }
  load(f);
}

template<class I, class Ipass, class O, class Opass>
void TFTi<I,Ipass,O,Opass>::load(FILE* f)
{

  clear();

  if(fread(&ttn,sizeof(ttn),1,f)!=1) { fprintf(stderr, "Binary input error."); return;}
  if(fread(&states,sizeof(states),1,f)!=1) { fprintf(stderr, "Binary input error."); return;}
  if(fread(&transitions,sizeof(transitions),1,f)!=1) { fprintf(stderr, "Binary input error."); return;}
  if(fread(itype,sizeof(char),ftTYPELEN,f)!=ftTYPELEN) { fprintf(stderr, "Binary input error."); return;}
  if(fread(otype,sizeof(char),ftTYPELEN,f)!=ftTYPELEN) { fprintf(stderr, "Binary input error."); return;}
  if(fread(&copy_default,sizeof(copy_default),1,f)!=1) { fprintf(stderr, "Binary input error."); return;}
  if(fread(&print_mode,sizeof(print_mode),1,f)!=1) { fprintf(stderr, "Binary input error."); return;}
  if((tt=new TTrans_i<I,Ipass,O,Opass>[ttn])==NULL) { fprintf(stderr, "Cannot allocate memory for tt."); return;}
  if(fread(tt,sizeof(TTrans_i<I,Ipass,O,Opass>),ttn,f)!=ttn) { fprintf(stderr, "Binary input error."); return; }
  fclose(f);


}

//---------------------------------------------------------------------------

template<class I, class Ipass, class O, class Opass>
void TFTi<I,Ipass,O,Opass>::save(const char* filename)
{
  FILE* f;
  if(*filename)
    f=fopen(filename,"wb");
  else
    f=stdout;
  if(!f) err("Cannot open file.");
  save(f);
}

template<class I, class Ipass, class O, class Opass>
void TFTi<I,Ipass,O,Opass>::save(FILE* f)
{
  if(fwrite(&ttn,sizeof(ttn),1,f)!=1) { fprintf(stderr,"Binary output error."); exit(1); }
  if(fwrite(&states,sizeof(states),1,f)!=1) { fprintf(stderr,"Binary output error."); exit(1); }
  if(fwrite(&transitions,sizeof(transitions),1,f)!=1) { fprintf(stderr,"Binary output error."); exit(1); }
  if(fwrite(itype,sizeof(char),ftTYPELEN,f)!=ftTYPELEN) { fprintf(stderr,"Binary output error."); exit(1); }
  if(fwrite(otype,sizeof(char),ftTYPELEN,f)!=ftTYPELEN) { fprintf(stderr,"Binary output error."); exit(1); }
  if(fwrite(&copy_default,sizeof(copy_default),1,f)!=1) { fprintf(stderr,"Binary output error."); exit(1); }
  if(fwrite(&print_mode,sizeof(print_mode),1,f)!=1) { fprintf(stderr,"Binary output error."); exit(1); }
  if(fwrite(tt,sizeof(TTrans_i<I,Ipass,O,Opass>),ttn,f)!=ttn) { fprintf(stderr,"Binary output error."); exit(1); }
  fclose(f);
}

//---------------------------------------------------------------------------

template<class I, class Ipass, class O, class Opass>
void TFTi<I,Ipass,O,Opass>::clear()
{
  if(tt) delete[] tt;
  ttn=0;
}

//---------------------------------------------------------------------------
/*
template<class I, class Ipass, class O, class Opass>
istream& operator>>(istream& is, TFTi<I,Ipass,O,Opass>& ft)
{
  long *si;                             // state-index relation
  long ci=0;                            // current index
  char ch;                              // character read;
  int empty=0;                          // no of states with 0 trans?
  char intype[FT::ftTYPELEN];
  char outtype[FT::ftTYPELEN];

  ft.clear();

  is >> ft.states >> ft.transitions >> intype >> outtype;

  if(strcmp(intype,ft.itype)!=0 ||
     strcmp(outtype,ft.otype)!=0 && strcmp(outtype,"void")!=0)
    { is.clear(ios::badbit); return is; };

  while(is.peek()==' ' || is.peek()=='\t') is.get(ch);
  while(is.peek()!='\n')
  {
    char s[20];
    is >> s;
    if(strcmp(s,"COPY")==0 && strcmp(intype,outtype)==0) ft.copy_default=true;
    else if(strcmp(s,"NOCOPY")==0) ft.copy_default=false;
    else if(strcmp(s,"II")==0) ft.print_mode=FT::II;
    else if(strcmp(s,"OO")==0) ft.print_mode=FT::OO;
    else if(strcmp(s,"IOIO")==0) ft.print_mode=FT::IOIO;
    else if(strcmp(s,"OIOI")==0) ft.print_mode=FT::OIOI;
    else if(strcmp(s,"IIOO")==0) ft.print_mode=FT::IIOO;
    else if(strcmp(s,"OIOI")==0) ft.print_mode=FT::OIOI;
    while(is.peek()==' ' || is.peek()=='\t') is.get(ch);
  }

  ft.ttn=ft.transitions+2;      // 1 state without trans., 1 additional
  si=new long[ft.states];
  ft.tt=new TTrans_i<I,Ipass,O,Opass>[ft.ttn];

  for(long cs=0;cs<ft.states;cs++)
  {
    long tc;                             // transition counter
    si[cs]=ci;
    do is >> ch; while(ch!='+' && ch!='-');
    switch(ch)
    {
      case '-': ft.tt[ci].final(false); break;
      case '+': ft.tt[ci].final(true); break;
      default: return is;
    }
    tc=0, ft.tt[ci].continued(false);
    while((is.get(ch),ch==' '))
    {
      if(!is) return is;
      switch(is.peek())
      {
        case '~': ft.tt[ci].epsi(true); ft.tt[ci].defi(true); is.get(ch);
                  break;
        case '@': ft.tt[ci].epsi(false); ft.tt[ci].defi(true); is.get(ch);
                  break;
        default : ft.tt[ci].geti(is);
      }
      if(!is) return is;
      if(is.peek()=='/')
      {
        is.get(ch);
        switch(is.peek())
        {
          case '~': ft.tt[ci].epso(true); ft.tt[ci].defo(true); is.get(ch);
                    break;
          case '@': ft.tt[ci].epso(false); ft.tt[ci].defo(true); is.get(ch);
                    break;
          default : ft.tt[ci].geto(is);
        }
      }
      else
      {
        ft.tt[ci].defo(true);
        if(ft.copy_default) ft.tt[ci].epso(false); else ft.tt[ci].epso(true);
      }
      if(!is) return is;

      unsigned long transition;
      is >> transition;
      ft.tt[ci].next(transition);

      ft.tt[ci].continued(false);

      ft.tt[ci].empty(false);
      if(tc>0) ft.tt[ci-1].continued(true);
      tc++,ci++;
    }
    if(tc==0)
    {
      if(++empty>2) err("Nondeterministic automaton.");
      ft.tt[ci].empty(true);
      ci++;
    }
    if(ch!='\n') { is.clear(ios::badbit); return is; }
  }

  ft.ttn=ft.transitions+empty;
  if(ft.ttn!=ci) { is.clear(ios::badbit); return is; };
  for(long i=0;i<ft.ttn;i++)
    ft.tt[i].next(si[ft.tt[i].next()]);
  delete[] si;
  ft.sort();
  return is;
}
*/
//---------------------------------------------------------------------------
/*
template<class I, class Ipass, class O, class Opass>
ostream& operator<<(ostream& os, const TFTi<I,Ipass,O,Opass>& ft)
{
  os << ft.states << ' ' << ft.transitions << ' '
     << ft.itype << ' ' << ft.otype << ' ';
  os << (ft.copy_default ? "COPY" : "NOCOPY") << ' ';
  switch(ft.print_mode)
  {
    case FT::II  : os << "II"; break;
    case FT::OO  : os << "OO"; break;
    case FT::IOIO: os << "IOIO"; break;
    case FT::OIOI: os << "OIOI"; break;
    case FT::IIOO: os << "IIOO"; break;
    case FT::OOII: os << "OOII";
  }
  os << ' ' << '\n';

  long* si=new long[ft.ttn];
  long cs=0;
  for(long i=0;i<ft.ttn;i++)
  {
    si[i]=cs;
    if(ft.continued(i)==false) cs++;
  }

  bool first=true;
  for(long i=0;i<ft.ttn;i++)
  {
    if(first)
      if(ft.final(i)) os << '+'; else os << '-';

    if(!ft.empty(i))
    {
      os << ' ';
      if(ft.epsi(i))
        os << FT::ftEPSILON;
      else
      if(ft.defi(i))
        os << FT::ftDEFAULT;
      else
        os << ft.input(i);

      if(ft.epso(i))
      { if(ft.copy_default) os << '/' << FT::ftEPSILON; }
      else
      if(ft.defo(i))
      { if(!ft.copy_default) os << '/' << FT::ftDEFAULT; }
      else
      { os << '/' << ft.output(i); }

      if(strcmp(ft.itype,"char")!=0 || strcmp(ft.otype,"char")!=0)

        os << ' ';
      os << si[ft.next(i)];
    }
    if(ft.continued(i))
      first=false;
    else
    { os << '\n'; first=true; }
  }
  return os;
}
*/
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

template<class I, class O>
class TFTiv : public TFTi<I,I,O,O>
{
public:
  TFTiv() : TFTi<I,I,O,O>() {};
  TFTiv(const char* filename) : TFTi<I,I,O,O>(filename) {};
};

//---------------------------------------------------------------------------

template<class I, class O>
class TFTir : public TFTi<I,I&,O,O&>
{
public:
  TFTir() : TFTi<I,I,O,O>() {};
};

//---------------------------------------------------------------------------
#endif
