#include <fstream.h>
#include <iomanip.h>
#include <string.h>
#include <time.h>



/*-------------------------------------------------------------------------
   LineFile : a class to manage a file of lines of fixed length.
   version 1 : 17-05-02, University  of Crete, Paris Pamfilos 
    version 2 : 21-05-02  append even if user forgot 'a'

   TODO
   security ??? (io-errors ??) (line-length ??)
   count characters on entry and replace line with two if too long?
   change max line-length of catalog
   order catalog
   interchange two lines
   insert line at
   number lines 
   phone catalog   specialize
   folder catalog  specialize
   add abstraction level user interface, hidding details of io
   use multimap with key the name and value the rest of string
---------------------------------------------------------------------------*/

class  LineFile
{
private:
   enum
   {
      MXCH           = 1024,  // max line width
      MAIN_LINE_WIDTH      = 74,  // actually used line width
      MARGIN            = 5,
      TOTAL_WIDTH       = MAIN_LINE_WIDTH + MARGIN + 1
   };
   char  fName[100];    // name of catalog
   char  line[MXCH];    // line buffer
   char  myTime[20];    // store time stamp
   int     wordLineStart;  // line to start search
   bool    wordFound;
   string  wordSearch; // word to search for

public:
   LineFile();
   LineFile( char* fname ) ;
   ~LineFile();

   int    AcceptDelete();
   void AppendLine();
   void AppendLineEndFile();
   void DeleteLine();
   void DisplayNrCommentLine( int n, char* comment );
   int    FindLineWord( string & word , int lineToStart);
   int    GetLineLength() ;
   int    GetAnInteger();
   void GetThisLine( int count );
   void GetWordSearchFor( string & str );
   void InitLineFile();
   char MenuSelection();
   void NewWordSearch();
   int  NumOfLines();
   void OldWordSearch();
   void OpenSeekLine( fstream & kata, int count, bool toRead=true );
   void OverideFileWithBuffer( char * pch, unsigned int size );
   void PrintLines( );
   void PutInBuffer( char* pch, unsigned int offset, unsigned int size );
   void ReplaceLine( );
   void ShowLine( );
   void ShowLineFound( int lineFound );
   void TimeString();
   int  WordFoundInLine( string & word );
   void WriteLine( fstream & kata );
   void WriteMessage( char* mess );
};


/*-------------------------------------------------------------------------
   construct ,  destruct
---------------------------------------------------------------------------*/
LineFile::LineFile( char* fname ) { strcpy(fName,fname); InitLineFile(); }
LineFile::LineFile(){ InitLineFile(); }
LineFile::~LineFile(){}

/*-------------------------------------------------------------------------
   Ask user for deletion and check
---------------------------------------------------------------------------*/
int LineFile::AcceptDelete()
{
   int n = GetAnInteger();             // get line-nr from user
   GetThisLine(n);
   DisplayNrCommentLine(n, "-line delete ?? (y = yes )" );
   cin.getline(line,MXCH);                // get line from cin
   if( line[0] != 'y' ) n = 0;
   return n;
}

/*-------------------------------------------------------------------------
   Ask user for a line and append to end of file
---------------------------------------------------------------------------*/
void LineFile::AppendLine( )
{
   WriteMessage( "write a line") ;     // WARNING check length ?????
   cin.getline(line,MXCH);          // get line from cin
   AppendLineEndFile();
}

/*-------------------------------------------------------------------------
   store content of <line> as last line
---------------------------------------------------------------------------*/
void LineFile::AppendLineEndFile()
{
   fstream  kata(fName, ios::app);      // open to append 
   WriteLine( kata );
   kata.close();
}

