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

#ifndef ZYPP_MediaSetAccess_H
#define ZYPP_MediaSetAccess_H

#include <iosfwd>
#include <string>
#include <vector>
#include <zypp/base/Function.h>

#include <zypp/base/ReferenceCounted.h>
#include <zypp/base/NonCopyable.h>
#include <zypp/base/Flags.h>
#include <zypp/base/PtrTypes.h>
#include <zypp/media/MediaManager.h>
#include <zypp/Pathname.h>
#include <zypp/CheckSum.h>
#include <zypp-core/OnMediaLocation>
#include <zypp/ManagedFile.h>
#include <zypp/media/MediaUrl.h>

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

    DEFINE_PTR_TYPE(MediaSetAccess);

    ///////////////////////////////////////////////////////////////////
    //
    //	CLASS NAME : MediaSetAccess
    //
    /**
     * Media access layer responsible for handling files distributed on a set
     * of media with media change and abort/retry/ingore user callback handling.
     *
     * This is provided as a means to handle CD or DVD sets accessible through
     * dir, iso, nfs or other URL schemes other than cd/dvd (see
     * \ref MediaManager for info on different implemented media backends).
     * Currently it handles URLs ending on (case insensitive ) CD#, DVD# or MEDIA#,
     * where # is the number of a particular medium in the set.
     *
     * Examples:
     * \code
     * "iso:/?iso=/path/to/iso/images/openSUSE-10.3-Alpha2plus-DVD-x86_64-DVD1.iso"
     * "dir:/path/to/cdset/sources/openSUSE-10.3/Alpha2plus/CD1"
     * \endcode
     *
     * MediaSetAccess accesses files on the desired medium by rewriting
     * the original URL, replacing the digit (usually) 1 with requested media
     * number and uses \ref MediaManager to get the files from the new URL.
     *
     * NOTE: Access to medium #1 always uses the url passed to the CTOR!
     *
     * Additionaly, each media number can be assinged a media verifier which
     * checks if the media we are trying to access is the desired one. See
     * \ref MediaVerifierBase for more info.
     *
     * Code example:
     * \code
     * Url url("dir:/path/to/cdset/sources/openSUSE-10.3/Alpha2plus/CD1");
     *
     * MediaSetAccess access(url);
     *
     * access.setVerifier(1, media1VerifierRef);
     * access.setVerifier(2, media2VerifierRef);
     *
     * Pathname file1 = "/some/file/on/media1";
     * Pathname providedfile1 = access.provideFile(file1, 1);
     * Pathname file2 = "/some/file/on/media2";
     * Pathname providedfile2 = access.provideFile(file1, 2);
     *
     * \endcode
     */
    class ZYPP_API MediaSetAccess : public base::ReferenceCounted, private base::NonCopyable
    {
      friend std::ostream & operator<<( std::ostream & str, const MediaSetAccess & obj );

    public:
      /**
       * Creates a callback enabled media access for specified \a url.
       *
       * \param url
       * \param prefered_attach_point Prefered attach (mount) point. Use, if
       *        you want to mount the media to a specific directory.
       */
      MediaSetAccess( Url url, Pathname  prefered_attach_point = "" );
      /** \overload Also taking a \ref label. */
      MediaSetAccess( std::string  label_r, Url url, Pathname  prefered_attach_point = "" );

      /**
       * Creates a callback enabled media access for specified \a url.
       *
       * \param urls
       * \param prefered_attach_point Prefered attach (mount) point. Use, if
       *        you want to mount the media to a specific directory.
       */
      MediaSetAccess( std::vector<media::MediaUrl> urls, Pathname  prefered_attach_point = "" );
      /** \overload Also taking a \ref label. */
      MediaSetAccess( std::string label_r, std::vector<media::MediaUrl> urls, Pathname  prefered_attach_point = "" );


      MediaSetAccess(const MediaSetAccess &) = delete;
      MediaSetAccess(MediaSetAccess &&) = delete;
      MediaSetAccess &operator=(const MediaSetAccess &) = delete;
      MediaSetAccess &operator=(MediaSetAccess &&) = delete;

      ~MediaSetAccess() override;

      /**
       * Sets a \ref MediaVerifier verifier for given media number.
       */
      void setVerifier( unsigned media_nr, const media::MediaVerifierRef& verifier );

      /**
       * The label identifing this media set and to be sent in a media change request.
       */
      const std::string & label() const;

      /**
       * Set the label identifing this media set and to be sent in a media change request.
       */
      void setLabel( const std::string & label_r );

      enum ProvideFileOption
      {
        /**
         * The user is not asked anything, and the error
         * exception is just propagated */
        PROVIDE_DEFAULT = 0x0,
        PROVIDE_NON_INTERACTIVE = 0x1
      };
      ZYPP_DECLARE_FLAGS(ProvideFileOptions,ProvideFileOption);

      /**
         * Tries to fetch the given files and precaches them. Those files
         * need to be queried using provideFile and can be read from the cache directly.
         * The implementation is not allowed to block but needs to use seperate means to
         * download the files concurrently.
         * A backend can choose to completely ignore this functionaly, the default implementation
         * does nothing.
         *
         * \param files List of files that should be precached
         */
      void precacheFiles(const std::vector<OnMediaLocation> &files);

      /**
       * Provides a file from a media location.
       *
       * \param resource location of the file on media
       * \return local pathname of the requested file
       *
       * \throws MediaException if a problem occured and user has chosen to
       *         abort the operation. The calling code should take care
       *         to quit the current operation.
       * \throws SkipRequestException if a problem occured and user has chosen
       *         to skip the current operation. The calling code should continue
       *         with the next one, if possible.
       *
       *
       * If the resource is marked as optional, no Exception is thrown
       * and Pathname() is returned
       *
       * the optional deltafile argument describes a file that can
       * be used for delta download algorithms.
       *
       * \note interaction with the user does not ocurr if
       * \ref ProvideFileOptions::NON_INTERACTIVE is set.
       *
       * \note OnMediaLocation::optional() hint has no effect on the transfer.
       *
       * \see zypp::media::MediaManager::provideFile()
       */
      Pathname provideFile( const OnMediaLocation & resource, ProvideFileOptions options = PROVIDE_DEFAULT );

      /**
       * \deprecated The deltafile argument is part of the OnMediaLocation now, use the version of \ref provideFile( const OnMediaLocation & resource, ProvideFileOptions options )
       */
      ZYPP_DEPRECATED Pathname provideFile( const OnMediaLocation & resource, ProvideFileOptions options, const Pathname &deltafile );

      /**
       * Provides \a file from media \a media_nr.
       *
       * \param file path to the file relative to media URL
       * \param media_nr the media number in the media set
       * \return local pathname of the requested file
       *
       * \note interaction with the user does not ocurr if
       * \ref ProvideFileOptions::NON_INTERACTIVE is set.
       *
       * \note OnMediaLocation::optional() hint has no effect on the transfer.
       *
       * \throws MediaException if a problem occured and user has chosen to
       *         abort the operation. The calling code should take care
       *         to quit the current operation.
       * \throws SkipRequestException if a problem occured and user has chosen
       *         to skip the current operation. The calling code should continue
       *         with the next one, if possible.
       * \see zypp::media::MediaManager::provideFile()
       */
      Pathname provideFile(const Pathname & file, unsigned media_nr = 1, ProvideFileOptions options = PROVIDE_DEFAULT );

      /**
       * Provides an optional \a file from media \a media_nr.
       *
       * Like \ref provideFile (NON_INTERACTIVE), but return an empty \ref Pathname
       * rather than throwing a \ref MediaException if the file is not present on
       * the media.
       */
      Pathname provideOptionalFile( const Pathname & file, unsigned media_nr = 1 );

      /**
       * Provides \a file from \a url.
       *
       * \param absolute url to the file
       * \return local pathname of the requested file
       *
       * \note interaction with the user does not ocurr if
       * \ref ProvideFileOptions::NON_INTERACTIVE is set.
       *
       * \throws MediaException if a problem occured and user has chosen to
       *         abort the operation. The calling code should take care
       *         to quit the current operation.
       * \throws SkipRequestException if a problem occured and user has chosen
       *         to skip the current operation. The calling code should continue
       *         with the next one, if possible.
       * \see zypp::media::MediaManager::provideFile()
       */
      static ManagedFile provideFileFromUrl( const Url & file_url, ProvideFileOptions options = PROVIDE_DEFAULT );

      /**
       * Provides an optional \a file from \a url.
       *
       * Like \ref provideFileFromUrl( NON_INTERACTIVE ), but return an empty \ref Pathname
       * rather than throwing a \ref MediaException if the file is not present on
       * the media.
       */
      static ManagedFile provideOptionalFileFromUrl( const Url & file_url );

      /**
       * Release file from media.
       * This signal that file is not needed anymore.
       *
       * \param resource location of the file on media
       */
      void releaseFile( const OnMediaLocation &resource );


      /**
       * Release file from media.
       * This signal that file is not needed anymore.
       *
       * \param file path to the file relative to media URL
       * \param media_nr the media number in the media set
       */
      void releaseFile(const Pathname & file, unsigned media_nr = 1 );

      ///////////////////////////////////////////////////////////////////
      /// \class MediaSetAccess::ReleaseFileGuard
      /// \brief Release a provided file upon destruction.
      /// In case you don't want to wait until the \ref MediaSetAccess
      /// itself goes out of scope.
      /// \code
      ///   MediaSetAccess media;
      ///   OnMediaLocation loc;
      ///   {
      ///   	Pathname file = media.provideFile( loc );
      ///   	ReleaseFileGuard guard( media, loc );
      ///   }   // provided file is released here.
      /// \endcode
      /// \ingroup g_RAII
      ///////////////////////////////////////////////////////////////////
      struct ReleaseFileGuard
      {
        NON_COPYABLE( ReleaseFileGuard );
        NON_MOVABLE( ReleaseFileGuard );
        ReleaseFileGuard( MediaSetAccess & media_r, const OnMediaLocation & loc_r )
        : _media( media_r )
        , _loc( loc_r )
        {}
        ~ReleaseFileGuard()
        { _media.releaseFile( _loc ); }
      private:
        MediaSetAccess & _media;
        const OnMediaLocation & _loc;
      };

      /**
       * Provides direcotry \a dir from media number \a media_nr.
       *
       * \param dir path to the directory relative to media URL
       * \param recursive whether to provide the whole directory subtree
       * \param media_nr the media number in the media set
       * \return local pathname of the requested directory
       *
       * \throws MediaException if a problem occured and user has chosen to
       *         abort the operation. The calling code should take care
       *         to quit the current operation.
       * \todo throw SkipRequestException if a problem occured and user has chosen
       *         to skip the current operation. The calling code should continue
       *         with the next one, if possible.
       * \see zypp::media::MediaManager::provideDir()
       * \see zypp::media::MediaManager::provideDirTree()
       */
      Pathname provideDir(const Pathname & dir, bool recursive, unsigned media_nr = 1, ProvideFileOptions options = PROVIDE_DEFAULT );

      /**
       * Checks if a file exists on the specified media, with user callbacks.
       *
       * \param file file to check
       * \param media_nr Media number
       *
       * \throws MediaException if a problem occured and user has chosen to
       *         abort the operation. The calling code should take care
       *         to quit the current operation.
       * \throws SkipRequestException if a problem occured and user has chosen
       *         to skip the current operation. The calling code should continue
       *         with the next one, if possible.
       * \see zypp::media::MediaManager::doesFileExist(MediaAccessId,const Pathname&)
       */
      bool doesFileExist(const Pathname & file, unsigned media_nr = 1 );

      /**
       * Fills \ref retlist with directory information.
       */
      void dirInfo( filesystem::DirContent &retlist, const Pathname &dirname,
                    bool dots = true, unsigned media_nr = 1 );

      /**
       * Release all attached media of this set.
       *
       * \throws MediaNotOpenException for invalid access IDs.
       */
      void release();

      /**
       * Replaces media number in specified url with given \a medianr.
       *
       * Media number in the URL is searched for with regex
       * <tt> "^(.*(cd|dvd|media))([0-9]+)(\\.iso)$" </tt> for iso scheme and
       * with <tt> "^(.*(cd|dvd|media))([0-9]+)(/?)$" </tt> for other schemes.
       *
       * For cd and dvd scheme it returns the original URL, as well as for
       * URL which do not match the above regexes.
       *
       * \param url_r   original URL
       * \param medianr requested media number
       * \return        rewritten URL if applicable, the original URL otherwise
       */
      static Url rewriteUrl (const Url & url_r, const media::MediaNr medianr);

    protected:
      /**
       * Provides the \a file from medium number \a media_nr and returns its
       * local path.
       *
       * \note   The method must not throw if \a checkonly is <tt>true</tt>.
       *
       * \throws MediaException \a checkonly is <tt>false</tt> and
       *         a problem occured and user has chosen to
       *         abort the operation. The calling code should take care
       *         to quit the current operation.
       * \throws SkipRequestException \a checkonly is <tt>false</tt> and
       *         a problem occured and user has chosen
       *         to skip the current operation. The calling code should continue
       *         with the next one, if possible.
       */
      Pathname provideFileInternal( const OnMediaLocation &resource, ProvideFileOptions options );

      using ProvideOperation = function<void (media::MediaAccessId, const OnMediaLocation &)>;

      void provide( const ProvideOperation& op, const OnMediaLocation &resource, ProvideFileOptions options );

      media::MediaAccessId getMediaAccessId (media::MediaNr medianr);
      std::ostream & dumpOn( std::ostream & str ) const override;

    private:
      class Impl;
      /** Pointer to implementation */
      std::unique_ptr<Impl> _pimpl;
    };
    ///////////////////////////////////////////////////////////////////
    ZYPP_DECLARE_OPERATORS_FOR_FLAGS(MediaSetAccess::ProvideFileOptions);

    /** \relates MediaSetAccess Stream output */
    inline std::ostream & operator<<( std::ostream & str, const MediaSetAccess & obj )
    { return obj.dumpOn( str ); }


} // namespace zypp
///////////////////////////////////////////////////////////////////
#endif // ZYPP_SOURCE_MediaSetAccess_H
