//------------------------------------------------------------------------------
// Copyright (c) 2011-2017 by European Organization for Nuclear Research (CERN)
// Author: Krzysztof Jamrog <krzysztof.piotr.jamrog@cern.ch>,
//         Michal Simon <michal.simon@cern.ch>
//------------------------------------------------------------------------------
// This file is part of the XRootD software suite.
//
// XRootD is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// XRootD 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 Lesser General Public License
// along with XRootD.  If not, see <http://www.gnu.org/licenses/>.
//
// In applying this licence, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
//------------------------------------------------------------------------------

#ifndef __XRD_CL_OPERATION_HANDLERS_HH__
#define __XRD_CL_OPERATION_HANDLERS_HH__

#include "XrdCl/XrdClFile.hh"
#include "XrdCl/XrdClCtx.hh"

#include<functional>
#include<future>
#include <memory>

namespace XrdCl
{
  //----------------------------------------------------------------------------
  //! Helper class for unpacking single XAttrStatus from bulk response
  //----------------------------------------------------------------------------
  class UnpackXAttrStatus : public ResponseHandler
  {
    public:

      UnpackXAttrStatus( ResponseHandler *handler ) : handler( handler )
      {
      }

      //------------------------------------------------------------------------
      //! Callback method.
      //------------------------------------------------------------------------
      void HandleResponse( XRootDStatus *status, AnyObject *response )
      {
        // status maybe error for old servers not supporting xattrs
        if( !status->IsOK() )
        {
          handler->HandleResponse( status, nullptr );
          return;
        }

        std::vector<XAttrStatus> *bulk = nullptr;
        response->Get( bulk );
        *status = bulk->front().status;
        handler->HandleResponse( status, nullptr );
        delete response;
      }

    private:

      ResponseHandler *handler;
  };

  //----------------------------------------------------------------------------
  //! Helper class for unpacking single XAttr from bulk response
  //----------------------------------------------------------------------------
  class UnpackXAttr : public ResponseHandler
  {
    public:

      UnpackXAttr( ResponseHandler *handler ) : handler( handler )
      {
      }

      //------------------------------------------------------------------------
      //! Callback method.
      //------------------------------------------------------------------------
      void HandleResponse( XRootDStatus *status, AnyObject *response )
      {
        // status is always OK for bulk response

        std::vector<XAttr> *bulk = nullptr;
        response->Get( bulk );
        *status = bulk->front().status;
        std::string *rsp = new std::string( std::move( bulk->front().value ) );
        delete bulk;
        response->Set( rsp );
        handler->HandleResponse( status, response );
      }

    private:

      ResponseHandler *handler;
  };

  //----------------------------------------------------------------------------
  // Helper class for creating null references for particular types
  //
  // @arg Response : type for which we need a null reference
  //----------------------------------------------------------------------------
  template<typename Response>
  struct NullRef
  {
      static Response value;
  };

  //----------------------------------------------------------------------------
  // Initialize the 'null-reference'
  //----------------------------------------------------------------------------
  template<typename Response>
  Response NullRef<Response>::value;

  //----------------------------------------------------------------------------
  //! Unpack response
  //!
  //! @param rsp : AnyObject holding response
  //! @return    : the response
  //----------------------------------------------------------------------------
  template<typename Response>
  inline Response* GetResponse( AnyObject *rsp )
  {
    Response *ret = nullptr;
    rsp->Get( ret );
    return ret;
  }

  //----------------------------------------------------------------------------
  //! Unpack response
  //!
  //! @param rsp    : AnyObject holding response
  //! @param status :
  //! @return       : the response
  //----------------------------------------------------------------------------
  template<typename Response>
  inline Response* GetResponse( XRootDStatus *status, AnyObject *rsp )
  {
    if( !status->IsOK() ) return &NullRef<Response>::value;
    return GetResponse<Response>( rsp );
  }

  //----------------------------------------------------------------------------
  //! Lambda wrapper
  //!
  //! @arg ResponseType : type of response returned by the server
  //----------------------------------------------------------------------------
  template<typename Response>
  class FunctionWrapper: public ResponseHandler
  {
    public:

      //------------------------------------------------------------------------
      //! Constructor.
      //
      //! @param func : function, functor or lambda (2 arguments)
      //------------------------------------------------------------------------
      FunctionWrapper(
          std::function<void( XRootDStatus&, Response& )> handleFunction ) :
          fun( [handleFunction]( XRootDStatus &s, Response &r, HostList& ){ handleFunction( s, r ); } )
      {
      }

