/*---------------------------------------------------------------------\
|                          ____ _   __ __ ___                          |
|                         |__  / \ / / . \ . \                         |
|                           / / \ V /|  _/  _/                         |
|                          / /__ | | | | | |                           |
|                         /_____||_| |_| |_|                           |
|                                                                      |
\---------------------------------------------------------------------*/
/** \file	zypp/CpeId.cc
 */
#include <iostream>
#include <array>

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

#include <zypp/CpeId.h>

using std::endl;

/** Initializer list with all wfn attributes */
#define WFN_ATTRIBUTES {\
  Attribute::part,	\
  Attribute::vendor,	\
  Attribute::product,	\
  Attribute::version,	\
  Attribute::update,	\
  Attribute::edition,	\
  Attribute::language,	\
  Attribute::sw_edition,\
  Attribute::target_sw,	\
  Attribute::target_hw,	\
  Attribute::other,	\
}

///////////////////////////////////////////////////////////////////
namespace zypp
{
  ///////////////////////////////////////////////////////////////////
  namespace
  {
    /** Hex-digit to number or -1. */
    inline int heDecodeCh( char ch )
    {
      if ( '0' <= ch && ch <= '9' )
        return( ch - '0' );
      if ( 'A' <= ch && ch <= 'F' )
        return( ch - 'A' + 10 );
      if ( 'a' <= ch && ch <= 'f' )
        return( ch - 'a' + 10 );
      return -1;
    }

    /** Printable non whitespace in [0x00,0x7f] valid in WFN */
    inline bool chIsValidRange( char ch )
    { return( '!' <= ch && ch <= '~' ); }

    /** Alpha */
    inline bool chIsAlpha( char ch )
    { return( ( 'a' <= ch && ch <= 'z' ) || ( 'A' <= ch && ch <= 'Z' ) ); }

    /** Digit */
    inline bool chIsNum( char ch )
    { return( '0' <= ch && ch <= '9' ); }

    /** Alphanum */
    inline bool chIsAlNum( char ch )
    { return( chIsAlpha( ch ) || chIsNum( ch ) ); }

    /** Alphanum or \c underscore are unescaped in WFN */
    inline bool chIsWfnUnescaped( char ch )
    { return( chIsAlNum( ch ) || ch == '_' ); }

  } // namespace
  ///////////////////////////////////////////////////////////////////

  constexpr CpeId::NoThrowType CpeId::noThrow;

  ///////////////////////////////////////////////////////////////////
  /// \class CpeId::Impl
  /// \brief CpeId implementation.
  ///////////////////////////////////////////////////////////////////
  class CpeId::Impl : private base::NonCopyable
  {
    using Wfn = std::array<Value, Attribute::numAttributes>;

  public:
    Impl() {}

    Impl( const std::string & cpe_r )
      : _wfn( unbind( cpe_r ) )
    {}

  public:
    explicit operator bool() const
    { for ( const auto & val : _wfn ) if ( ! val.isANY() ) return true; return false; }

    std::string asFs() const
    {
      str::Str ret;
      ret << "cpe:2.3";
      for ( auto ai : WFN_ATTRIBUTES )
      {
        ret << ':' << _wfn[ai].asFs();
      }
      return ret;
    }

    std::string asUri() const
    {
      str::Str ret;
      ret << "cpe:/";
      unsigned colon = 0;	// to remember trailing colons
      for ( auto ai : WFN_ATTRIBUTES )
      {
        std::string val = _wfn[ai].asUri();

        if ( ai == Attribute::edition )
        {
          if ( ! ( _wfn[Attribute::sw_edition].isANY()
                && _wfn[Attribute::target_sw].isANY()
                && _wfn[Attribute::target_hw].isANY()
                && _wfn[Attribute::other].isANY() ) )
          {
            // packing is needed
            val = str::Str()
                  << '~' << val//Attribute::edition
                  << '~' << _wfn[Attribute::sw_edition].asUri()
                  << '~' << _wfn[Attribute::target_sw].asUri()
                  << '~' << _wfn[Attribute::target_hw].asUri()
                  << '~' << _wfn[Attribute::other].asUri();
          }
        }

        if ( ! val.empty() )
        {
          if ( colon )
            ret << std::string( colon, ':' );
          ret << val;
          colon = 1;
        }
        else
          ++colon;

        if ( ai == Attribute::language )
          break;	// remaining attrs packaed in edition
      }
      return ret;
    }

