/*
  Copyright 2021 Grégory Soutadé

  This file is part of libgourou.

  libgourou 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.

  libgourou 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 Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with libgourou. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef _LIBGOUROU_H_
#define _LIBGOUROU_H_

#include "bytearray.h"
#include "device.h"
#include "user.h"
#include "fulfillment_item.h"
#include "drmprocessorclient.h"

#include <pugixml.hpp>
#include <stdint.h>

#ifndef HOBBES_DEFAULT_VERSION
#define HOBBES_DEFAULT_VERSION  "10.0.4"
#endif

#ifndef ACS_SERVER
#define ACS_SERVER              "https://adeactivate.adobe.com/adept"
#endif

#define LIBGOUROU_VERSION       "0.8.9"

namespace gourou
{
    /**
     * @brief Main class that handle all ADEPTS functions (fulfill, download, signIn, activate)
     */
    class DRMProcessor
    {
    public:

        static const std::string VERSION;

        enum ITEM_TYPE { EPUB=0, PDF };
        /**
         * @brief Main constructor. To be used once all is configured (user has signedIn, device is activated)
         *
         * @param client          Client processor
         * @param deviceFile      Path of device.xml
         * @param activationFile  Path of activation.xml
         * @param deviceKeyFile   Path of devicesalt
         */
        DRMProcessor(DRMProcessorClient* client, const std::string& deviceFile, const std::string& activationFile, const std::string& deviceKeyFile);

        ~DRMProcessor();

        /**
         * @brief Fulfill ACSM file to server in order to retrieve ePub fulfillment item
         *
         * @param ACSMFile       Path of ACSMFile
         * @param notify         Notify server if requested by response
         *
         * @return a FulfillmentItem if all is OK
         */
        FulfillmentItem* fulfill(const std::string& ACSMFile, bool notify=true);

        /**
         * @brief Once fulfilled, ePub file needs to be downloaded.
         * During this operation, DRM information is added into downloaded file
         *
         * @param item            Item from fulfill() method
         * @param path            Output file path
         * @param resume          false if target file should be truncated, true to try resume download
         *
         * @return Type of downloaded item
         */
        ITEM_TYPE download(FulfillmentItem* item, std::string path, bool resume=false);

        /**
         * @brief SignIn into ACS Server (required to activate device)
         *
         * @param adobeID         AdobeID username
         * @param adobePassword   Adobe password
         */
        void signIn(const std::string& adobeID, const std::string& adobePassword);

        /**
         * @brief Activate newly created device (user must have successfuly signedIn before)
         */
        void activateDevice();

        /**
         * @brief Return loaned book to server
         *
         * @param loanID          Loan ID received during fulfill
         * @param operatorURL     URL of operator that loans this book
         * @param notify          Notify server if requested by response
         */
        void returnLoan(const std::string& loanID, const std::string& operatorURL, bool notify=true);

        /**
         * @brief Return default ADEPT directory (ie /home/<user>/.config/adept)
         */
        static std::string getDefaultAdeptDir(void);

        /**
         * @brief Create a new ADEPT environment (device.xml, devicesalt and activation.xml).
         *
         * @param client          Client processor
         * @param randomSerial    Always generate a new device (or not)
         * @param dirName         Directory where to put generated files (.adept)
         * @param hobbes          Override hobbes default version
         * @param ACSServer       Override main ACS server (default adeactivate.adobe.com)
         */
        static DRMProcessor* createDRMProcessor(DRMProcessorClient* client,
                                                bool randomSerial=false, std::string dirName=std::string(""),
                                                const std::string& hobbes=std::string(HOBBES_DEFAULT_VERSION),
                                                const std::string& ACSServer=ACS_SERVER);

        /**
         * @brief Get current log level
         */
        static int getLogLevel();

        /**
         * @brief Set log level (higher number for verbose output)
         */
        static void setLogLevel(int logLevel);

        /**
         * Functions used internally, should not be called by user
         */

        /**
         * @brief Send HTTP (GET or POST) request
         *
         * @param URL             HTTP URL
         * @param POSTData        POST data if needed, if not set, a GET request is done
         * @param contentType     Optional content type of POST Data
         * @param responseHeaders Optional Response headers of HTTP request
         * @param fd              Optional File descriptor to write received data
         * @param resume          false if target file should be truncated, true to try resume download (works only in combination of a valid fd)
         *
         * @return data of HTTP response
         */
        ByteArray sendRequest(const std::string& URL, const std::string& POSTData=std::string(), const char* contentType=0, std::map<std::string, std::string>* responseHeaders=0, int fd=0, bool resume=false);

        /**
         * @brief Send HTTP POST request to URL with document as POSTData
         */
        ByteArray sendRequest(const pugi::xml_document& document, const std::string& url);

        /**
         * @brief In place encrypt data with private device key
         */
        ByteArray encryptWithDeviceKey(const unsigned char* data, unsigned int len);

        /**
         * @brief In place decrypt data with private device key
         */
        ByteArray decryptWithDeviceKey(const unsigned char* data, unsigned int len);

        /**
         * @brief Return base64 encoded value of RSA public key
         */
        std::string serializeRSAPublicKey(void* rsa);

        /**
         * @brief Return base64 encoded value of RSA private key encrypted with private device key
         */
        std::string serializeRSAPrivateKey(void* rsa);

        /**
         * @brief Export clear private license key into path
         */
        void exportPrivateLicenseKey(std::string path);

        /**
         * @brief Get current user
         */
        User* getUser() { return user; }

        /**
         * @brief Get current device
         */
        Device* getDevice() { return device; }

        /**
         * @brief Get current client
         */
        DRMProcessorClient* getClient() { return client; }

        /**
         * @brief Remove ADEPT DRM
         * Warning: for PDF format, filenameIn must be different than filenameOut
         *
         * @param filenameIn         Input file (with ADEPT DRM)
         * @param filenameOut        Output file (without ADEPT DRM)
         * @param type               Type of file (ePub or PDF)
         * @param encryptionKey      Optional encryption key, do not try to decrypt the one inside input file
         * @param encryptionKeySize  Size of encryption key (if provided)
         */
        void removeDRM(const std::string& filenameIn, const std::string& filenameOut, ITEM_TYPE type, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0);

    private:
        gourou::DRMProcessorClient* client;
        gourou::Device* device;
        gourou::User* user;

        DRMProcessor(DRMProcessorClient* client);

        void pushString(void* sha_ctx, const std::string& string);
        void pushTag(void* sha_ctx, uint8_t tag);
        void hashNode(const pugi::xml_node& root, void *sha_ctx, std::map<std::string,std::string> nsHash);
        void hashNode(const pugi::xml_node& root, unsigned char* sha_out);
        void signNode(pugi::xml_node& rootNode);
        void addNonce(pugi::xml_node& root);
        void buildAuthRequest(pugi::xml_document& authReq);
        void buildInitLicenseServiceRequest(pugi::xml_document& initLicReq, std::string operatorURL);
        void doOperatorAuth(std::string operatorURL);
        void operatorAuth(std::string operatorURL);
        void buildFulfillRequest(pugi::xml_document& acsmDoc, pugi::xml_document& fulfillReq);
        void buildActivateReq(pugi::xml_document& activateReq);
        void buildReturnReq(pugi::xml_document& returnReq, const std::string& loanID, const std::string& operatorURL);
        ByteArray sendFulfillRequest(const pugi::xml_document& document, const std::string& url);
        void buildSignInRequest(pugi::xml_document& signInRequest, const std::string& adobeID, const std::string& adobePassword, const std::string& authenticationCertificate);
        void fetchLicenseServiceCertificate(const std::string& licenseURL,
                                            const std::string& operatorURL);
        void buildNotifyReq(pugi::xml_document& returnReq, pugi::xml_node& body);
        void notifyServer(pugi::xml_node& notifyRoot);
        void notifyServer(pugi::xml_document& fulfillReply);
        std::string encryptedKeyFirstPass(pugi::xml_document& rightsDoc, const std::string& encryptedKey, const std::string& keyType);
        void decryptADEPTKey(pugi::xml_document& rightsDoc, unsigned char* decryptedKey, const unsigned char* encryptionKey=0, unsigned encryptionKeySize=0);
        void removeEPubDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
        void generatePDFObjectKey(int version,
                                  const unsigned char* masterKey, unsigned int masterKeyLength,
                                  int objectId, int objectGenerationNumber,
                                  unsigned char* keyOut);
        void removePDFDRM(const std::string& filenameIn, const std::string& filenameOut, const unsigned char* encryptionKey, unsigned encryptionKeySize);
    };
}

#endif
