// ------------------------------- //
// -------- Start of File -------- //
// ------------------------------- //
// ----------------------------------------------------------- // 
// C++ Source Code File Name: ffind.cpp 
// Compiler Used: MSVC40, DJGPP 2.7.2.1, GCC 2.7.2.1, HP CPP 10.24
// Produced By: gaer@nhc.noaa.gov
// File Creation Date: 03/09/1999 
// Date Last Modified: 03/26/1999
// ----------------------------------------------------------- // 
// ------------- Program description and details ------------- // 
// ----------------------------------------------------------- // 
/*
THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
THE ENTIRE RISK OF THE QUALITY AND PERFORMANCE OF THIS SOFTWARE
IS WITH YOU. SHOULD ANY ELEMENT OF THIS SOFTWARE PROVE DEFECTIVE,
YOU WILL ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR
CORRECTION.
  
This program is used to find and replace strings in a file.
*/
// ----------------------------------------------------------- // 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fstream.h>
#include <iostream.h>
#include "strutil.h"
#include "ustring.h"
#include "dllist.h"

// Version number and program name
const double FFindVersionNumber = 1031.102;
const char *ProgramName = "ffind";

// Program globals
const int MAX_LINE = 1024;   // Maximum characters per line
char *open_file = 0;         // Name of file currently opened
unsigned num_files = 0;      // Total number of files processed
char *string_to_find = 0;    // String to find in each line
int find_string = 0;         // Find specified string flag
int find_all = 0;            // Find all occurrences
int check_case = 1;          // Case sensitive/insensitive compare flag
unsigned num_matches = 0;    // Total number of matches found
int cat_file = 0;            // Concatenate file flag
char *string_to_replace = 0; // String to replace
char *string_to_insert = 0;  // String to insert
int replacing_string = 0;    // Replace string flag
int num_replaced = 0;        // Number of strings replaced
int replacing_line = 0;      // Replace line flag
int inserting_string = 0;    // Insert line flag
int append_lines = 1;        // Append lines connected with a backslash
int num_processed = 0;       // Number of string processed per file

void HelpMessage(const char *program_name, const double version_number)
{
  char vbuffer[255];
  sprintf(vbuffer, "%.3f", version_number);
  cout << endl;
  cout << program_name << " program version "
       << vbuffer  << endl;
  cout << "Usage: " << program_name << " [switches] iofile.txt " << endl; 
  cout << "Switches: " << endl;
  cout << "          -a = Do not append lines connected by a backslash"
       << endl;
  cout << "          -c = Concatenate file to stdout." << endl;
  cout << "          -f = Find first occurrence of specified string: "
       << "-f\"string\"" << endl;
  cout << "          -F = Find all occurrences of specified string: "
       << "-F\"string\"" << endl;
  cout << "          -i = Perform case insensitive compare." << endl;
  cout << endl;
  cout << "          -R -W = Replace all occurrences of a string in a file."
       << endl;
  cout << "          Usage: -R\"string\" -W\"replacement\"" << endl;
  cout << endl;
  cout << "          -L -W = Replace entire line after the specifed string."
       << endl;
  cout << "          Usage: -L\"string\" -W\"replacement\"" << endl;
  cout << endl;
  cout << "          -I -W = Insert a line after the specifed string."
       << endl;
  cout << "          Usage: -I\"string\" -W\"insert\"" << endl;
  cout << endl;
  exit(0);
}