/*-------------------------------------------------------------------------
   delete   line from file, use <ios::binary> to save white space
   The line splits the file in two parts of  (n-1) lines before and
   (N-n) lines after the deleted line. 
---------------------------------------------------------------------------*/
void LineFile::DeleteLine( )
{
   unsigned int n = (unsigned int)AcceptDelete();  // ask user etc...
   if(n == 0 ) return ;        // user canceled ... do nothing

   unsigned int    N       = NumOfLines();   // find total lines
   unsigned int   totpos      = (N-1)*TOTAL_WIDTH; // size after deletion
   unsigned int   beforeSize  = (n-1)*TOTAL_WIDTH; // size before line
   unsigned int   afterSize   = totpos-beforeSize; // size after line

   char *  pch  = new char[ totpos ];      // to receive the new text
   if(n>1)                          // if not first line delete
      PutInBuffer( pch, 0, beforeSize );   // store upper part to buffer
   if(n != N)                       // i.e. if not last line to delete
      PutInBuffer( pch+beforeSize, n*TOTAL_WIDTH,  afterSize );
                                // store lower part to buffer
   OverideFileWithBuffer( pch, totpos ); // write back buffer to file
   delete[] pch;                   // WARNING  memory ???
}

/*-------------------------------------------------------------------------
   display n-th number with a comment and the <line>
---------------------------------------------------------------------------*/
void LineFile::DisplayNrCommentLine( int n, char* comment )
{
   WriteMessage("-----------");
   cout << n << comment << endl <<line << endl;
   WriteMessage("-----------");
}

/*-------------------------------------------------------------------------
   Find first occurence of <word> in lines , starting with <lineToStart> 
   line. Returns the line-nr containing or -1 if not found.
---------------------------------------------------------------------------*/
int LineFile::FindLineWord( string & word , int lineToStart)
{
   fstream kata;
   OpenSeekLine( kata, lineToStart );  // open and go to start of <lineToStart>
   int n(lineToStart);
   int result;
   while( kata.getline( line, MXCH ) )  // read line from file
   {
      result = (int)WordFoundInLine( word ) ;         // test if word there
      if( result > -1 ){ kata.close(); return n;}
      n++;
   }
   kata.close();
   return -1;
}

/*-------------------------------------------------------------------------
---------------------------------------------------------------------------*/
   int    LineFile::GetLineLength() { return strlen(line);}

/*-------------------------------------------------------------------------
   Get an integer from user, adjust it to be inside the range of existing
   lines of the file
---------------------------------------------------------------------------*/
int  LineFile::GetAnInteger()
{
   int N = NumOfLines();
   cout << "Give an integer  0 < n <= " << N << endl;
   cin.getline(line,MXCH);
   int n = atoi(line);           // transform to int
   if( (n<=N) && ( n > 0) ) return n; // normal case
   else if( n <=0 ) return 1;   // if <0 replace by  1
   else return N;          // if > N replace by  N
}

/*-------------------------------------------------------------------------
   Store into <line> the count-line of the file
---------------------------------------------------------------------------*/
void  LineFile::GetThisLine( int count )
{
   fstream kata;
   OpenSeekLine(kata, count);
   kata.getline( line, MXCH );
   kata.close();
}


/*-------------------------------------------------------------------------
   Ask user to give a word to search for. Use whole line to avoid 
   interfer with redundant words (after the search word)
   uses the one-time initalized char* <seps> (separators of words)
---------------------------------------------------------------------------*/
void  LineFile::GetWordSearchFor( string &  str )
{
   static  char seps[]   = " ,\t\n";

   WriteMessage(" Give a word to search for:");
   cin.getline( line, MXCH );       // get a line from user 
   if(strlen(line)==0) str = "****";
   else
    str = strtok( line, seps );        // its first word (token)
}

/*-------------------------------------------------------------------------
   used to initialize the class and print useful info on file status
---------------------------------------------------------------------------*/
void LineFile::InitLineFile()
{
   wordFound = false;
   WriteMessage("-----------");
   cout << "line width =  " << TOTAL_WIDTH << endl;
   cout << NumOfLines() << " lines in file: " << fName << endl;
   WriteMessage("-----------");
}