    std::string asWfn() const
    {
      str::Str ret;
      ret << "wfn:[";
      for ( auto ai : WFN_ATTRIBUTES )
      {
        const Value & val( _wfn[ai] );
        if ( ! val.isANY() )
        {
          if ( ai ) ret << ',';
          ret << Attribute::asString( ai ) << '=';
          if ( val.isString() )
            ret << '"' << val << '"';
          else
            ret << "NA";	// as ANY is omitted, it must be NA
        }
      }
      return ret << "]";
    }

  public:
    SetCompare setRelationMixinCompare( const Impl & trg ) const
    {
      SetCompare ret = SetCompare::equal;
      for ( auto ai : WFN_ATTRIBUTES )
      {
        switch ( _wfn[ai].compare( trg._wfn[ai] ).asEnum() )
        {
          case SetCompare::uncomparable:
            ret = SetCompare::uncomparable;
            break;

          case SetCompare::equal:
            break;

          case SetCompare::properSubset:
            if ( ret == SetCompare::equal )
              ret = SetCompare::properSubset;
            else if ( ret != SetCompare::properSubset )
              ret = SetCompare::uncomparable;
            break;

          case SetCompare::properSuperset:
            if ( ret == SetCompare::equal )
              ret = SetCompare::properSuperset;
            else if ( ret != SetCompare::properSuperset )
              ret = SetCompare::uncomparable;
            break;

          case SetCompare::disjoint:
            ret = SetCompare::disjoint;
            break;
        }
        if ( ret == SetCompare::uncomparable || ret == SetCompare::disjoint )
          break;
      }
      return ret;
    }

  private:
    /** Assign \a val_r if it meets \a attr_r specific contraints.
     * \throws std::invalid_argument if string is malformed
     */
    static void assignAttr( Wfn & wfn_r, Attribute attr_r, const Value & val_r )
    {
      if ( val_r.isString() )
      {
        switch ( attr_r.asEnum() )
        {
          case Attribute::part:
            {
              const std::string & wfn( val_r.asWfn() );
              switch ( wfn[0] )
              {
                case 'h':
                case 'o':
                case 'a':
                  if ( wfn[1] == '\0' )
                    break;
                  // else: fallthrough
                default:
                  throw std::invalid_argument( str::Str() << "CpeId:Wfn:part: '" << wfn << "' illegal value; expected: 'h' | 'o' | 'a'"   );
                  break;
              }
            }
            break;

          case Attribute::language:
            {
              const std::string & wfn( val_r.asWfn() );
              std::string::size_type len = 0;
              // (2*3ALPHA) ["-" (2ALPHA / 3DIGIT)]
              if ( chIsAlpha( wfn[0] ) && chIsAlpha( wfn[1] ) )
              {
                len = chIsAlpha( wfn[2] ) ? 3 : 2;
                if ( wfn[len] == '-' )
                {
                  if ( chIsAlpha( wfn[len+1] ) && chIsAlpha( wfn[len+2] ) )
                    len += 3;
                  else if ( chIsNum( wfn[len+1] ) && chIsNum( wfn[len+2] ) && chIsNum( wfn[len+3] ) )
                    len += 4;
                }
              }
              if ( wfn.size() != len )
                throw std::invalid_argument( str::Str() << "CpeId:Wfn:language: '" << wfn << "' illegal value; expected RFC5646 conform: language ['-' region]" );
            }
            break;

          default:
            // no contraints
            break;
        }
      }
      wfn_r[attr_r.asIntegral()] = val_r;
    }

  private:
    /** Parse magic and unbind accordingly
     * \throws std::invalid_argument if string is malformed
     */
    static Wfn unbind( const std::string & cpe_r );

    /** Parse Uri and unbind
     * \throws std::invalid_argument if string is malformed
     */
    static Wfn unbindUri( const std::string & cpe_r );

    /** Parse Fs and unbind
     * \throws std::invalid_argument if string is malformed
     */
    static Wfn unbindFs( const std::string & cpe_r );

  private:
    Wfn _wfn;
  };

