/*---------------------------------------------------------------------\
|                          ____ _   __ __ ___                          |
|                         |__  / \ / / . \ . \                         |
|                           / / \ V /|  _/  _/                         |
|                          / /__ | | | | | |                           |
|                         /_____||_| |_| |_|                           |
|                                                                      |
\---------------------------------------------------------------------*/
/** \file zypp/target/rpm/BinHeader.cc
 *
*/

extern "C"
{
#ifdef _RPM_5
#undef RPM_NULL_TYPE
#define RPM_NULL_TYPE rpmTagType(0)
typedef rpmuint32_t rpm_count_t;
#endif
}

#include <iostream>

#include <zypp-core/base/Logger.h>
#include <zypp-core/base/NonCopyable.h>
#include <zypp-core/base/String.h>
#include <zypp-core/AutoDispose.h>

#include "BinHeader.h"

using std::endl;
using namespace zypp;

/** Helper for header data retieval.
   * With \c _RPM_4 use \c ::headerGet; with \c _RPM_5
   * use the meanwhile deprecated \c ::headerGetEntry.
   * \ingroup g_RAII
   */
struct HeaderEntryGetter : private base::NonCopyable
{
public:
  HeaderEntryGetter( const Header & h_r, rpmTag & tag_r );
  ~HeaderEntryGetter();
  rpmTagType  type();
  rpm_count_t cnt();
  void *      val();
private:
#ifndef _RPM_5
  ::rpmtd		_rpmtd;
#else
  rpmTagType	_type;
  rpm_count_t	_cnt;
  void *		_val;
#endif //_RPM_5
};

#ifndef _RPM_5
inline HeaderEntryGetter::HeaderEntryGetter( const Header & h_r, rpmTag & tag_r )
  : _rpmtd( ::rpmtdNew() )
{ ::headerGet( h_r, tag_r, _rpmtd, HEADERGET_EXT ); }
inline HeaderEntryGetter::~HeaderEntryGetter()
{ ::rpmtdFreeData( _rpmtd ); ::rpmtdFree( _rpmtd ); }
inline rpmTagType	HeaderEntryGetter::type()	{ return rpmtdType( _rpmtd ); }
inline rpm_count_t	HeaderEntryGetter::cnt()	{ return _rpmtd->count; }
inline void *		HeaderEntryGetter::val()	{ return _rpmtd->data; }
#else
inline HeaderEntryGetter::HeaderEntryGetter( const Header & h_r, rpmTag & tag_r )
  : _type( RPM_NULL_TYPE )
  , _cnt( 0 )
  , _val( 0 )
{ ::headerGetEntry( h_r, tag_r, hTYP_t(&_type), &_val, &_cnt ); }
inline HeaderEntryGetter::~HeaderEntryGetter()
{ if ( _val && _type == RPM_STRING_ARRAY_TYPE ) free( _val ); }
inline rpmTagType	HeaderEntryGetter::type()	{ return _type; }
inline rpm_count_t	HeaderEntryGetter::cnt()	{ return _cnt; }
inline void *		HeaderEntryGetter::val()	{ return _val; }
#endif //_RPM_5

///////////////////////////////////////////////////////////////////
//
//        CLASS NAME : BinHeader::intList
//
///////////////////////////////////////////////////////////////////

unsigned BinHeader::intList::set( void * val_r, unsigned cnt_r, rpmTagType type_r )
{
  _type = type_r; // remember the type!
  if ( val_r )
    switch ( _type )
    {
#if RPM_CHAR_TYPE != RPM_INT8_TYPE
      case RPM_CHAR_TYPE:
        std::vector<long>( static_cast<char*>(val_r), static_cast<char*>(val_r)+cnt_r ).swap( _data );
        break;
#endif
      case RPM_INT8_TYPE:
        std::vector<long>( static_cast<int8_t*>(val_r), static_cast<int8_t*>(val_r)+cnt_r ).swap( _data );
        break;
      case RPM_INT16_TYPE:
        std::vector<long>( static_cast<int16_t*>(val_r), static_cast<int16_t*>(val_r)+cnt_r ).swap( _data );
        break;
      case RPM_INT32_TYPE:
        std::vector<long>( static_cast<int32_t*>(val_r), static_cast<int32_t*>(val_r)+cnt_r ).swap( _data );
        break;
#ifndef _RPM_5
      case RPM_INT64_TYPE:
        std::vector<long>( static_cast<int64_t*>(val_r), static_cast<int64_t*>(val_r)+cnt_r ).swap( _data );
        break;
#endif
      default:
        std::vector<long>( cnt_r, 0L ).swap( _data );
        break;
    }
  else
    _data.clear();
  return _data.size();
}

