/*******************************************************************************
 * Copyright (c) 2017 - 2018 fortiss GmbH
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Johannes Messmer - initial API and implementation and/or initial documentation
 *   Jose Cabral - Cleaning of namespaces
 *******************************************************************************/

#ifndef SRC_CORE_IO_DEVICE_CONTROLLER_H_
#define SRC_CORE_IO_DEVICE_CONTROLLER_H_

#include <extevhan.h>
#include <forte_sync.h>
#include <forte_thread.h>
#include <devlog.h>
#include <fortelist.h>

#include <io/mapper/io_handle.h>

namespace forte {
  namespace core {
    namespace io {

      class IOConfigFBController;

      /*! @brief Abstract Device Controller
       *
       * Base class of the device controller implementation.
       * All hardware specific IO implementations should inherit from this class.
       *
       * Features:
       * - runs Device Controller in separate thread
       * - thread safe communication with corresponding controller configuration fb (see #notifyConfigFB)
       * - base structure for safe startups and resets (see #init, #runLoop, and #deInit)
       * - automatic error handling and restart (see #IOConfigFBController)
       */
      class IODeviceController : protected CExternalEventHandler, protected CThread {
          friend class IOConfigFBController;

        public:
          /*! @brief Abstract configuration struct.
           *
           * The child controller should implement an own configuration struct, which inherits from this struct and is initialized with default parameters.
           * The controller configuration fb uses the #setConfig method to set the runtime configuration.
           */
          struct Config {

          };

          /*! @brief Abstract descriptor of device handles.
           *
           * Used to exchange information about handles between the device controller and the corresponding configuration fb.
           * The device implementation should extend the struct with properties, which uniquely identify the handle.
           * The #initHandle method then creates an #IOHandle instance based on the handle descriptor.
           */
          class HandleDescriptor {
            public:
              CIEC_WSTRING const &mId;
              IOMapper::Direction mDirection;

              HandleDescriptor(CIEC_WSTRING const &paId, IOMapper::Direction paDirection) :
                  mId(paId), mDirection(paDirection) {

              }
          };

          enum NotificationType {
            UnknownNotificationType,
            //! Notifies the configuration fb about a successful operation e.g. a successful startup.
            Success,
            //! Notifies the configuration about an error. It should be emitted during the init method or the runLoop method.
            Error,
            //! Placeholder for custom hardware specific communication between the device and configuration fb controller.
            Custom
          };
          NotificationType mNotificationType;
          const void* mNotificationAttachment;
          bool mNotificationHandled;

          /*! @brief Sets the configuration of the controller
           *
           * @param paConfig Pointer to the configuration struct provided by the corresponding controller configuration fb. Use *static_cast<CustomConfig*>(config) to retrieve the struct instance.
           */
          virtual void setConfig(Config* paConfig) = 0;

          void fireIndicationEvent(IOObserver *paObserver);

          virtual void handleChangeEvent(IOHandle *paHandle);

          //TODO: adapt this properly to the new handler model. This mockup is just to avoid the classes below to be abstract
          virtual size_t getIdentifier() const {
            return 0;
          }

        protected:
          explicit IODeviceController(CDeviceExecution& paDeviceExecution);

          /*! @brief Initializes the controller.
           *
           * The method should initialize and startup the controller.
           * All required hardware libraries should be started and checked for errors.
           * If the system requires an initialization sequence which is executed once at startup, place it here.
           * In case of an error, return an error description.
           *
           * @return 0/NULL if the initialization was successful. Otherwise return an error description.
           */
          virtual const char* init() = 0;

          /*! @brief Contains the blocking run sequence.
           *
           * The method should implement the algorithm, which sets and gets the state of the hardware handles.
           * In case of a runtime error, set the #error property.
           * It should always check if the controller is still active (#isAlive method).
           * If the controller got inactive or an error occurred, the method should return.
           *
           * @attention Never use While(true) loops without any sleep methods. They would consume all processing power and slow down FORTE.
           */
          virtual void runLoop() = 0;

          /*! @brief Deinitializes the controller.
           *
           * Stops all initialized hardware libraries.
           * Frees the allocated resources of the controller.
           */
          virtual void deInit() = 0;

          bool hasError();
          const char* mError;

          /*! @brief Notifies the corresponding configuration fb about a startup or runtime event.
           *
           * The method should not be used for time critical operations, such as a write operation of an output fb.
           * You should avoid multiple messages in a short time period, as notifications are not yet queued but dropped.
           *
           * @param paType Type of notification
           * @param paAttachment Attachment of notification. E.g. a const* char pointer should be attached to an Error notification.
           */
          void notifyConfigFB(NotificationType paType, const void* paAttachment = 0);

          typedef CSinglyLinkedList<IOHandle *> THandleList;

          /*! @brief Adds an IO handle to the controller
           *
           * This method is called by the corresponding configuration function block during the initialization.
           * The handle is automatically added to the correct list (#inputHandles and #outputHandles).
           * The handle lists should be accessed by the controller to read and write IOs.
           *
           * @param paHandleDescriptor Descriptor of the handle
           */
          virtual void addHandle(HandleDescriptor *paHandleDescriptor);

          /*! @brief Initializer for all IO handles.
           *
           * The method is called when a configuration fb adds an handle via the #addHandle method.
           * It should return an handle instance based on the descriptor information.
           *
           * @param paHandleDescriptor Descriptor of the handle
           */
          virtual IOHandle* initHandle(HandleDescriptor *paHandleDescriptor) = 0;

          /*! @brief Iterates over all input handles and fires an indication event in case of a change.
           *
           * The method iterates over the #inputHandles list.
           * It checks if the input handle has a bound observer and then calls the #isHandleValueEqual method.
           * If the #isHandleValueEqual returns true, the indication event is fired for the bound observer.
           * @attention The method does only work if the #isHandleValueEqual is overridden.
           */
          void checkForInputChanges();

          /*! @brief Checks if the value of a handle has changed. Used by the #checkForInputChanges method.
           *
           * @param paHandle IOHandle which should be compared to the previous IO state
           * @return True if the current state is equal to the previous IO state. In case it has changed, return false.
           */
          virtual bool isHandleValueEqual(IOHandle* paHandle);

          /*! @brief Synchronizes the access to the #inputHandles and #outputHandles. Use it for iterations over the lists. */
          CSyncObject mHandleMutex;

          /*! @brief All input handles of the main controller
           * The list should only contain input handles of the main controller.
           * The list is managed by the #addHandle and #dropHandles method.
           * #dropHandles is called automatically during deinitialization.
           */
          THandleList mInputHandles;

          /*! @brief All output handles of the main controller
           * The list should only contain output handles of the main controller.
           * The list is managed by the #addHandle and #dropHandles method.
           * #dropHandles is called automatically during deinitialization.
           */
          THandleList mOutputHandles;

        private:

          IOConfigFBController *mDelegate;

          /*! @brief Drops all handle instances which were previously added by the #addHandle method
           *
           * The method is automatically called during the deinitialization of the corresponding configuration function block.
           */
          void dropHandles();

          void run();

          void setInitDelay(int paDelay);

          int mInitDelay;

          void addHandle(THandleList* paList, CIEC_WSTRING const &paId, IOHandle* paHandle);

          // Functions needed for the external event handler interface
          void enableHandler(void) {
            //do nothing
          }
          void disableHandler(void) {
            //do nothing
          }
          void setPriority(int) {
            //do nothing
          }
          int getPriority(void) const {
            return 0;
          }
      };

    } //namespace IO
  } //namepsace core
} //namespace forte

#endif /* SRC_CORE_IO_DEVICE_CONTROLLER_H_ */