  CpeId::Impl::Wfn CpeId::Impl::unbind( const std::string & cpe_r )
  {
    Wfn ret;
    if ( cpe_r[0] == 'c'
      && cpe_r[1] == 'p'
      && cpe_r[2] == 'e'
      && cpe_r[3] == ':' )
    {
      if ( cpe_r[4] == '/' )
      {
        ret = unbindUri( cpe_r );
      }
      else if ( cpe_r[4] == '2'
             && cpe_r[5] == '.'
             && cpe_r[6] == '3'
             && cpe_r[7] == ':' )
      {
        ret = unbindFs( cpe_r );
      }
      else
        throw std::invalid_argument( "CpeId: bad magic; expected: 'cpe:2.3:' | 'cpe:/'" );
    }
    else if ( cpe_r[0] != '\0' )
      throw std::invalid_argument( "CpeId: bad magic; expected: 'cpe:2.3:' | 'cpe:/'" );
    return ret;
  }

  CpeId::Impl::Wfn CpeId::Impl::unbindUri( const std::string & cpe_r )
  {
    Wfn ret;

    static constexpr unsigned numUriAttr = 7u;	// basic URI attibutes
    std::vector<std::string> field;
    field.reserve( Attribute::numAttributes );	// reserve 7 + 4 for packed extened attrs in edition
    if ( str::splitFields( cpe_r.c_str()+5/* skip magic 'cpe:/' */, std::back_inserter(field), ":" ) > numUriAttr )
      throw std::invalid_argument( str::Str() << "CpeId:Uri: too many fields (" << field.size() << "); expected " << numUriAttr );
    field.resize( Attribute::numAttributes );	// fillup with ANY(""),

    for ( auto ai : WFN_ATTRIBUTES )
    {
      if ( ai == Attribute::edition && field[ai][0] == '~' )
      {
        // unpacking is needed
        static constexpr unsigned numPacks = 6u;	// dummy_before_~ + edition + 4 extended attributes
        std::vector<std::string> pack;
        pack.reserve( numPacks );
        if ( str::splitFields( field[ai], std::back_inserter(pack), "~" ) > numPacks )
          throw std::invalid_argument( str::Str() << "CpeId:Uri:edition: too many packs (" << pack.size() << "); expected " << numPacks );
        pack.resize( numPacks );	// fillup with ANY(""), should be noOP

        pack[1].swap( field[Attribute::edition] );
        pack[2].swap( field[Attribute::sw_edition] );
        pack[3].swap( field[Attribute::target_sw] );
        pack[4].swap( field[Attribute::target_hw] );
        pack[5].swap( field[Attribute::other] );
      }
      assignAttr( ret, ai, Value( field[ai], Value::uriFormat ) );
    }
    return ret;
  }

  CpeId::Impl::Wfn CpeId::Impl::unbindFs( const std::string & cpe_r )
  {
    Wfn ret;

    std::vector<std::string> field;
    field.reserve( Attribute::numAttributes );
    if ( str::splitFields( cpe_r.c_str()+8/* skip magic 'cpe:2.3:' */, std::back_inserter(field), ":" ) > Attribute::numAttributes )
      throw std::invalid_argument( str::Str() << "CpeId:Fs: too many fields (" << field.size() << "); expected 11" /*<< Attribute::numAttributes but g++ currently can't resoolve this as constexpr*/ );
    if ( !field.empty() && field.back().empty() )	// A trailing ':' leads to an empty (illegal) field, but we fillup missing fields with ANY|"*"
      field.back() = "*";
    field.resize( Attribute::numAttributes, "*" );	// fillup with ANY|"*"

    for ( auto ai : WFN_ATTRIBUTES )
    {
      assignAttr( ret, ai, Value( field[ai], Value::fsFormat ) );
    }
    return ret;
  }


  ///////////////////////////////////////////////////////////////////
  //	class CpeId
  ///////////////////////////////////////////////////////////////////

  std::string CpeId::NoThrowType::lastMalformed;

  CpeId::CpeId()
    : _pimpl( new Impl )
  {}

  CpeId::CpeId( const std::string & cpe_r )
    : _pimpl( new Impl( cpe_r ) )
  {}

  CpeId::CpeId( const std::string & cpe_r, NoThrowType )
  {
    try
    {
      _pimpl.reset( new Impl( cpe_r ) );
      NoThrowType::lastMalformed.clear();
    }
    catch(...)
    {
      _pimpl.reset( new Impl );
      NoThrowType::lastMalformed = cpe_r;
    }
  }

  CpeId::~CpeId()
  {}

  CpeId::operator bool() const
  { return bool(*_pimpl); }