///////////////////////////////////////////////////////////////////
//
//        CLASS NAME : BinHeader::stringList
//
///////////////////////////////////////////////////////////////////

unsigned BinHeader::stringList::set( char ** val_r, unsigned cnt_r )
{
  if ( val_r )
    std::vector<std::string>( val_r, val_r+cnt_r ).swap( _data );
  else
    _data.clear();
  return _data.size();
}

///////////////////////////////////////////////////////////////////
//
//        CLASS NAME : BinHeader
//
///////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////
//
//
//        METHOD NAME : BinHeader::BinHeader
//        METHOD TYPE : Constructor
//
BinHeader::BinHeader( Header h_r )
  : _h( h_r )
{
  if ( _h )
  {
    headerLink( _h );
  }
}

///////////////////////////////////////////////////////////////////
//
//
//        METHOD NAME : BinHeader::BinHeader
//        METHOD TYPE : Constructor
//
BinHeader::BinHeader( BinHeader::Ptr & rhs )
{
  INT << "INJECT from " << rhs;
  if ( ! (rhs && rhs->_h) )
  {
    _h = 0;
  }
  else
  {
    _h = rhs->_h;  // ::headerLink already done in rhs
    rhs->_h = 0;
  }
  INT << ": " << *this << "   (" << rhs << ")" << endl;
}

///////////////////////////////////////////////////////////////////
//
//
//        METHOD NAME : BinHeader::~BinHeader
//        METHOD TYPE : Destructor
//
BinHeader::~BinHeader()
{
  if ( _h )
  {
    headerFree( _h );
  }
}

///////////////////////////////////////////////////////////////////
//
//
//        METHOD NAME : BinHeader::assertHeader
//        METHOD TYPE : void
//
bool BinHeader::assertHeader()
{
  if ( !_h )
  {
    _h = ::headerNew();
    if ( !_h )
    {
      INT << "OOPS: NULL HEADER created!" << endl;
      return false;
    }
  }
  return true;
}

///////////////////////////////////////////////////////////////////
//
//
//        METHOD NAME : BinHeader::has_tag
//        METHOD TYPE : bool
//
//        DESCRIPTION :
//
bool BinHeader::has_tag( tag tag_r ) const
{
  return( !empty() && ::headerIsEntry( _h, tag_r ) );
}

///////////////////////////////////////////////////////////////////
//
//
//        METHOD NAME : BinHeader::int_list
//        METHOD TYPE : unsigned
//
//        DESCRIPTION :
//
unsigned BinHeader::int_list( tag tag_r, intList & lst_r ) const
{
  if ( !empty() )
  {
    HeaderEntryGetter headerget( _h, tag_r );

    if ( headerget.val() )
    {
      switch ( headerget.type() )
      {
        case RPM_NULL_TYPE:
          return lst_r.set( 0, 0, headerget.type() );
#if RPM_CHAR_TYPE != RPM_INT8_TYPE
        case RPM_CHAR_TYPE:
#endif
        case RPM_INT8_TYPE:
        case RPM_INT16_TYPE:
        case RPM_INT32_TYPE:
#ifndef _RPM_5
        case RPM_INT64_TYPE:
#endif
          return lst_r.set( headerget.val(), headerget.cnt(), headerget.type() );

        default:
          INT << "RPM_TAG MISSMATCH: RPM_INTxx_TYPE " << tag_r << " got type " << headerget.type() << endl;
      }
    }
  }
  return lst_r.set( 0, 0, RPM_NULL_TYPE );
}

