/*---------------------------------------------------------------------\
|                          ____ _   __ __ ___                          |
|                         |__  / \ / / . \ . \                         |
|                           / / \ V /|  _/  _/                         |
|                          / /__ | | | | | |                           |
|                         /_____||_| |_| |_|                           |
|                                                                      |
\---------------------------------------------------------------------*/

/** \file HistoryLogReader.cc
 *
 */
#include <iostream>

#include <utility>
#include <zypp-core/base/InputStream>
#include <zypp-core/base/IOStream.h>
#include <zypp-core/base/Logger.h>
#include <zypp-core/parser/ParseException>

#include <zypp/parser/HistoryLogReader.h>

using std::endl;

///////////////////////////////////////////////////////////////////
namespace zypp
{
  ///////////////////////////////////////////////////////////////////
  namespace parser
  {

  /////////////////////////////////////////////////////////////////////
  //
  //	class HistoryLogReader::Impl
  //
  /////////////////////////////////////////////////////////////////////
  struct HistoryLogReader::Impl
  {
    Impl( Pathname &&historyFile_r, Options &&options_r, ProcessData &&callback_r )
    :  _filename( std::move(historyFile_r) )
    , _options( std::move(options_r) )
    , _callback( std::move(callback_r) )
    {}

    bool parseLine( const std::string & line_r, unsigned int lineNr_r );

    void readAll( const ProgressData::ReceiverFnc & progress_r );
    void readFrom( const Date & date_r, const ProgressData::ReceiverFnc & progress_r );
    void readFromTo( const Date & fromDate_r, const Date & toDate_r, const ProgressData::ReceiverFnc & progress_r );

    void addActionFilter( const HistoryActionID & action_r )
    {
      if ( action_r == HistoryActionID::NONE )
        _actionfilter.clear();
      else
        _actionfilter.insert( action_r.asString() );
    }

    Pathname _filename;
    Options  _options;
    ProcessData _callback;
    std::set<std::string> _actionfilter;
  };

  bool HistoryLogReader::Impl::parseLine( const std::string & line_r, unsigned lineNr_r )
  {
    // parse into fields
    HistoryLogData::FieldVector fields;
    str::splitEscaped( line_r, std::back_inserter(fields), "|", true );

    if ( fields.size() < 2 ) {
      WAR << "Ignore invalid history log entry on line #" << lineNr_r << " '"<< line_r << "'" << endl;
      return true;	// At least an action field[1] is needed!
    }
    fields[1] = str::trim( std::move(fields[1]) );	// for whatever reason writer is padding the action field

    if ( !_actionfilter.empty() && !_actionfilter.count( fields[1] ) )
      return true;

    // move into data class
    HistoryLogData::Ptr data;
    try
    {
      data = HistoryLogData::create( fields );
    }
    catch ( const Exception & excpt )
    {
      ZYPP_CAUGHT( excpt );
      if ( _options.testFlag( IGNORE_INVALID_ITEMS ) )
      {
        WAR << "Ignore invalid history log entry on line #" << lineNr_r << " '"<< line_r << "'" << endl;
        return true;
      }
      else
      {
        ERR << "Invalid history log entry on line #" << lineNr_r << " '"<< line_r << "'" << endl;
        ParseException newexcpt( str::Str() << "Error in history log on line #" << lineNr_r );
        newexcpt.remember( excpt );
        ZYPP_THROW( newexcpt );
      }
    }

    // consume data
    if ( _callback && !_callback( data ) )
    {
      WAR << "Stop parsing requested by consumer callback on line #" << lineNr_r << endl;
      return false;
    }
    return true;
  }

  void HistoryLogReader::Impl::readAll( const ProgressData::ReceiverFnc & progress_r )
  {
    InputStream is( _filename );
    iostr::EachLine line( is );

    ProgressData pd;
    pd.sendTo( progress_r );
    pd.toMin();

    for ( ; line; line.next(), pd.tick() )
    {
      // ignore comments
      if ( (*line)[0] == '#' )
        continue;

      if ( ! parseLine( *line, line.lineNo() ) )
        break;	// requested by consumer callback
    }

    pd.toMax();
  }