  std::string CpeId::asFs() const
  { return _pimpl->asFs(); }

  std::string CpeId::asUri() const
  { return _pimpl->asUri(); }

  std::string CpeId::asWfn() const
  { return _pimpl->asWfn(); }

  SetCompare CpeId::setRelationMixinCompare( const CpeId & trg ) const
  { return _pimpl->setRelationMixinCompare( *trg._pimpl ); }

  ///////////////////////////////////////////////////////////////////
  //	class CpeId::WfnAttribute
  ///////////////////////////////////////////////////////////////////

  const std::string & CpeId::EAttributeDef::asString( Enum val_r )
  {
    static std::map<Enum,std::string> _table = {
#define OUTS(N) { N, #N }
      OUTS( part ),
      OUTS( vendor ),
      OUTS( product ),
      OUTS( version ),
      OUTS( update ),
      OUTS( edition ),
      OUTS( language ),
      OUTS( sw_edition ),
      OUTS( target_sw ),
      OUTS( target_hw ),
      OUTS( other ),
#undef OUTS
    };
    return _table[val_r];
  }

  ///////////////////////////////////////////////////////////////////
  //	class CpeId::Value
  ///////////////////////////////////////////////////////////////////

  const CpeId::Value CpeId::Value::ANY;
  const CpeId::Value CpeId::Value::NA( "" );

  constexpr CpeId::Value::FsFormatType CpeId::Value::fsFormat;
  constexpr CpeId::Value::UriFormatType CpeId::Value::uriFormat;

  CpeId::Value::Value( const std::string & value_r )
  {
    if ( value_r.empty() )	// NA
    {
      if ( ! CpeId::Value::NA._value )	// initialized by this ctor!
        _value.reset( new std::string );
      else
        _value = CpeId::Value::NA._value;
    }
    else if ( value_r != "*" )	// ANY is default constructed
    {
      bool starting = true;	// false after the 1st non-?
      for_( chp, value_r.begin(), value_r.end() )
      {
        switch ( *chp )
        {
          case '\\':	// quoted
            ++chp;
            if ( ! chIsValidRange( *chp )  )
            {
              if ( *chp )
                throw std::invalid_argument( str::Str() << "CpeId:Wfn: illegal quoted character '\\" << reinterpret_cast<void*>(*chp) << "'" );
              else
                throw std::invalid_argument( "CpeId:Wfn: Backslash escapes nothing" );
            }
            else if ( chIsWfnUnescaped( *chp ) )
              throw std::invalid_argument( str::Str() << "CpeId:Wfn: unnecessarily quoted character '\\" << *chp << "'" );
            else if ( starting && *chp == '-' && chp+1 == value_r.end() )
              throw std::invalid_argument( str::Str() << "CpeId:Wfn: '\\-' is illegal value" );
            break;

          case '?':	// sequence at beginning or end of string
            while ( *(chp+1) == '?' )
              ++chp;
            if ( ! ( starting || chp+1 == value_r.end() ) )
              throw std::invalid_argument( "CpeId:Wfn: embedded ?" );
            break;

          case '*':	// single at beginning or end of string
            if ( ! ( starting || chp+1 == value_r.end() ) )
              throw std::invalid_argument( "CpeId:Wfn: embedded *" );
            break;

          default:	// everything else unquoted
            if ( ! chIsWfnUnescaped( *chp ) )
            {
              if ( chIsValidRange( *chp ) )
                throw std::invalid_argument( str::Str() << "CpeId:Wfn: missing quote before '" << *chp << "'" );
              else
                throw std::invalid_argument( str::Str() << "CpeId:Wfn: illegal character '" << reinterpret_cast<void*>(*chp) << "'" );
            }
            break;
        }
        if ( starting )
          starting = false;
      }
      _value.reset( new std::string( value_r ) );
    }
  }