int ProcessArgs(char *arg)
{
  switch(arg[1]) {
    case 'a':
      append_lines = 0;
      break;
      
    case 'c':
      cat_file = 1;
      break;

    case 'f':				
      if(arg[2] == '\0') {
	cerr << endl;
	cerr << "You must enter a string following the -f option." << endl;
	cerr << "Example usage: " << ProgramName << " -f\"string\"" << endl; 
	cerr << endl;
	return 0;
      }
      find_string = 1;
      find_all = 0;
      replacing_line = 0;  
      replacing_string = 0;  
      inserting_string = 0;
      string_to_find = arg+2;
      break;

    case 'F':				
      if(arg[2] == '\0') {
	cerr << endl;
	cerr << "You must enter a string following the -F option." << endl;
	cerr << "Example usage: " << ProgramName << " -F\"string\"" << endl; 
	cerr << endl;
	return 0;
      }
      find_string = 1;
      find_all =1;
      replacing_line = 0;  
      replacing_string = 0;  
      inserting_string = 0;
      string_to_find = arg+2;
      break;

    case 'i':
      check_case = 0;
      break;
      
    case 'R':
      if(arg[2] == '\0') {
	cerr << endl;
	cerr << "You must enter a string following the -R option." << endl;
	cerr << "Example usage: " << ProgramName << " -R\"string\"" << endl; 
	cerr << endl;
	return 0;
      }
      string_to_replace = arg+2;
      replacing_string = 1;  
      replacing_line = 0;
      inserting_string = 0;
      find_string = 0;
      find_all = 0;
      break;

    case 'L':
      if(arg[2] == '\0') {
	cerr << endl;
	cerr << "You must enter a string following the -L option." << endl;
	cerr << "Example usage: " << ProgramName << " -L\"string\"" << endl; 
	cerr << endl;
	return 0;
      }
      string_to_replace = arg+2;
      replacing_line = 1;  
      replacing_string = 0;
      inserting_string = 0;
      find_string = 0;
      find_all = 0;
      break;

    case 'I':
      if(arg[2] == '\0') {
	cerr << endl;
	cerr << "You must enter a string following the -I option." << endl;
	cerr << "Example usage: " << ProgramName << " -I\"string\"" << endl; 
	cerr << endl;
	return 0;
      }
      string_to_replace = arg+2;
      inserting_string = 1;
      replacing_line = 0;  
      replacing_string = 0;  
      find_string = 0;
      find_all = 0;
      break;

    case 'W':
      if(arg[2] == '\0') {
	cerr << endl;
	cerr << "You must enter a string following the -W option." << endl;
	cerr << "Example usage: " << ProgramName << " -W\"string\"" << endl; 
	cerr << endl;
	return 0;
      }

      if(string_to_replace == 0) {
	cerr << endl;
	cerr << "The -W option must be used with the -R, -L, or -I option."
	     << endl;
	cerr << "Usage: -R\"string\" -W\"replacement\"" << endl;
	cerr << endl;
	return 0;
      }

      string_to_insert = arg+2;
      break;
      
    default:
      cerr << endl;
      cerr << "Unknown switch " << arg << endl;
      cerr << "Exiting..." << endl;
      cerr << endl;
      return 0;
  }
  arg[0] = '\0';
  return 1; // All command line arguments were valid
}