      //------------------------------------------------------------------------
      //! Constructor.
      //
      //! @param func : function, functor or lambda (3 arguments)
      //------------------------------------------------------------------------
      FunctionWrapper(
          std::function<void( XRootDStatus&, Response&, HostList& )> handleFunction ) :
          fun( handleFunction )
      {
      }

      //------------------------------------------------------------------------
      //! Callback method.
      //------------------------------------------------------------------------
      void HandleResponseWithHosts( XRootDStatus *status, AnyObject *response, HostList *hostList )
      {
        std::unique_ptr<XRootDStatus> delst( status );
        std::unique_ptr<AnyObject> delrsp( response );
        std::unique_ptr<HostList> delhl( hostList );
        Response *res = GetResponse<Response>( status, response );
        fun( *status, *res, *hostList );
      }

    private:
      //------------------------------------------------------------------------
      //! user defined function, functor or lambda
      //------------------------------------------------------------------------
      std::function<void( XRootDStatus&, Response&, HostList& )> fun;
  };

  //----------------------------------------------------------------------------
  //! Lambda wrapper
  //!
  //! Template specialization for responses that return no value (void)
  //----------------------------------------------------------------------------
  template<>
  class FunctionWrapper<void> : public ResponseHandler
  {
    public:

      //------------------------------------------------------------------------
      //! Constructor.
      //
      //! @param func : function, functor or lambda (1 argument)
      //------------------------------------------------------------------------
      FunctionWrapper(
          std::function<void( XRootDStatus& )> handleFunction ) :
          fun( [handleFunction]( XRootDStatus& s, HostList& ){ handleFunction( s ); } )
      {
      }

      //------------------------------------------------------------------------
      //! Constructor.
      //
      //! @param func : function, functor or lambda (2 arguments)
      //------------------------------------------------------------------------
      FunctionWrapper(
          std::function<void( XRootDStatus&, HostList& )> handleFunction ) :
          fun( handleFunction )
      {
      }

      //------------------------------------------------------------------------
      //! Callback method.
      //------------------------------------------------------------------------
      void HandleResponseWithHosts( XRootDStatus *status, AnyObject *response, HostList *hostList )
      {
        std::unique_ptr<XRootDStatus> delst( status );
        std::unique_ptr<AnyObject> delrsp( response );
        std::unique_ptr<HostList> delhl( hostList );
        fun( *status, *hostList );
      }

    private:
      //------------------------------------------------------------------------
      //! user defined function, functor or lambda
      //------------------------------------------------------------------------
      std::function<void( XRootDStatus&, HostList& )> fun;
  };

  //----------------------------------------------------------------------------
  //! Packaged Task wrapper
  //!
  //! @arg Response : type of response returned by the server
  //! @arg Return   : type of the value returned by the task
  //----------------------------------------------------------------------------
  template<typename Response, typename Return>
  class TaskWrapper: public ResponseHandler
  {
    public:

      //------------------------------------------------------------------------
      //! Constructor.
      //
      //! @param task : a std::packaged_task
      //------------------------------------------------------------------------
      TaskWrapper( std::packaged_task<Return( XRootDStatus&, Response& )> && task ) :
        task( std::move( task ) )
      {
      }

      //------------------------------------------------------------------------
      //! Callback method.
      //------------------------------------------------------------------------
      void HandleResponse( XRootDStatus *status, AnyObject *response )
      {
        std::unique_ptr<XRootDStatus> delst( status );
        std::unique_ptr<AnyObject> delrsp( response );
        Response *resp = GetResponse<Response>( status, response );
        task( *status, *resp );
      }

    private:

      //------------------------------------------------------------------------
      //! user defined task
      //------------------------------------------------------------------------
      std::packaged_task<Return( XRootDStatus&, Response& )> task;
  };

  //----------------------------------------------------------------------------
  //! Packaged Task wrapper, specialization for requests that have no response
  //! except for status.
  //!
  //! @arg Response : type of response returned by the server
  //! @arg Return   : type of the value returned by the task
  //----------------------------------------------------------------------------
  template<typename Return>
  class TaskWrapper<void, Return>: public ResponseHandler
  {
    public:

      //------------------------------------------------------------------------
      //! Constructor.
      //
      //! @param task : a std::packaged_task
      //------------------------------------------------------------------------
      TaskWrapper( std::packaged_task<Return( XRootDStatus& )> && task ) :
        task( std::move( task ) )
      {
      }

