/*  Zcat - decompress and concatenate files to standard output
    Copyright (C) 2010, 2011 Antonio Diaz Diaz.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

struct Cat_options
  {
  int number_lines;		// 0 = no, 1 = nonblank, 2 = all
  bool show_ends;
  bool show_nonprinting;
  bool show_tabs;
  bool squeeze_blank;

  Cat_options() throw()
    : number_lines( 0 ), show_ends( false ), show_nonprinting( false ),
      show_tabs( false ), squeeze_blank( false ) {}
  };


class Line_number		// unlimited size line counter
  {
  std::string str;
  int first_digit_pos;

public:
  Line_number() : str( "     0\t" ), first_digit_pos( 5 ) {}

  void next()
    {
    for( int i = str.size() - 2; i >= first_digit_pos; --i )
      {
      if( str[i] < '9' ) { ++str[i]; return; }
      str[i] = '0';
      }
    if( first_digit_pos > 0 ) str[--first_digit_pos] = '1';
    else str.insert( 0U, 1, '1' );
    }

  int sprint( uint8_t * const buf )
    {
    std::memcpy( buf, str.c_str(), str.size() );
    return str.size();
    }
  };

Line_number line_number;


void show_zcat_help() throw()
  {
  std::printf( "Zcat copies each given file (\"-\" means standard input), to standard\n" );
  std::printf( "output. If any given file is compressed, its uncompressed content is\n" );
  std::printf( "used. If a given file does not exist, and its name does not end with one\n" );
  std::printf( "of the known extensions, zcat tries the compressed file names\n" );
  std::printf( "corresponding to the supported compressors. If no files are specified,\n" );
  std::printf( "data is read from standard input, decompressed if needed, and sent to\n" );
  std::printf( "standard output. Data read from standard input must be of the same type;\n" );
  std::printf( "all uncompressed or all compressed with the same compressor.\n" );
  std::printf( "The supported compressors are bzip2, gzip, lzip and xz.\n" );
  std::printf( "\nUsage: zcat [options] [files]\n" );
  std::printf( "\nExit status is 0 if no errors occurred, 1 otherwise.\n" );
  std::printf( "\nOptions:\n" );
  std::printf( "  -h, --help                 display this help and exit\n" );
  std::printf( "  -V, --version              output version information and exit\n" );
  std::printf( "  -A, --show-all             equivalent to `-vET'\n" );
  std::printf( "  -b, --number-nonblank      number nonblank output lines\n" );
  std::printf( "  -e                         equivalent to `-vE'\n" );
  std::printf( "  -E, --show-ends            display `$' at end of each line\n" );
  std::printf( "  -n, --number               number all output lines\n" );
  std::printf( "  -q, --quiet                suppress all messages\n" );
  std::printf( "  -r, --recursive            operate recursively on directories\n" );
  std::printf( "  -s, --squeeze-blank        never more than one single blank line\n" );
  std::printf( "  -t                         equivalent to `-vT'\n" );
  std::printf( "  -T, --show-tabs            display TAB characters as `^I'\n" );
  std::printf( "  -v, --show-nonprinting     use `^' and `M-' notation, except for LF and TAB\n" );
  std::printf( "      --verbose              verbose mode (show error messages)\n" );
  show_help_addr();
  }


int do_cat( const int infd, const int buffer_size,
            uint8_t * const inbuf, uint8_t * const outbuf,
            const std::string & input_filename,
            const Cat_options & cat_options )
  {
  static int at_bol = 1;	// at begin of line. 0 = false, 1 = true,
				// 2 = at begin of second blank line.
  int inpos = 0;		// positions in buffers
  int outpos = 0;
  int rd = -1;			// bytes read by the last readblock
  unsigned char c;

  while( true )
    {
    do {
      if( outpos >= buffer_size )
        {
        if( writeblock( STDOUT_FILENO, outbuf, outpos ) != outpos )
          { show_error( "Write error", errno ); return 1; }
        outpos = 0;
        }
      if( inpos > rd )			// inbuf is empty
        {
        rd = readblock( infd, inbuf, buffer_size );
        if( rd != buffer_size && errno )
          {
          show_error2( "Error reading file", input_filename.c_str() );
          return 1;
          }
        if( rd == 0 )
          {
          if( writeblock( STDOUT_FILENO, outbuf, outpos ) != outpos )
            { show_error( "Write error", errno ); return 1; }
          outpos = 0;
          return 0;
          }
        inpos = 0;
        inbuf[rd] = '\n';		// sentinel newline
        }
      else				// a real newline was found
        {
        if( at_bol > 1 )
          {
          if( cat_options.squeeze_blank ) { c = inbuf[inpos++]; continue; }
          }
        else ++at_bol;
        if( at_bol > 1 && cat_options.number_lines == 2 )
          {
          line_number.next();
          outpos += line_number.sprint( &outbuf[outpos] );
          }
        if( cat_options.show_ends ) outbuf[outpos++] = '$';
        outbuf[outpos++] = '\n';		// output the newline
        }
      c = inbuf[inpos++];
      }
    while( c == '\n' );

    if( at_bol > 0 && cat_options.number_lines )
      {
      line_number.next();
      outpos += line_number.sprint( &outbuf[outpos] );
      }
    at_bol = 0;

    // the loops below continue until a newline (real or sentinel) is found

    if( cat_options.show_nonprinting )
      while( true )
        {
        if( c < 32 || c >= 127 )
          {
          if( c == '\n' ) break;
          if( c != '\t' || cat_options.show_tabs )
            {
            if( c >= 128 )
              { c -= 128; outbuf[outpos++] = 'M'; outbuf[outpos++] = '-'; }
            if( c < 32 ) { c += 64; outbuf[outpos++] = '^'; }
            else if( c == 127 ) { c = '?'; outbuf[outpos++] = '^'; }
            }
          }
        outbuf[outpos++] = c;
        c = inbuf[inpos++];
        }
    else				// not quoting
      while( c != '\n' )
        {
        if( c == '\t' && cat_options.show_tabs )
          { c += 64; outbuf[outpos++] = '^'; }
        outbuf[outpos++] = c;
        c = inbuf[inpos++];
        }
    }
  }


int cat( int infd, const std::string & input_filename,
         const Cat_options & cat_options )
  {
  enum { buffer_size = 4096 };
  // buffer with space for sentinel newline at the end
  uint8_t * const inbuf = new uint8_t[buffer_size+1];
  // buffer with space for character quoting and 255-digit line number
  uint8_t * const outbuf = new uint8_t[(4*buffer_size)+256];
  pid_t pid = 0;
  int retval = 0;
  if( !set_data_feeder( &infd, &pid ) ) retval = 1;
  else
    retval = do_cat( infd, buffer_size, inbuf, outbuf,
                     input_filename, cat_options );
  if( retval == 0 )
    if( pid && wait_for_child( pid, "data feeder" ) != 0 ) retval = 1;
  if( retval == 0 )
    if( close( infd ) != 0 )
      { show_close_error( "data feeder" ); retval = 1; }
  delete[] inbuf; delete[] outbuf;
  return retval;
  }