  CpeId::Value::Value( const std::string & encoded_r, FsFormatType )
  {
    if ( encoded_r != "*" )	// ANY is default constructed
    {
      if ( encoded_r == "-" )	// NA
      {
        _value = CpeId::Value::NA._value;
      }
      else
      {
        str::Str result;
        bool starting = true;	// false after the 1st non-?
        for_( chp, encoded_r.begin(), encoded_r.end() )
        {
          switch ( *chp )
          {
            case '\\':	// may stay quoted
              ++chp;
              if ( chIsWfnUnescaped( *chp ) )
                result << *chp;
              else if ( chIsValidRange( *chp ) )
                result << '\\' << *chp;
              else if ( *chp )
                throw std::invalid_argument( str::Str() << "CpeId:Fs: illegal quoted character '\\" << *chp << "'" );
              else
                throw std::invalid_argument( "CpeId:Fs: Backslash escapes nothing" );
              break;

            case '?':	// sequence at beginning or end of string
              result << '?';
              while ( *(chp+1) == '?' )
              {
                ++chp;
                result << '?';
              }
              if ( ! ( starting || chp+1 == encoded_r.end() ) )
                throw std::invalid_argument( "CpeId:Fs: embedded ?" );
              break;

            case '*':	// single at beginning or end of string
              if ( starting || chp+1 == encoded_r.end() )
                result << '*';
              else
                throw std::invalid_argument( "CpeId:Fs: embedded *" );
              break;

            default:
              if ( chIsWfnUnescaped( *chp ) )
                result << *chp;
              else if ( chIsValidRange( *chp ) )
                result << '\\' << *chp;
              else
                throw std::invalid_argument( str::Str() << "CpeId:Fs: illegal character '" << reinterpret_cast<void*>(*chp) << "'" );
              break;
          }
          if ( starting )
            starting = false;
        }
        if ( starting )
          throw std::invalid_argument( "CpeId:Fs: '' value is illegal" );
        _value.reset( new std::string( result ) );
      }
    }
  }

  CpeId::Value::Value( const std::string & encoded_r, UriFormatType )
  {
    if ( ! encoded_r.empty() )	// ANY is default constructed
    {
      if ( encoded_r == "-" )	// NA
      {
        _value = CpeId::Value::NA._value;
      }
      else
      {
        str::Str result;
        bool starting = true;	// false after the 1st non-? (%01)
        for_( chp, encoded_r.begin(), encoded_r.end() )
        {
          char ch = *chp;

          if ( ch == '%' )	// legal '%xx' sequence first
          {
            int d1 = heDecodeCh( *(chp+1) );
            if ( d1 != -1 )
            {
              int d2 = heDecodeCh( *(chp+2) );
              if ( d2 != -1 )
              {
                chp += 2;	// skip sequence
                if ( d1 == 0 )
                {
                  if ( d2 == 1 )	// %01 - ? valid sequence at begin or end
                  {
                    result << '?';
                    while ( *(chp+1) == '%' && *(chp+2) == '0' && *(chp+3) == '1' )
                    {
                      chp += 3;
                      result << '?';
                    }
                    if ( starting || chp+1 == encoded_r.end() )
                    {
                      starting = false;
                      continue;	// -> continue;
                    }
                    else
                      throw std::invalid_argument( "CpeId:Uri: embedded %01" );
                  }
                  else if ( d2 == 2 )	// %02 - * valid at begin or end
                  {
                    if ( starting || chp+1 == encoded_r.end() )
                    {
                      result << '*';
                      starting = false;
                      continue;	// -> continue;
                    }
                    else
                      throw std::invalid_argument( "CpeId:Uri: embedded %02" );
                  }
                }
                ch = (d1<<4)|d2;
                if ( ! chIsValidRange( ch ) )
                  throw std::invalid_argument( str::Str() << "CpeId:Uri: illegal % encoded character '" << reinterpret_cast<void*>(ch) << "'" );
              }
            }
          }
          else if ( ! chIsValidRange( ch ) )
            throw std::invalid_argument( str::Str() << "CpeId:Uri: illegal character '" << reinterpret_cast<void*>(ch) << "'" );

          if ( chIsWfnUnescaped( ch ) )
            result << ch;
          else
            result << '\\' << ch;

          if ( starting )
            starting = false;
        }
        _value.reset( new std::string( result ) );
      }
    }
  }

  std::string CpeId::Value::asWfn() const
  {
    std::string ret;
    if ( ! _value )
    {
      static const std::string any( "*" );
      ret = any;
    }
    else
      ret = *_value;	// includes "" for NA
    return ret;
  }