      //------------------------------------------------------------------------
      //! Callback method.
      //------------------------------------------------------------------------
      void HandleResponse( XRootDStatus *status, AnyObject *response )
      {
        std::unique_ptr<XRootDStatus> delst( status );
        std::unique_ptr<AnyObject> delrsp( response );
        task( *status );
      }

    private:

      //------------------------------------------------------------------------
      //! user defined task
      //------------------------------------------------------------------------
      std::packaged_task<Return( XRootDStatus& )> task;
  };


  //----------------------------------------------------------------------------
  //! Lambda wrapper
  //----------------------------------------------------------------------------
  class ExOpenFuncWrapper: public ResponseHandler
  {
    public:

      //------------------------------------------------------------------------
      //! Constructor.
      //
      //! @param func : function, functor or lambda (2 arguments)
      //------------------------------------------------------------------------
      ExOpenFuncWrapper( const Ctx<File> &f,
          std::function<void( XRootDStatus&, StatInfo& )> handleFunction ) :
          f( f ), fun( [handleFunction]( XRootDStatus &s, StatInfo &i, HostList& ){ handleFunction( s, i ); } )
      {
      }

      //------------------------------------------------------------------------
      //! Constructor.
      //
      //! @param func : function, functor or lambda (3 arguments)
      //------------------------------------------------------------------------
      ExOpenFuncWrapper( const Ctx<File> &f,
          std::function<void( XRootDStatus&, StatInfo&, HostList& )> handleFunction ) :
          f( f ), fun( handleFunction )
      {
      }

      //------------------------------------------------------------------------
      //! Callback method.
      //------------------------------------------------------------------------
      void HandleResponseWithHosts( XRootDStatus *status, AnyObject *response, HostList *hostList )
      {
        delete response;
        std::unique_ptr<XRootDStatus> delst( status );
        std::unique_ptr<StatInfo> delrsp;
        std::unique_ptr<HostList> delhl;
        StatInfo *info = nullptr;
        if( status->IsOK() )
        {
          XRootDStatus st = f->Stat( false, info );
          delrsp.reset( info );
        }
        else
          info = &NullRef<StatInfo>::value;
        fun( *status, *info, *hostList );
      }

    private:
      Ctx<File> f;
      //------------------------------------------------------------------------
      //! user defined function, functor or lambda
      //------------------------------------------------------------------------
      std::function<void( XRootDStatus&, StatInfo&, HostList& )> fun;
  };

  //----------------------------------------------------------------------------
  //! Pipeline exception, wrapps an XRootDStatus
  //----------------------------------------------------------------------------
  class PipelineException : public std::exception
  {
    public:

      //------------------------------------------------------------------------
      //! Constructor from XRootDStatus
      //------------------------------------------------------------------------
      PipelineException( const XRootDStatus &error ) : error( error ), strerr( error.ToString() )
      {

      }

      //------------------------------------------------------------------------
      //! Copy constructor.
      //------------------------------------------------------------------------
      PipelineException( const PipelineException &ex ) : error( ex.error ), strerr( ex.error.ToString() )
      {

      }

      //------------------------------------------------------------------------
      //! Assigment operator
      //------------------------------------------------------------------------
      PipelineException& operator=( const PipelineException &ex )
      {
        error  = ex.error;
        strerr = ex.strerr;
        return *this;
      }

      //------------------------------------------------------------------------
      //! inherited from std::exception
      //------------------------------------------------------------------------
      const char* what() const noexcept
      {
        return strerr.c_str();
      }

      //------------------------------------------------------------------------
      //! @return : the XRootDStatus
      //------------------------------------------------------------------------
      const XRootDStatus& GetError() const
      {
        return error;
      }

    private:

      //------------------------------------------------------------------------
      //! the XRootDStatus associated with this exception
      //------------------------------------------------------------------------
      XRootDStatus error;
      std::string  strerr;
  };

  //----------------------------------------------------------------------------
  //! A wrapper handler for a std::promise / std::future.
  //!
  //! @arg Response : response type
  //----------------------------------------------------------------------------
  template<typename Response>
  class FutureWrapperBase : public ResponseHandler
  {
    public:

      //------------------------------------------------------------------------
      //! Constructor, initializes the std::future argument from its
      //! own std::promise
      //!
      //! @param ftr : the future to be linked with this handler
      //------------------------------------------------------------------------
      FutureWrapperBase( std::future<Response> &ftr ) : fulfilled( false )
      {
        ftr = prms.get_future();
      }

