/*  Lziprecover - Data recovery tool for lzipped files
    Copyright (C) 2009, 2010, 2011, 2012 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/>.
*/

#define _FILE_OFFSET_BITS 64

#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <stdint.h>
#include <unistd.h>
#include <sys/stat.h>

#include "lzip.h"


namespace {

void first_filename( const std::string & input_filename,
                     const std::string & default_output_filename,
                     std::string & output_filename )
  {
  if( default_output_filename.size() )
    output_filename = default_output_filename;
  else
    output_filename = input_filename;
  int b = output_filename.size();
  while( b > 0 && output_filename[b-1] != '/' ) --b;
  output_filename.insert( b, "rec00001" );
  }


bool next_filename( std::string & output_filename )
  {
  int b = output_filename.size();
  while( b > 0 && output_filename[b-1] != '/' ) --b;
  for( int i = b + 7; i >= b + 3; --i )			// "rec00001"
    {
    if( output_filename[i] < '9' ) { ++output_filename[i]; return true; }
    else output_filename[i] = '0';
    }
  return false;
  }


// Search forward from 'pos' for "LZIP" (Boyer-Moore algorithm)
// Return pos of found string or 'pos+size' if not found.
//
int find_magic( const uint8_t * const buffer, const int pos, const int size )
  {
  const unsigned char table[256] = {
    4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
    4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
    4,4,4,4,4,4,4,4,4,1,4,4,3,4,4,4,4,4,4,4,4,4,4,4,4,4,2,4,4,4,4,4,
    4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
    4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
    4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
    4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
    4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4 };

  for( int i = pos; i <= pos + size - 4; i += table[buffer[i+3]] )
    if( buffer[i]   == 'L' && buffer[i+1] == 'Z' &&
        buffer[i+2] == 'I' && buffer[i+3] == 'P' )
      return i;				// magic string found
  return pos + size;
  }


int do_split_file( const std::string & input_filename, uint8_t * & base_buffer,
                   const std::string & default_output_filename, const bool force )
  {
  const int hsize = File_header::size;
  const int tsize = File_trailer::size();
  const int buffer_size = 65536;
  const int base_buffer_size = tsize + buffer_size + hsize;
  base_buffer = new uint8_t[base_buffer_size];
  uint8_t * const buffer = base_buffer + tsize;

  struct stat in_stats;
  const int infd = open_instream( input_filename, &in_stats, true, true );
  if( infd < 0 ) return 1;
  int size = readblock( infd, buffer, buffer_size + hsize ) - hsize;
  bool at_stream_end = ( size < buffer_size );
  if( size != buffer_size && errno )
    { show_error( "Read error", errno ); return 1; }
  if( size <= tsize )
    { show_error( "Input file is too short." ); return 2; }
  if( !verify_header( *(File_header *)buffer ) ) return 2;

  std::string output_filename;
  first_filename( input_filename, default_output_filename, output_filename );
  int outfd = open_outstream_rw( output_filename, force );
  if( outfd < 0 ) { close( infd ); return 1; }

  long long partial_member_size = 0;
  while( true )
    {
    int pos = 0;
    for( int newpos = 1; newpos <= size; ++newpos )
      {
      newpos = find_magic( buffer, newpos, size + 4 - newpos );
      if( newpos <= size )
        {
        long long member_size = 0;
        for( int i = 1; i <= 8; ++i )
          { member_size <<= 8; member_size += base_buffer[tsize+newpos-i]; }
        if( partial_member_size + newpos - pos == member_size )
          {						// header found
          const int wr = writeblock( outfd, buffer + pos, newpos - pos );
          if( wr != newpos - pos )
            { show_error( "Write error", errno ); return 1; }
          if( close( outfd ) != 0 )
            { show_error( "Error closing output file", errno ); return 1; }
          if( !next_filename( output_filename ) )
            { show_error( "Too many members in file." ); close( infd ); return 1; }
          outfd = open_outstream_rw( output_filename, force );
          if( outfd < 0 ) { close( infd ); return 1; }
          partial_member_size = 0;
          pos = newpos;
          }
        }
      }

    if( at_stream_end )
      {
      const int wr = writeblock( outfd, buffer + pos, size + hsize - pos );
      if( wr != size + hsize - pos )
        { show_error( "Write error", errno ); return 1; }
      break;
      }
    if( pos < buffer_size )
      {
      partial_member_size += buffer_size - pos;
      const int wr = writeblock( outfd, buffer + pos, buffer_size - pos );
      if( wr != buffer_size - pos )
        { show_error( "Write error", errno ); return 1; }
      }
    std::memcpy( base_buffer, base_buffer + buffer_size, tsize + hsize );
    size = readblock( infd, buffer + hsize, buffer_size );
    at_stream_end = ( size < buffer_size );
    if( size != buffer_size && errno )
      { show_error( "Read error", errno ); return 1; }
    }
  close( infd );
  if( close( outfd ) != 0 )
    { show_error( "Error closing output file", errno ); return 1; }
  return 0;
  }

} // end namespace


int split_file( const std::string & input_filename,
                const std::string & default_output_filename, const bool force )
  {
  uint8_t * base_buffer;
  const int retval = do_split_file( input_filename, base_buffer,
                                    default_output_filename, force );
  delete[] base_buffer;
  return retval;
  }