  std::string CpeId::Value::asFs() const
  {
    std::string ret;
    if ( isANY() )
    {
      static const std::string asterisk( "*" );
      ret = asterisk;
    }
    else if ( isNA() )
    {
      static const std::string dash( "-" );
      ret = dash;
    }
    else
    {
      str::Str result;
      for_( chp, _value->begin(), _value->end() )
      {
        if ( *chp != '\\' )
          result << *chp;
        else
        {
          ++chp;
          switch ( *chp )
          {
            case '-':
            case '.':
            case '_':
              result << *chp;	// without escaping
              break;

            case '\0':
              throw std::invalid_argument( "CpeId:Wfn: Backslash escapes nothing" );
              break;

            default:
              result << '\\' << *chp;
              break;
          }
        }
      }
      ret = result;
    }
    return ret;
  }

  std::string CpeId::Value::asUri() const
  {
    std::string ret;	// ANY
    if ( ! isANY() )
    {
      if ( isNA() )
      {
        static const std::string dash( "-" );
        ret = dash;
      }
      else
      {
        str::Str result;
        for_( chp, _value->begin(), _value->end() )
        {
          if ( chIsWfnUnescaped( *chp ) )
          {
            result << *chp;
          }
          else
          {
            static const char *const hdig = "0123456789abcdef";
            switch ( *chp )
            {
              case '\\':
                ++chp;
                switch ( *chp )
                {
                  case '-':
                  case '.':
                    result << *chp;	// without encodeing
                    break;

                  case '\0':
                    throw std::invalid_argument( "CpeId:Wfn: Backslash escapes nothing" );
                    break;

                  default:
                    result << '%' << hdig[(unsigned char)(*chp)/16] << hdig[(unsigned char)(*chp)%16];
                    break;
                }
                break;

              case '?':
                result << "%01";
                break;

              case '*':
                result << "%02";
                break;

              default:
                throw std::invalid_argument( str::Str() << "CpeId:Wfn: illegal char '" << *chp << "' in WFN" );
                break;
            }
          }
        }
        ret = result;
      }
    }
    return ret;
  }

  ///////////////////////////////////////////////////////////////////
  namespace
  {
    /** Whether it's a wildcard character (<tt>[*?]</tt>). */
    inline bool isWildchar( char ch_r )
    { return( ch_r == '*' || ch_r == '?' ); }

    /** Whether there is an even number of consecutive backslashes before and including \a rbegin_r
     * An even number of backslashes means the character following is unescaped.
     */
    inline bool evenNumberOfBackslashes( const std::string::const_reverse_iterator& rbegin_r, const std::string::const_reverse_iterator& rend_r )
    {
      unsigned backslashes = 0;
      for_( it, rbegin_r, rend_r )
      {
        if ( *it == '\\' )
          ++backslashes;
        else
          break;
      }
      return !(backslashes & 1U);
    }

    /** Number of chars (not counting escaping backslashes) in <tt>[begin_r,end_r[</tt> */
    inline unsigned trueCharsIn( const std::string & str_r, std::string::size_type begin_r, std::string::size_type end_r )
    {
      unsigned chars = 0;
      for_( it, begin_r, end_r )
      {
        ++chars;
        if ( str_r[it] == '\\' )
        {
          if ( ++it == end_r )
            break;
        }
      }
      return chars;
    }

    /** Match helper comparing 2 Wildcardfree string values (case insensitive). */
    inline bool matchWildcardfreeString( const std::string & lhs, const std::string & rhs )
    { return( str::compareCI( lhs, rhs ) == 0 ); }