  void HistoryLogReader::Impl::readFrom( const Date & date_r, const ProgressData::ReceiverFnc & progress_r )
  {
    InputStream is( _filename );
    iostr::EachLine line( is );

    ProgressData pd;
    pd.sendTo( progress_r );
    pd.toMin();

    bool pastDate = false;
    for ( ; line; line.next(), pd.tick() )
    {
      const std::string & s = *line;

      // ignore comments
      if ( s[0] == '#' )
        continue;

      if ( pastDate )
      {
        if ( ! parseLine( s, line.lineNo() ) )
          break;	// requested by consumer callback
      }
      else
      {
        Date logDate( s.substr( 0, s.find('|') ), HISTORY_LOG_DATE_FORMAT );
        if ( logDate > date_r )
        {
          pastDate = true;
          if ( ! parseLine( s, line.lineNo() ) )
            break;	// requested by consumer callback
        }
      }
    }

    pd.toMax();
  }

  void HistoryLogReader::Impl::readFromTo( const Date & fromDate_r, const Date & toDate_r, const ProgressData::ReceiverFnc & progress_r )
  {
    InputStream is( _filename );
    iostr::EachLine line( is );

    ProgressData pd;
    pd.sendTo( progress_r );
    pd.toMin();

    bool pastFromDate = false;
    for ( ; line; line.next(), pd.tick() )
    {
      const std::string & s = *line;

      // ignore comments
      if ( s[0] == '#' )
        continue;

      Date logDate( s.substr( 0, s.find('|') ), HISTORY_LOG_DATE_FORMAT );

      // past toDate - stop reading
      if ( logDate >= toDate_r )
        break;

      // past fromDate - start reading
      if ( !pastFromDate && logDate > fromDate_r )
        pastFromDate = true;

      if ( pastFromDate )
      {
        if ( ! parseLine( s, line.lineNo() ) )
          break;	// requested by consumer callback
      }
    }

    pd.toMax();
  }

  /////////////////////////////////////////////////////////////////////
  //
  //	class HistoryLogReader
  //
  /////////////////////////////////////////////////////////////////////

  HistoryLogReader::HistoryLogReader( Pathname historyFile_r, Options options_r, ProcessData callback_r )
  : _pimpl( new HistoryLogReader::Impl( std::move(historyFile_r), std::move(options_r), std::move(callback_r) ) )
  {}

  HistoryLogReader::~HistoryLogReader()
  {}

  void HistoryLogReader::setIgnoreInvalidItems( bool ignoreInvalid_r )
  { _pimpl->_options.setFlag( IGNORE_INVALID_ITEMS, ignoreInvalid_r ); }

  bool HistoryLogReader::ignoreInvalidItems() const
  { return _pimpl->_options.testFlag( IGNORE_INVALID_ITEMS ); }

  void HistoryLogReader::readAll( const ProgressData::ReceiverFnc & progress_r )
  { _pimpl->readAll( progress_r ); }

  void HistoryLogReader::readFrom( const Date & date_r, const ProgressData::ReceiverFnc & progress_r )
  { _pimpl->readFrom( date_r, progress_r ); }

  void HistoryLogReader::readFromTo( const Date & fromDate_r, const Date & toDate_r, const ProgressData::ReceiverFnc & progress_r )
  { _pimpl->readFromTo( fromDate_r, toDate_r, progress_r ); }

  void HistoryLogReader::addActionFilter( const HistoryActionID & action_r )
  { _pimpl->addActionFilter( action_r ); }

  } // namespace parser
  ///////////////////////////////////////////////////////////////////
} // namespace zypp
///////////////////////////////////////////////////////////////////