/*-------------------------------------------------------------------------
   Write the menu and get user's selection
---------------------------------------------------------------------------*/
char   LineFile::MenuSelection()
{
   WriteMessage("<MENU>\n AddLine (a)  , DeleteLine (d) , PrintLines (p) , ReplaceLine (r)");
   WriteMessage(" ShowLine (s) , WordSearch (f) , AgainSearch (g) , Quit (q)");
   WriteMessage("</MENU>");
   cin.getline(line,MXCH); // eat all the line but use only  1st char
   if( GetLineLength() >10 )  // append !! *** user forgot to press a before !!
   {
      AppendLineEndFile();
   }
   return line[0];
}

/*-------------------------------------------------------------------------
   Start new search for a word stored in <wordSearch>
---------------------------------------------------------------------------*/
void  LineFile::NewWordSearch()
{
   wordFound=false;              // not found yet
   wordLineStart=1;              // line to start search
   GetWordSearchFor( wordSearch );      // get from user
   if( wordSearch.length() != 0 )      // is a real word ??
   {
      int lineFound = FindLineWord( wordSearch , wordLineStart);
      if( lineFound >-1 )           // if some line found
      {
         wordFound = true;       // prepare for next search
         ShowLineFound( lineFound );
      }
      else //nothing found
      {
         cout << "passed end of file" << endl;  // inform user that no other line ...
      }
   }
}


/*-------------------------------------------------------------------------
   Open the file and calc its number of lines  WARNING 
---------------------------------------------------------------------------*/
int LineFile::NumOfLines()
{
   fstream kata(fName, ios::binary  | ios_base::in );
   kata.seekg(0,ios_base::end);     // set ptr to end of file
   int  n ( kata.tellg()/TOTAL_WIDTH); // this is the total line-nr
   kata.close();
   return n;
}

/*-------------------------------------------------------------------------
   Continue searching in other lines for the word: <wordSearch>
   This function continues the work started by  NewWordSearch()
---------------------------------------------------------------------------*/
void  LineFile::OldWordSearch()
{
   if( wordFound )               // something has been found previously
   {
      int lineFound;
      if( !(wordLineStart<NumOfLines()) ||
         (lineFound = FindLineWord( wordSearch , wordLineStart+1)) == -1 )
      {
         cout << "passed end of file" << endl;  // nothing new found 
         wordLineStart = 1;                  // to restart search
      }
      else
      {
         ShowLineFound( lineFound );
      }
   }
   else
      NewWordSearch();  // start a new search
}


/*-------------------------------------------------------------------------
   open file and seekg (to read) seekp (to write) at the beginning of count-line
---------------------------------------------------------------------------*/
void LineFile::OpenSeekLine( fstream & kata, int count, bool toRead /*true*/ )
{
   fstream::pos_type pos((count-1)*TOTAL_WIDTH);
   kata.open(fName, ios_base::in | ios_base::out  );  // WARNING errors ??
   if(toRead) kata.seekg(pos);
   else kata.seekp(pos);
}

/*-------------------------------------------------------------------------
---------------------------------------------------------------------------*/
void LineFile::OverideFileWithBuffer( char * pch, unsigned int size )
{
   ofstream  kata(fName, ios::binary | ios_base::trunc );
   kata.write( pch, size );
   kata.close();
}


/*-------------------------------------------------------------------------
   Print on screen all lines of the file
---------------------------------------------------------------------------*/
void LineFile::PrintLines()
{
   int n(0);
   WriteMessage("-----------");
   ifstream kata;             // open file to read from
   kata.open(fName);
   while( kata.getline( line, MXCH ) )
   {
      cout << line << endl;
      n++;
   }
   WriteMessage("-----------");
   cout << n << " lines in " << fName << endl;
}