    /** Match helper matching Wildcarded source against Wildcardfree target.
     *
     * Constraints on usage of the unquoted question mark (zero or one char in \a trg):
     * 1. An unquoted question mark MAY be used at the beginning and/or the end of an
     *    attribute-value string.
     * 2. A contiguous sequence of unquoted question marks MAY appear at the beginning
     *    and/or the end of an attribute-value string.
     * 3. An unquoted question mark SHALL NOT be used in any other place in an
     *    attribute-value string.
     *
     * Constraints on usage of the unquoted asterisk  (zero or more chars in \a trg):
     * 1. A single unquoted asterisk MAY be used as the entire attribute-value string.
     * 2. A single unquoted asterisk MAY be used at the beginning and/or end of an
     *    attribute-value string.
     * 3. An unquoted asterisk SHALL NOT be used in any other place in an attribute-value
     *    string.
     *
     * Unquoted question marks and asterisks MAY appear in the same attribute-value string
     * as long as they meet the constraints above.
     *
     * Example of illegal usage: "foo?bar", "bar??baz", "q??x",
     *                           "foo*bar", "**foo", "bar***",
     *                           "*?foobar", "foobar*?"
     *
     * \note Relies on \a src and \a trg being wellformed.
     */
    inline bool matchWildcardedString( std::string src, std::string trg )
    {
      // std::string::npos remembers an asterisk
      // unescaped wildcard prefix
      std::string::size_type prefx = 0;
      switch ( *src.begin() )	// wellformed implies not empty
      {
        case '*':
          if ( src.size() == 1 )
            return true;	// "*" matches always: superset
          // else
          prefx = std::string::npos;
          src.erase( 0, 1 );
          break;
        case '?':
          ++prefx;
          for_( it, ++src.begin(), src.end() )
          { if ( *it == '?' ) ++prefx; else break; }
          if ( src.size() == prefx )
            return( trg.size() <= prefx );	// "??..?": superset if at most #prefx chars
          // else
          src.erase( 0, prefx );
          break;
        default:
          break;
      }
      // unescaped wildcard suffix
      std::string::size_type suffx = 0;
      if ( ! src.empty() )
      {
        switch ( *src.rbegin() )
        {
          case '*':
            if ( evenNumberOfBackslashes( ++src.rbegin(), src.rend() ) )
            {
              suffx = std::string::npos;
              src.erase( src.size()-1 );
            }
            break;
          case '?':
            ++suffx;
            for_( it, ++src.rbegin(), src.rend() )
            { if ( *it == '?' ) ++suffx; else break; }
            if ( ! evenNumberOfBackslashes( src.rbegin()+suffx, src.rend() ) )
              --suffx;	// last '?' was escaped.
            src.erase( src.size()-suffx );
            break;
          default:
            break;
        }
      }
      // now match; find src in trg an check surrounding wildcards
      src = str::toLower( src );
      trg = str::toLower( trg );
      for ( std::string::size_type match = trg.find( src, 0 );
            match != std::string::npos;
            match = trg.find( src, match+1 ) )
      {
        if ( prefx != std::string::npos && trueCharsIn( trg, 0, match ) > prefx )
          break;	// not "*", and already more chars than "?"s before match: disjoint
        std::string::size_type frontSize = match + src.size();
        if ( suffx != std::string::npos && trueCharsIn( trg, frontSize, trg.size() ) > suffx )
          continue;	// not "*", and still more chars than "?"s after match: check next match
        return true;	// match: superset
      }
      return false;	// disjoint
    }
  } // namespace
  ///////////////////////////////////////////////////////////////////

  bool CpeId::Value::containsWildcard() const
  {
    const std::string & value( *_value );
    return ( isWildchar( *value.begin() )
        || ( isWildchar( *value.rbegin() ) && evenNumberOfBackslashes( ++value.rbegin(), value.rend() ) ) );
  }