int ProcessTextFile(fstream &iofile, ostream &stream)
{
  char LineBuffer[MAX_LINE];
  char rawLineBuffer[MAX_LINE];
  UString LineData;
  UString appendLineBuffer;
  DLList<UString> list;
  int i, j, rv, offset = 0;
  unsigned long line_number = 0;
  
  while(!iofile.eof()) { // Read in the file line by line

    // Clear the buffers
    for(i = 0; i < MAX_LINE; i++) LineBuffer[i] = '\0';
    for(i = 0; i < MAX_LINE; i++) rawLineBuffer[i] = '\0';
    LineData.DeleteAt(0, LineData.length());
    offset = 0; // Reset string offset
    
    iofile.getline(rawLineBuffer, MAX_LINE);
    line_number++;
        
    // Filter each line of text
    for(i = 0, j = 0; i < MAX_LINE; i++) {
      if((rawLineBuffer[i] == '\r') || (rawLineBuffer[i] == '\n')) break;
      LineBuffer[j++] = rawLineBuffer[i];
    }

    // Detect backslashes used to mark the continuation of a line
    if((LineBuffer[strlen(LineBuffer)-1] == '\\') && (append_lines == 1)) {
      appendLineBuffer += LineBuffer;
      appendLineBuffer += '\n'; // Insert LF after the backslash 
      continue;  // Goto the top of loop and append next line
    }
    if(appendLineBuffer.length() > 0) { // Append the next line
      appendLineBuffer += LineBuffer;
      LineData = appendLineBuffer;
      appendLineBuffer.DeleteAt(0, appendLineBuffer.length());
    }
    else
      LineData = LineBuffer;
    
    // Routine to cat file
    // ----------------------------------------------------------- 
    if(cat_file) {
      if(!iofile.eof()) // Get rid of EOF marker
	stream << LineData << endl;
      else
	stream << LineData;
    }
    
    // Routine to find a string
    // ----------------------------------------------------------- 
    if(string_to_find != 0 && find_string != 0) {
      if(find_all) {
	while(1) {
	  if(check_case)
	    offset = LineData.Find(string_to_find, offset);
	  else
	    offset = LineData.IFind(string_to_find, offset);
	  if(offset == UString::NoMatch) break;
	  num_matches++; num_processed++;
	  offset++;
	}
      }
      else {
	if(check_case)
	  rv = LineData.Find(string_to_find);
	else
	  rv = LineData.IFind(string_to_find);
	if(rv != UString::NoMatch) {
	  num_matches++; num_processed++;
	  cout << "Found a match on line #" << line_number << endl;
	}
      }
    }

    // Get rid of EOF marker
    if((strcmp(LineData.c_str(), "\0") == 0) && (iofile.eof())) break;

    // Routine to replace a string
    // ----------------------------------------------------------- 
    if(replacing_string) {
      while(1) {
	if(check_case)
	  offset = LineData.Find(string_to_replace, offset);
	else
	  offset = LineData.IFind(string_to_replace, offset);
	if(offset == UString::NoMatch) break;
	LineData.DeleteAt(offset, (strlen(string_to_replace)));
	LineData.InsertAt(offset, string_to_insert);
	num_replaced++;  // Update global replacement counter
	num_processed++; // Record number of string processed for this file
	offset += strlen(string_to_insert);
      }
      list.StoreNode(LineData); // Store all the lines
    }
    
    // Routine to replace a line
    // ----------------------------------------------------------- 
    if(replacing_line) {
      if(check_case)
	offset = LineData.Find(string_to_replace, offset);
      else
	offset = LineData.IFind(string_to_replace, offset);

      if(offset != UString::NoMatch) {
	offset += strlen(string_to_replace);
	LineData.DeleteAt(offset, (LineData.length() - offset));
	LineData.InsertAt(offset, string_to_insert);
	num_replaced++;  // Update global replacement counter
	num_processed++; // Record number of string processed for this file
      }
      list.StoreNode(LineData); // Store all the lines 
    }
    
    // Routine to insert a line
    // ----------------------------------------------------------- 
    if(inserting_string) {
      if(check_case)
	offset = LineData.Find(string_to_replace, offset);
      else
	offset = LineData.IFind(string_to_replace, offset);

      if(offset != UString::NoMatch) {
	offset += strlen(string_to_replace);
	LineData.InsertAt(offset, string_to_insert);
	num_replaced++;  // Update global replacement counter
	num_processed++; // Record number of string processed for this file
      }
      list.StoreNode(LineData); // Store all the lines 
    }
  } 

  // If any strings were replaced in the file, rewrite the file
  // ----------------------------------------------------------- 
  if(num_replaced > 0) {
    char *EndOfLineSequence;
    iofile.close(); 
#if defined(__DOS__)
    // In DOS/Windows there are two file types, text and binary
    fstream iofile(open_file, ios::out|ios::trunc|ios::binary);
    EndOfLineSequence = "\r\n"; 
#else 
    // In UNIX there is only one file type
    fstream iofile(open_file, ios::out|ios::in|ios::trunc);
    EndOfLineSequence = "\n"; 
#endif
    
    if(!iofile) {
      cerr << endl;
      cerr << "Cannot write to: " << open_file << endl;
      cerr << "Exiting..." << endl;
      cerr << endl;
      exit(0);
    }

    DNode<UString> *list_ptr = list.GetFront();
    while(!list.IsHeader(list_ptr)) {
      iofile.write(list_ptr->Data.c_str(), list_ptr->Data.length());
      iofile.write(EndOfLineSequence, strlen(EndOfLineSequence));
      list_ptr = list_ptr->GetNext(); 
    }

    iofile.close();
  }

  list.Clear();
  return 1;
}

// Program's main thread of execution.
// ----------------------------------------------------------- 
int main(int argc,     // Number of strings in array argv.
	 char *argv[], // Array of command-line argument strings.
	 char *envp[]) // Array of environment variable strings.