///////////////////////////////////////////////////////////////////
//
//
//        METHOD NAME : BinHeader::string_list
//        METHOD TYPE : unsigned
//
//        DESCRIPTION :
//
unsigned BinHeader::string_list( tag tag_r, stringList & lst_r ) const
{
  if ( !empty() )
  {
    HeaderEntryGetter headerget( _h, tag_r );

    if ( headerget.val() )
    {
      switch ( headerget.type() )
      {
        case RPM_NULL_TYPE:
          return lst_r.set( 0, 0 );
        case RPM_STRING_ARRAY_TYPE:
          return lst_r.set( (char**)headerget.val(), headerget.cnt() );

        default:
          INT << "RPM_TAG MISSMATCH: RPM_STRING_ARRAY_TYPE " << tag_r << " got type " << headerget.type() << endl;
      }
    }
  }
  return lst_r.set( 0, 0 );
}

///////////////////////////////////////////////////////////////////
//
//
//        METHOD NAME : BinHeader::int_val
//        METHOD TYPE : int
//
//        DESCRIPTION :
//
int BinHeader::int_val( tag tag_r ) const
{
  if ( !empty() )
  {
    HeaderEntryGetter headerget( _h, tag_r );

    if ( headerget.val() )
    {
      switch ( headerget.type() )
      {
        case RPM_NULL_TYPE:
          return 0;
#if RPM_CHAR_TYPE != RPM_INT8_TYPE
        case RPM_CHAR_TYPE:
          return *(static_cast<char*>(headerget.val()));
#endif
        case RPM_INT8_TYPE:
          return *(static_cast<int8_t*>(headerget.val()));
        case RPM_INT16_TYPE:
          return *(static_cast<int16_t*>(headerget.val()));
        case RPM_INT32_TYPE:
          return *(static_cast<int32_t*>(headerget.val()));
#ifndef _RPM_5
        case RPM_INT64_TYPE:
          return *(static_cast<int64_t*>(headerget.val()));
#endif

        default:
          INT << "RPM_TAG MISSMATCH: RPM_INTxx_TYPE " << tag_r << " got type " << headerget.type() << endl;
      }
    }
  }
  return 0;
}

///////////////////////////////////////////////////////////////////
//
//
//        METHOD NAME : BinHeader::string_val
//        METHOD TYPE : std::string
//
//        DESCRIPTION :
//
std::string BinHeader::string_val( tag tag_r ) const
{
  if ( !empty() )
  {
    HeaderEntryGetter headerget( _h, tag_r );

    if ( headerget.val() )
    {
      switch ( headerget.type() )
      {
        case RPM_NULL_TYPE:
          return "";
        case RPM_STRING_TYPE:
          return (char*)headerget.val();

        default:
          INT << "RPM_TAG MISSMATCH: RPM_STRING_TYPE " << tag_r << " got type " << headerget.type() << endl;
      }
    }
  }
  return "";
}

std::string BinHeader::nvra() const
{
  if ( !empty() ) {
    std::string nvra = string_val( RPMTAG_NVRA );
    if ( nvra.empty() ) {
      //name-version-release-arch
      nvra = string_val( RPMTAG_NAME ) + "-" + string_val( RPMTAG_VERSION )  + "-" + string_val( RPMTAG_RELEASE)  + "-" + string_val( RPMTAG_ARCH )  ;
    }
    return nvra;
  }
  return "";
}



std::string BinHeader::format(const char *fmt) const
{
  zypp::AutoDispose<char *> form(headerFormat(_h, fmt, NULL), free);
  if ( !form )
    return std::string();

  return std::string(form);
}

Header BinHeader::get() const
{
  return _h;
}

///////////////////////////////////////////////////////////////////
//
//
//        METHOD NAME : BinHeader::stringList_val
//        METHOD TYPE : std::list<std::string>
//
//        DESCRIPTION :
//
std::list<std::string> BinHeader::stringList_val( tag tag_r ) const
{
  std::list<std::string> ret;

  if ( !empty() )
  {
    stringList lines;
    unsigned count = string_list( tag_r, lines );
    for ( unsigned i = 0; i < count; ++i )
    {
      ret.push_back( lines[i] );
    }
  }
  return ret;
}

///////////////////////////////////////////////////////////////////
//
//
//      METHOD NAME : BinHeader::dumpOn
//      METHOD TYPE : ostream &
//
//      DESCRIPTION :
//
std::ostream & BinHeader::dumpOn( std::ostream & str ) const
{
  ReferenceCounted::dumpOn( str );
  return str << '{' << static_cast<void*>(_h) << '}';
}
