/*
 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */

#pragma once

#include <string>
#include <VirtualDevice.hpp>
#include <ExternalObject.hpp>
#include "iotcs_storage_object.h"
#include <Exception.hpp>
#include <time.h>

#ifdef __MBED__
#include "mbed.h"
#endif
/**
 * @file StorageObject.hpp
 * @brief iotdcl::StorageObject provides information about content in the cloud storage.
 *
 * <pre>
 *     // Upload example
 *     StorageObject lenna =
 *         directlyConnectedDevice.createStorageObject("lenna.jpg", "image/jpeg");
 *     lenna.setInputPath("../images/lenna.jpg");
 *
 *     // onSync is called when the content referenced by the storageObject
 *     // is in sync with the storage cloud, or the sync has failed.
 *    
 *     class MySyncCallback: public SyncCallback<VirtualDevice> {
 *         public:
 *             void onSync(SyncEvent<VirtualDevice>& event) const {
 *                StorageObject& storageObject = event.getSource();
 *                 if (storageObject.getSyncStatus() == IN_SYNC) {
 *                     // image was uploaded and can be deleted
 *                 } else if (storageObject.getSyncStatus() == SYNC_FAILED) {
 *                     // image was not uploaded, take action!
 *                 }
 *            }
 *     };
 *     lenna.setOnSync(mySyncCallback);
 *     virtualDevice.set("image", lenna);
 *
 *     // Download example
 *     // onChange is called when the attribute value changes.
 *     class MyChangeCallback: public ChangeCallback {
 *         public:
 *             virtual void onChange(const ChangeEvent<VirtualDevice>& event) const {
 *                 NamedValue& namedValue = event.getNamedValue();
 *                 StorageObject& storageObject = (StorageObject)namedValue.getValue<StorageObject&>();
 *                 if (storageObject.getLength() < availableDiskSpace) {
 *                     // syncTo will kick off the async download of the content
 *                     storageObject.setOnSync(mySyncCallback);
 *                     storageObject.setOutputPath("../downloads/filename");
 *                     storageObject.sync();
 *                 }
 *             }
 *         }
 *     };
 *     virtualDevice.setOnChange(myChangeCallback);
 * </pre>
 *
 */
namespace iotdcl {
    class StorageObject;
    /**
     * @brief The status of whether or not the content is in sync with the storage cloud.
     */
    enum SyncStatus {
        /**
         * @brief The content is in sync with the storage cloud. This means that
         * the upload or download is complete.
         */
        IN_SYNC = 0,

        /**
         * @brief The content is not in sync with the storage cloud because
         * the upload or download failed.
         */
        SYNC_FAILED,
        
        /**
         * @brief The content is not in sync with the storage cloud because
         * it has not yet been uploaded or download.
         */
        NOT_IN_SYNC,

        /**
         * @brief The content is not in sync with the storage cloud, but a
         * sync is pending.
         */
        SYNC_PENDING
    };

    /**
     * @brief An event passed to the setOnSync(SyncCallback) SyncCallback
     * when content referred to by an attribute value has been successfully
     * synchronized, or has failed to be synchronized.
     * @param <V> the type of AbstractVirtualDevice
     */
    template<class V> class SyncEvent {
        public:
            /** 
             * @brief Get the virtual device that is the source of the event.
             * @return the virtual device, or NULL if StorageObject.sync() was 
             * called independently
             */
            virtual V* getVirtualDevice() const = 0;

            /**
             * @brief Get the name of the attribute, action, or format that this event
             * is associated with.
             * @return the name 
             * independently 
             */
            virtual const std::string& getName() const = 0;

            /**
             * @brief Get the iotdcl::StorageObject} that is the source of this event.
             * @return the storageObject
             */
            virtual StorageObject* getSource() const = 0;

            /**
             * @brief Destructor
             */
            virtual ~SyncEvent() {};
    };

    /**
     * @brief A syncCallback interface for receiving an event when content referred to by
     * an attribute value has been successfully synchronized, or has failed
     * to be synchronized.
     * @param <V> the type of AbstractVirtualDevice
     * @see StorageObject::setOnSync(SyncCallback)
     */
    class SyncCallback {
        public:
            /**
             * @brief Callback for receiving an event when content referred to by
             * an attribute value has been successfully synchronized, or has failed
             * to be synchronized.
             * @param event synchronization status
             */
            virtual void onSync(const SyncEvent<VirtualDevice>& event) const = 0;

            /**
             * @brief Desctructor
             */
            virtual ~SyncCallback() {
            }
    };

    class StorageObject : public ExternalObject {
        public:
            /**
             * @brief Destructor
             */
            virtual ~StorageObject();

            /**
             * @brief Get the the name of this object in the storage cloud. 
             * @return the name of this object in the storage cloud
             */
            virtual const std::string& getName() const;