      //------------------------------------------------------------------------
      //! Destructor
      //------------------------------------------------------------------------
      virtual ~FutureWrapperBase()
      {
        if( !fulfilled ) SetException( XRootDStatus( stError, errPipelineFailed ) );
      }

    protected:

      //------------------------------------------------------------------------
      //! Set exception in the std::promise / std::future
      //!
      //! @param err : the error
      //------------------------------------------------------------------------
      inline void SetException( const XRootDStatus &err )
      {
        std::exception_ptr ex = std::make_exception_ptr( PipelineException( err ) );
        prms.set_exception( ex );
        fulfilled = true;
      }

      //------------------------------------------------------------------------
      //! promise that corresponds to the future
      //------------------------------------------------------------------------
      std::promise<Response> prms;
      bool                   fulfilled;
  };

  //----------------------------------------------------------------------------
  //! A wrapper handler for a std::promise / std::future.
  //!
  //! @arg Response : response type
  //----------------------------------------------------------------------------
  template<typename Response>
  class FutureWrapper : public FutureWrapperBase<Response>
  {
    public:

      //------------------------------------------------------------------------
      //! Constructor, @see FutureWrapperBase
      //!
      //! @param ftr : the future to be linked with this handler
      //------------------------------------------------------------------------
      FutureWrapper( std::future<Response> &ftr ) : FutureWrapperBase<Response>( ftr )
      {
      }

      //------------------------------------------------------------------------
      //! Callback method.
      //------------------------------------------------------------------------
      void HandleResponse( XRootDStatus *status, AnyObject *response )
      {
        std::unique_ptr<XRootDStatus> delst( status );
        std::unique_ptr<AnyObject> delrsp( response );
        if( status->IsOK() )
        {
          Response *resp = GetResponse<Response>( response );
          if( resp == &NullRef<Response>::value )
            this->SetException( XRootDStatus( stError, errInternal ) );
          else
          {
            this->prms.set_value( std::move( *resp ) );
            this->fulfilled = true;
          }
        }
        else
          this->SetException( *status );
      }
  };

  //----------------------------------------------------------------------------
  //! A wrapper handler for a std::promise / std::future, overload for void type
  //----------------------------------------------------------------------------
  template<>
  class FutureWrapper<void> : public FutureWrapperBase<void>
  {
    public:

      //------------------------------------------------------------------------
      //! Constructor, @see FutureWrapperBase
      //!
      //! @param ftr : the future to be linked with this handler
      //------------------------------------------------------------------------
      FutureWrapper( std::future<void> &ftr ) : FutureWrapperBase<void>( ftr )
      {
      }

      //------------------------------------------------------------------------
      //! Callback method.
      //------------------------------------------------------------------------
      void HandleResponse( XRootDStatus *status, AnyObject *response )
      {
        std::unique_ptr<XRootDStatus> delst( status );
        std::unique_ptr<AnyObject> delrsp( response );
        if( status->IsOK() )
        {
          prms.set_value();
          fulfilled = true;
        }
        else
          SetException( *status );
      }
  };


  //----------------------------------------------------------------------------
  //! Wrapper class for raw response handler (ResponseHandler).
  //----------------------------------------------------------------------------
  class RawWrapper : public ResponseHandler
  {
    public:

      //------------------------------------------------------------------------
      //! Constructor
      //!
      //! @param handler : the actual operation handler
      //------------------------------------------------------------------------
      RawWrapper( ResponseHandler *handler ) : handler( handler )
      {
      }

      //------------------------------------------------------------------------
      //! Callback method (@see ResponseHandler)
      //!
      //! Note: does not delete itself because it is assumed that it is owned
      //!       by the PipelineHandler (@see PipelineHandler)
      //------------------------------------------------------------------------
      virtual void HandleResponseWithHosts( XRootDStatus *status,
                                            AnyObject    *response,
                                            HostList     *hostList )
      {
        handler->HandleResponseWithHosts( status, response, hostList );
      }

    private:
      //------------------------------------------------------------------------
      //! The actual operation handler (we don't own the pointer)
      //------------------------------------------------------------------------
      ResponseHandler *handler;
  };