  ///////////////////////////////////////////////////////////////////
  /// Symmetric attribute compare if wildcards are involved!
  /// The specs define any comarison with a wildcarded attribute as
  /// target to return \c uncomparable:
  /// \code
  ///    wildcardfree  <=>  wildcarded    ==>  uncomparable,
  ///    wildcarded    <=>  wildcardfree  ==>  superset or disjoint
  /// \endcode
  /// But a symmetric result is much more intuitive:
  /// \code
  ///    wildcardfree  <=>  wildcarded    ==>  subset or disjoint
  ///    wildcarded    <=>  wildcardfree  ==>  superset or disjoint
  /// \endcode
  ///////////////////////////////////////////////////////////////////
#define WFN_STRICT_SPEC 0
#if WFN_STRICT_SPEC
  //SetCompare CpeId::Value::setRelationMixinCompare( const CpeId::Value & trg ) const
  {
    static const SetCompare kNeedsCloserLook( SetCompare::Enum(-1) );	// artificial Compare value
    static const SetCompare matchTabel[4][4] = {{
      /* ANY,		ANY		*/ SetCompare::equal,
      /* ANY,		NA		*/ SetCompare::properSuperset,
      /* ANY,		wildcardfree	*/ SetCompare::properSuperset,
      /* ANY,		wildcarded	*/ SetCompare::uncomparable,
    },{
      /* NA,		ANY		*/ SetCompare::properSubset,
      /* NA,		NA		*/ SetCompare::equal,
      /* NA,		wildcardfree	*/ SetCompare::disjoint,
      /* NA,		wildcarded	*/ SetCompare::uncomparable,
    },{
      /* wildcardfree,	ANY		*/ SetCompare::properSubset,
      /* wildcardfree,	NA		*/ SetCompare::disjoint,
      /* wildcardfree,	wildcardfree	*/ kNeedsCloserLook,	// equal or disjoint
      /* wildcardfree,	wildcarded	*/ SetCompare::uncomparable,
    },{
      /* wildcarded,	ANY		*/ SetCompare::properSubset,
      /* wildcarded,	NA		*/ SetCompare::disjoint,
      /* wildcarded,	wildcardfree	*/ kNeedsCloserLook,	// superset or disjoint
      /* wildcarded,	wildcarded	*/ SetCompare::uncomparable,
    }};

    Type srcType = type();
    Type trgType = trg.type();
    SetCompare ret = matchTabel[srcType.asIntegral()][trgType.asIntegral()];
    if ( ret == kNeedsCloserLook )
    {
      if ( srcType == Type::wildcardfree )	// trgType == Type::wildcardfree
      {
        // simple string compare
        ret = matchWildcardfreeString( *_value, *trg._value ) ? SetCompare::equal : SetCompare::disjoint;
      }
      else if ( srcType == Type::wildcarded )	// trgType == Type::wildcardfree
      {
        // Needs wildcard compare
        ret = matchWildcardedString( *_value, *trg._value ) ? SetCompare::properSuperset : SetCompare::disjoint;
     }
    }
    return ret;
  }
#else
  SetCompare CpeId::Value::setRelationMixinCompare( const CpeId::Value & trg ) const
  {
    ///////////////////////////////////////////////////////////////////
    // ANY,		ANY		=> equal
    // ANY,		NA		=> properSuperset
    // ANY,		wildcardfree	=> properSuperset
    // ANY,		wildcarded	=> properSuperset
    //
    // NA,		ANY		=> properSubset
    // NA,		NA		=> equal
    // NA,		wildcardfree	=> disjoint
    // NA,		wildcarded	=> disjoint
    //
    // wildcardfree,	ANY		=> properSubset
    // wildcardfree,	NA		=> disjoint
    // wildcardfree,	wildcardfree	=> NeedsCloserLook:	equal or disjoint
    // wildcardfree,	wildcarded	=> NeedsCloserLook:	subset or disjoint
    //
    // wildcarded,	ANY		=> properSubset
    // wildcarded,	NA		=> disjoint
    // wildcarded,	wildcardfree	=> NeedsCloserLook:	superset or disjoint
    // wildcarded,	wildcarded	=> NeedsCloserLook"	equal or uncomparable
    ///////////////////////////////////////////////////////////////////

    SetCompare ret = SetCompare::disjoint;

    if ( isANY() )
    {
      ret = trg.isANY() ? SetCompare::equal : SetCompare::properSuperset;
    }
    else if ( trg.isANY() )
    {
      ret = SetCompare::properSubset;
    }
    else if ( isNA() )
    {
      if ( trg.isNA() ) ret = SetCompare::equal; // else: SetCompare::disjoint;
    }
    else if ( ! trg.isNA() ) // else: SetCompare::disjoint;
    {
      // NeedsCloserLook:
      if ( isWildcarded() )
      {
        if ( trg.isWildcarded() )
        {
          // simple string compare just to detect 'equal'
          ret = matchWildcardfreeString( *_value, *trg._value ) ? SetCompare::equal : SetCompare::uncomparable;
        }
        else
        {
          // Needs wildcard compare (src,trg)
          if ( matchWildcardedString( *_value, *trg._value ) ) ret = SetCompare::properSuperset; // else: SetCompare::disjoint;
        }
      }
      else
      {
        if ( trg.isWildcarded() )
        {
          // Needs wildcard compare (trg,src)
          if ( matchWildcardedString( *trg._value, *_value ) ) ret = SetCompare::properSubset; // else: SetCompare::disjoint;
        }
        else
        {
          // simple string compare
          if ( matchWildcardfreeString( *_value, *trg._value ) ) ret = SetCompare::equal; // else: SetCompare::disjoint;
        }
      }
    }
    return ret;
  }
#endif // WFN_STRICT_SPEC

  std::ostream & operator<<( std::ostream & str, const CpeId::Value & obj )
  { return str << obj.asString(); }

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