/*
 * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

/**
 * @file Action.hpp
 * @brief Action Base class and related definitions for IoT Client
 *
 * Defines a base class to be used by all Actions that can be run by the
 * IoT Client. Also contains definitions for related Action types like
 * ActionType, ActionState and ActionData
 */

#pragma once

#include <string>
#include <iostream>
#include <memory>
#include <atomic>

#include "util/Utf8String.hpp"
#include "util/threading/ThreadTask.hpp"

#include "ResponseCode.hpp"
#include "NetworkConnection.hpp"

#define DEFAULT_NETWORK_ACTION_THREAD_SLEEP_DURATION_MS 100

namespace awsiotsdk {
    /**
     * @brief ActionType Enum Class
     *
     * Defines a strongly typed enum which specifies different Action Types.
     * Actions can be registered in the core client for each of these Types.
     */
    enum class ActionType {
        RESERVED_ACTION = 0,
        CORE_PROCESS_INBOUND = 1,
        CORE_PROCESS_OUTBOUND = 2,
        CONNECT = 3,
        DISCONNECT = 4,
        PUBLISH = 5,
        PUBACK = 6,
        PUBREC = 7,
        PUBREL = 8,
        PUBCOMP = 9,
        SUBSCRIBE = 10,
        READ_INCOMING = 11,
        KEEP_ALIVE = 12,
        PUBLISH_INBOUND = 13,
        UNSUBSCRIBE = 14,
        RECONNECT = 15,
        GREENGRASS_DISCOVER = 16
    };

    /**
     * @brief Action State Class
     *
     * Defines an Action State class which is retained by each Action for its lifetime
     * This is a pure virtual class, cannot be instantiated
     */
    class ActionState {
    public:
        /**
         * @brief Get Action ID of the next Action
         *
         * @return uint16_t Next Action ID
         */
        virtual uint16_t GetNextActionId() = 0;

        // Rule of 5 stuff
        // Keeping defaults, explicitly mentioned to avoid conflicts with future C++ specifications
        // Must always be able to copy and move
        ActionState() = default;                                 // Default constructor
        ActionState(const ActionState &) = delete;               // Copy constructor
        ActionState(ActionState &&) = delete;                    // Move constructor
        ActionState &operator=(const ActionState &) & = delete;  // Copy assignment operator
        ActionState &operator=(ActionState &&) & = delete;       // Move assignment operator
        virtual ~ActionState() = default;                        // Default destructor
    };

    /**
     * @brief Action Data Class
     *
     * Defines an Action Data class which contains dynamic information to be used by the action
     * Instance of concrete implementation of this class is passed as argument each time Perform Action is called
     * This is a pure virtual class, cannot be instantiated
     */
    class ActionData {
    public:
        /**
         * Define a type for the Async Ack notification handler
         * Clients can provide an instance of this to receive notification on status of Async API calls
         */
        typedef std::function<void(uint16_t action_id, ResponseCode rc)> AsyncAckNotificationHandlerPtr;

        // Rule of 5 stuff
        // Since virtual destructor is present, derived classes will have to explicitly define defaults
        // Must always be able to copy and move
        ActionData() = default;                                 // Default constructor
        ActionData(const ActionData &) = default;               // Copy constructor
        ActionData(ActionData &&) = default;                    // Move constructor
        ActionData &operator=(const ActionData &) & = default;  // Copy assignment operator
        ActionData &operator=(ActionData &&) & = default;       // Move assignment operator
        virtual ~ActionData() = default;                        // Default destructor

        AsyncAckNotificationHandlerPtr p_async_ack_handler_;    ///< Handler to call when response is received for this action

        /**
         * @brief Get ID of the current run of this Action
         * @return uint16_t - Action ID
         */
        virtual uint16_t GetActionId() = 0;

        /**
         * @brief Set the Action ID for this run of the Action
         *
         * @param action_id - new Action ID
         */
        virtual void SetActionId(uint16_t action_id) = 0;
    };

    /**
     * @brief Action Class
     *
     * Defines a base class for SDK Actions. Provides basic template for concrete implementations.
     * Also includes code for Thread sync with client core.
     * All Actions that can be performed by the Client Core must inherit from this class.
     * This is a pure virtual class and cannot be instantiated
     *
     */
    class Action {
    public:
        /**
         * Define a type for Create Factory method. Takes Action state as argument and returns a unique_ptr to a new
         * action instance
         */
        typedef std::function<std::unique_ptr<Action>(std::shared_ptr<ActionState> p_action_state)> CreateHandlerPtr;

        /**
         * @brief Get Type of this Action
         * @return ActionType
         */
        ActionType GetActionType() {
            return action_type_;
        }

        /**
         * @brief Get information/description about the current action
         *
         * Gets runtime information about the currently running Action if it was set when the action was created
         *
         * @return String containing Info Text
         */
        util::String GetActionInfo() {
            return action_info_string_;
        }

        /**
         * @brief Sets the parent thread sync variable
         *
         * @param p_thread_continue - Pointer to the new sync variable to use
         */
        void SetParentThreadSync(std::shared_ptr<std::atomic_bool> p_thread_continue) {
            p_thread_continue_ = p_thread_continue;
        }

        /**
         * @brief Virtual base function for Performing Action
         *
         * This function is called by Client Core and defines how the Action is Performed.
         * This is a pure virtual function. Inherited classes MUST implement this.
         *
         * @param p_network_connection - Network connection to be used to perform the Action
         * @param p_action_data - Action data to be used for this run of the action
         * @return ResponseCode indicating result of the API call
         */
        virtual ResponseCode PerformAction(std::shared_ptr<NetworkConnection> p_network_connection,
                                           std::shared_ptr<ActionData> p_action_data) = 0;

        // Rule of 5 stuff
        // Disabling default, move and copy constructors
        // Actions instances can be run as threads if needed and should not be copied or moved
        Action() = delete;                             // Delete Default constructor
        Action(const Action &) = delete;               // Copy constructor
        Action(Action &&) = delete;                    // Move constructor
        Action &operator=(const Action &) & = delete;  // Copy assignment operator
        Action &operator=(Action &&) & = delete;       // Move assignment operator
        virtual ~Action() = default;                   // Default destructor

        /**
         * @brief Action Constructor
         *
         * @param action_type - Type fo the Action being instantiated
         * @param action_info_string - Info string describing the action
         */
        Action(ActionType action_type, util::String action_info_string);

    protected:
        ActionType action_type_;                               ///< Type of the action
        util::String action_info_string_;                      ///< Info string
        std::shared_ptr<std::atomic_bool> p_thread_continue_;  ///< Shared atomic variable used for sync when action is run in separate thread

        /**
         * @brief Generic Network Read function for all actions
         * @param p_network_connection - Network connection to be used to perform Read
         * @param read_buf - Buffer read data should be copied to. Assumed to already have enough memory reserved
         * @param bytes_to_read - Number of bytes to read
         * @return ResponeCode indicating result of the API call
         */
        ResponseCode ReadFromNetworkBuffer(std::shared_ptr<NetworkConnection> p_network_connection,
                                           util::Vector<unsigned char> &read_buf, size_t bytes_to_read);

        /**
         * @brief Generic Network Write function for all actions
         * @param p_network_connection - Network connection to be used to perform Write
         * @param read_buf - Buffer containing data to be written to the network instance
         * @return ResponeCode indicating result of the API call
         */
        ResponseCode WriteToNetworkBuffer(std::shared_ptr<NetworkConnection> p_network_connection,
                                          const util::String &write_buf);
    };
}