// NOTE: None of the MSVC compilers will expand wildcard characters
// used in command-line arguments unless linked with the setargv.obj
// library. All the UNIX compliers will expand wildcard characters
// by default.
{
  // If no arguments are given print usage message to the screen 
  if(argc < 3) {
    HelpMessage(ProgramName, FFindVersionNumber);
    return 0;
  }

  // Process command ling arguments and files 
  int narg;
  char *arg = argv[narg = 1];
  while (narg < argc) {
    if (arg[0] != '\0') {
      if (arg[0] == '-') { // Look for command line arguments
	if(!ProcessArgs(arg)) return 0; // Exit if argument is not valid
      }
      else { 
	open_file = arg; // Update the open file name pointer
#if defined(__DOS__) 
	// In MS-DOS there are two file types, text and binary
	fstream iofile(open_file, ios::in|ios::nocreate|ios::binary);
#else 
	// In UNIX there is only one file type
	fstream iofile(open_file, ios::in|ios::nocreate);
#endif
	if(!iofile) {
	  cerr << endl;
	  cerr << "Cannot open file: " << open_file << endl;
	  cerr << "Exiting..." << endl;
	  cerr << endl;
	  return 0;
	}
	num_files++;

	// Echo the name of the file and search operation
	// ----------------------------------------------------------- 
	if(replacing_string == 1) {
	  if(string_to_insert == 0) {
	    cerr << endl;
	    cerr << "The -R option must be used with the -W option." << endl;
	    cerr << "Usage: -R\"string\" -W\"replacement\"" << endl;
	    cerr << endl;
	    return 0;
	  }
	  num_processed = 0; // Number of string processed for this file
	  cout << endl;
	  cout << "Opening file: " << open_file << endl;
	  cout << "Replacing: " << string_to_replace << endl;
	  cout << "With: " << string_to_insert << endl;
	}

	if(replacing_line) {
	  if(string_to_insert == 0) {
	    cerr << endl;
	    cerr << "The -L option must be used with the -W option." << endl;
	    cerr << "Usage: -L\"string\" -W\"replacement\"" << endl;
	    return 0;
	  }
	  num_processed = 0; // Number of string processed for this file
	  cout << endl;
	  cout << "Opening file: " << open_file << endl;
	  cout << "Replacing line after: " << string_to_replace << endl;
	  cout << "With: " << string_to_insert << endl;
	}

	if(inserting_string ) {
	  if(string_to_insert == 0) {
	    cerr << endl;
	    cerr << "The -I option must be used with the -W option." << endl;
	    cerr << "Usage: -I\"string\" -W\"insert\"" << endl;
	    cerr << endl;
	    return 0;
	  }
	  num_processed = 0; // Number of string processed for this file
	  cout << endl;
	  cout << "Opening file: " << open_file << endl;
	  cout << "Inserting: " << string_to_replace << endl;
	  cout << "After: " << string_to_insert << endl;
	}

	if(find_string) {
	  num_processed = 0; // Number of string processed for this file
	  cout << endl;
	  cout << "Opening file: " << open_file << endl;
	  cout << "Seraching for: " << string_to_find << endl;
	}

	// Process the test file
	num_matches = 0; num_replaced = 0; num_processed = 0;
	ProcessTextFile(iofile, cout);
	if(num_files) iofile.close();
	
	// Report the total number of strings replaced/inserted 
	// ----------------------------------------------------------- 
	if(replacing_string) {
	  cout << "Replaced " << num_processed << " occurrence(s)." << endl;
	  cout << endl;
	}

	if(replacing_line) {
	  cout << "Replaced " << num_processed << " occurrence(s)." << endl;
	  cout << endl;
	}

	if(inserting_string ) {
	  cout << "Inserted " << num_processed << " line(s)." << endl;
	  cout << endl;
	}

	if(find_string) {
	  if(find_all) {
	    cout << "Found " << num_processed << " matches" << endl;
	    cout << endl;
	  }
	  if(num_matches == 0) { // Used by -f command if search fails
	    cout << "No matches found." << endl;
	    cout << endl;
	  }
	}
      }
      arg = argv[++narg];
    }
  }
  
  if(num_files == 0) {
    cerr << endl;
    cerr << "You must enter a file name." << endl;
    cerr << "Exiting..." << endl;
    cerr << endl;
    return 0;
  }
  
  // Return the total number of strings replaced/inserted 
  // ----------------------------------------------------------- 
  if(replacing_string) return num_replaced;
  if(replacing_line) return num_replaced;
  if(inserting_string ) return num_replaced;
  if(find_string) return num_matches;

  return 0;
}
// ----------------------------------------------------------- // 
// ------------------------------- //
// --------- End of File --------- //
// ------------------------------- //