/*-------------------------------------------------------------------------
   Put in buffer <size> bytes from file, start reading in file from <offset>
---------------------------------------------------------------------------*/
void LineFile::PutInBuffer( char* pch, unsigned int offset, unsigned int size )
{
   fstream kata(fName, ios::binary | ios_base::out | ios_base::in );
   kata.seekg(offset);        // start here reading from file
   kata.read( pch, size );    // ... put <size> bytes in pch
   kata.close();
}

/*-------------------------------------------------------------------------
   Replace a line of file with a new one 
---------------------------------------------------------------------------*/
void LineFile::ReplaceLine(  )
{
   int n = AcceptDelete();       // ask user etc...
   if(n == 0) return;            // user canceled
   WriteMessage("Write new line");
   cin.getline(line,MXCH);       // get line from user
   fstream kata;
   OpenSeekLine(kata, n, false );   // false = to write
   WriteLine( kata );
   kata.close();
}
/*-------------------------------------------------------------------------
   Show line by its line-number
---------------------------------------------------------------------------*/
void LineFile::ShowLine( )
{
   int n = GetAnInteger();
   GetThisLine(n);
   DisplayNrCommentLine( n , "-line is:" );
}


/*-------------------------------------------------------------------------
   Show line found on search request
---------------------------------------------------------------------------*/
void  LineFile::ShowLineFound( int lineFound )
{
   wordLineStart = lineFound;  // update line-count for next search
   GetThisLine( lineFound );
   string comment("-line contains word < ");
   comment += wordSearch;
   comment += " > ";
   DisplayNrCommentLine( lineFound, const_cast<char*>( comment.data() ) );
}

/*-------------------------------------------------------------------------
   Get actual time and trasforme to a date-stamp in <myTime> string
---------------------------------------------------------------------------*/
void LineFile::TimeString()
{
    time_t long_time;      // get time is seconds
    time( &long_time );

    struct tm *newtime;    // to format time-info
    newtime = localtime( &long_time ); // transform to date

   sprintf(myTime, "%2d/%2d/%2d ", newtime->tm_mday ,
                           newtime->tm_mon+1,
                           newtime->tm_year-100 );
}

/*-------------------------------------------------------------------------
   returns the offset or -1 in not found
---------------------------------------------------------------------------*/
int LineFile::WordFoundInLine( string & word )
{
   string   L(line);
   return L.find( word );
}

/*-------------------------------------------------------------------------
   Write content of <line>, fill with <space> + set time stamp + <new-line>
   to complete a new line in file.
---------------------------------------------------------------------------*/
void LineFile::WriteLine( fstream & kata )
{
   kata << line <<  setw(MAIN_LINE_WIDTH-GetLineLength())  << myTime
                                    << setw(MARGIN) << "**\n";
}

/*-------------------------------------------------------------------------
---------------------------------------------------------------------------*/
void  LineFile::WriteMessage( char* mess )
{
   cout << mess << endl;
}


/*-------------------------------------------------------------------------
---------------------------------------------------------------------------*/

int main(int argc, char* argv[])
{
   LineFile  lf("kat.txt");
   lf.TimeString();
   char ch(1);
   while( ch != 'q' )
   {
      ch = lf.MenuSelection();
      switch(ch)
      {
      case  'a':
         lf.AppendLine();
         break;
      case  'd':
         lf.DeleteLine();
         break;
      case  'p':
         lf.PrintLines();
         break;
      case  'r':
         lf.ReplaceLine();
         break;
      case  's':
         lf.ShowLine();
         break;
      case  'f':
         lf.NewWordSearch();
         break;
      case  'g':
         lf.OldWordSearch();
         break;
      case  'q':   //exit
         break;
      default:
         cout << "Error, please type only one of: a, d, p, r, s, f, g and q = quit" << endl;
         break;
      }
   }
}



Σημείωσε ότι γιά την μετατροπή του κώδικα σε κείμενο HTML χρησιμοποίησα το ελεύθερα διατιθέμενο πρόγραμμα μετατροπής Lore's source converter.