            /**
             * @brief Get the mime-type of the content. See
             * <a href="http://www.iana.org/assignments/media-types/media-types.xhtml">IANA Media Types</a>.
             * @return The mime-type of the content
             */
            virtual const std::string& getType() const;

            /**
             * @brief Get the compression scheme of the content.
             * @return the compression scheme of the content, or empty string if the content is not compressed
             */
            virtual const std::string& getEncoding() const;

            /**
             * @brief Get the length of the content in bytes. This is the number of bytes required to upload or download
             * the content.
             * @return The length of the content, or -1 if unknown
             */
            virtual long getLength() const;

            /**
             * @brief Get the date and time the content was created or last modified in cloud 
             * storage. This may be NULL if the content has not been uploaded.
             * The date and time stamp format is ISO 8601.
             * @return The date the content was last modified in cloud storage, or NULL if the
             * content has not been uploaded.
             */
            virtual time_t getDate() const;

            /**
             * @brief Get the status of whether or not the content is in sync with the storage cloud.
             * @return the status of whether or not the content is in sync with the storage cloud.
             */
            virtual SyncStatus getSyncStatus() const;

            /**
             * @brief Get the input path.
             * @return the input path, or empty string if not set
             */
            virtual const std::string& getInputPath() const;

            /**
             * @brief Set the input path for uploading content to the storage cloud. The implementation
             * allows for either the input path to be set, or the output path to be set, but not both.
             * If the inputPath parameter is not empty, the output path will empty
             * If the inputPath parameter is not empty and does not equal the current input path,
             * the sync status will be reset to SyncStatus#NOT_IN_SYNC}.
             * This method will throw an std::invalid_argument if
             * StorageObject::getSyncStatus() sync status is SyncStatus#SYNC_PENDING},
             * @param inputPath the path from which to read the content for upload
             * @throw iotdcl::GeneralException if the output path cannot be written
             * @throw std::invalid_argument if called when sync status is iotdcl::SyncStatus#SYNC_PENDING
             */
            virtual void setInputPath(const std::string& inputPath) throw (GeneralException, std::invalid_argument);

            /**
             * @brief Get the output path.
             * @return the output path, or empty string if not set
             */
            virtual const std::string& getOutputPath() const;

            /**
             * Set the output path for downloading content from the storage cloud. The implementation
             * allows for either the output path to be set, or the input path to be set, but not both.
             * If the outputPath parameter is not empty, the input path will be set to empty.
             * If the outputPath parameter is not empty and does not equal the current output path,
             * the sync status will be reset to iotdcl::SyncStatus#NOT_IN_SYNC.
             * This method will throw an std::invalid_argument if the
             * StorageObject.getSyncStatus() sync status is iotdcl::SyncStatus#SYNC_PENDING,
             * @param outputPath the path to which the content will be downloaded. If the path does not
             * already exist, it will be created.
             * @throw iotdcl::GeneralException if the output path cannot be written
             * @throw std::invalid_argument if called when sync status is iotdcl::SyncStatus#SYNC_PENDING
             */
            virtual void setOutputPath(const std::string& outputPath) throw (GeneralException, std::invalid_argument);

            /**
             * Notify the library to sync content with the storage cloud.
             * The direction of the sync, upload or download, depends on whether
             * the setInputPath(std::string) input path or the
             * setOutputPath(std::string) output path} has been set.
             * This method does not start any uploads or downloads if getSyncStatus() sync status
             * is other than iotdcl::SyncStatus#NOT_IN_SYNC.
             * <p>
             * This is a non-blocking call. The sync is performed on a separate thread.
             * The status of the sync can be monitored by setting a iotdcl::SyncCallback
             * on this StorageObject.
             * <p>
             * If the input path cannot be read, or the output path cannot be
             * written, an iotdcl::GeneralException is thrown. Any I/O exceptions
             * during the background sync are reported through the
             * iotdcl::ErrorCallback error syncCallback}
             * of the virtual device.
             *
             * @throw std::invalid_argument if both input path and output path are empty
             */
            virtual void sync() throw (GeneralException, std::invalid_argument);

            /**
             * Set a iotdcl::SyncCallback that is invoked when the content
             * referred to by this StorageObject is synchronized.
             * @param callback a callback to invoke when there is an error setting a
             * value, if NULL, the existing syncCallback will be removed
             */
            virtual void setOnSync(const SyncCallback *callback);
#ifndef IOTCS_DOXYGEN
        protected:
            VirtualDevice *vd;
            void setVirtualDevice(VirtualDevice *vd);
            iotcs_storage_object_handle c_handler() const;
            StorageObject() {};
            std::string *name;
            std::string *outPath;
            std::string *inPath;
            std::string *type;
            std::string *encoding;
            friend VirtualDevice;
            friend Alert;
            friend Data;
#endif
    };
};