  //----------------------------------------------------------------------------
  //! A base class for factories, creates ForwardingHandlers from
  //! ResponseHandler*, ResponseHandler& and std::future<Response>
  //!
  //! @arg Response : response type
  //----------------------------------------------------------------------------
  template<typename Response>
  struct RespBase
  {
      //------------------------------------------------------------------------
      //!  A factory method, simply forwards the given handler
      //!
      //! @param hdlr : the ResponseHandler that should be wrapped
      //! @return     : a ForwardingHandler instance
      //------------------------------------------------------------------------
      inline static ResponseHandler* Create( ResponseHandler *hdlr )
      {
        return new RawWrapper( hdlr );
      }

      //------------------------------------------------------------------------
      //!  A factory method, simply forwards the given handler
      //!
      //! @param hdlr : the ResponseHandler that should be wrapped
      //! @return     : a ForwardingHandler instance
      //------------------------------------------------------------------------
      inline static ResponseHandler* Create( ResponseHandler &hdlr )
      {
        return new RawWrapper( &hdlr );
      }

      //------------------------------------------------------------------------
      //! A factory method
      //!
      //! @arg   Response : response type
      //! @param ftr      : the std::future that should be wrapped
      //------------------------------------------------------------------------
      inline static ResponseHandler* Create( std::future<Response> &ftr )
      {
        return new FutureWrapper<Response>( ftr );
      }
  };

  //----------------------------------------------------------------------------
  //! Factory class, creates ForwardingHandler from std::function, in addition
  //! to what RespBase provides (@see RespBase)
  //!
  //! @arg Response : response type
  //----------------------------------------------------------------------------
  template<typename Response>
  struct Resp: RespBase<Response>
  {
      //------------------------------------------------------------------------
      //! A factory method
      //!
      //! @param func : the function/functor/lambda that should be wrapped
      //! @return     : FunctionWrapper instance
      //------------------------------------------------------------------------
      inline static ResponseHandler* Create( std::function<void( XRootDStatus&,
          Response& )> func )
      {
        return new FunctionWrapper<Response>( func );
      }

      //------------------------------------------------------------------------
      //! A factory method
      //!
      //! @param func : the function/functor/lambda that should be wrapped
      //! @return     : FunctionWrapper instance
      //------------------------------------------------------------------------
      inline static ResponseHandler* Create( std::function<void( XRootDStatus&,
          Response&, HostList& )> func )
      {
        return new FunctionWrapper<Response>( func );
      }

      //------------------------------------------------------------------------
      //! A factory method
      //!
      //! @param task : the task that should be wrapped
      //! @return     : TaskWrapper instance
      //------------------------------------------------------------------------
      template<typename Return>
      inline static ResponseHandler* Create( std::packaged_task<Return( XRootDStatus&,
          Response& )> &task )
      {
        return new TaskWrapper<Response, Return>( std::move( task ) );
      }

      //------------------------------------------------------------------------
      //! Make the Create overloads from RespBase visible
      //------------------------------------------------------------------------
      using RespBase<Response>::Create;
  };

  //----------------------------------------------------------------------------
  //! Factory class, overloads Resp for void type
  //!
  //! @arg Response : response type
  //----------------------------------------------------------------------------
  template<>
  struct Resp<void>: RespBase<void>
  {
      //------------------------------------------------------------------------
      //! A factory method
      //!
      //! @param func : the function/functor/lambda that should be wrapped
      //! @return     : SimpleFunctionWrapper instance
      //------------------------------------------------------------------------
      inline static ResponseHandler* Create( std::function<void( XRootDStatus& )> func )
      {
        return new FunctionWrapper<void>( func );
      }

      //------------------------------------------------------------------------
      //! A factory method
      //!
      //! @param func : the function/functor/lambda that should be wrapped
      //! @return     : SimpleFunctionWrapper instance
      //------------------------------------------------------------------------
      inline static ResponseHandler* Create( std::function<void( XRootDStatus&, HostList& )> func )
      {
        return new FunctionWrapper<void>( func );
      }

      //------------------------------------------------------------------------
      //! A factory method
      //!
      //! @param task : the task that should be wrapped
      //! @return     : TaskWrapper instance
      //------------------------------------------------------------------------
      template<typename Return>
      inline static ResponseHandler* Create( std::packaged_task<Return( XRootDStatus& )> &task )
      {
        return new TaskWrapper<void, Return>( std::move( task ) );
      }

      //------------------------------------------------------------------------
      //! Make the Create overloads from RespBase visible
      //------------------------------------------------------------------------
      using RespBase<void>::Create;
  };
}

#endif // __XRD_CL_OPERATIONS_HANDLERS_HH__