#include "ExceptionHandlingDecorator.h"
#include "DeviceModule.h"

#include <functional>

namespace ChimeraTK {

  template<typename UserType>
  ExceptionHandlingDecorator<UserType>::ExceptionHandlingDecorator(
      boost::shared_ptr<ChimeraTK::NDRegisterAccessor<UserType>> accessor, DeviceModule& devMod,
      VariableDirection direction, boost::shared_ptr<NDRegisterAccessor<UserType>> recoveryAccessor)
  : ChimeraTK::NDRegisterAccessorDecorator<UserType>(accessor), deviceModule(devMod),
    _recoveryAccessor(recoveryAccessor), _direction(direction) {
    // Register recoveryAccessor at the DeviceModule
    if(recoveryAccessor != nullptr) {
      // version number is still {nullptr} (i.e. invalid)
      _recoveryHelper = boost::make_shared<RecoveryHelper>(recoveryAccessor, VersionNumber(nullptr));
      deviceModule.addRecoveryAccessor(_recoveryHelper);
    }
  }

  template<typename UserType>
  void ExceptionHandlingDecorator<UserType>::setOwnerValidity(bool hasExceptionNow) {
    if(hasExceptionNow != previousReadFailed) {
      previousReadFailed = hasExceptionNow;
      if(!_owner) return;
      if(hasExceptionNow) {
        _owner->incrementExceptionCounter();
      }
      else {
        _owner->decrementExceptionCounter();
      }
    }
  }

  template<typename UserType>
  void ExceptionHandlingDecorator<UserType>::doReadTransferSynchronously() {
    if(transferAllowed) {
      ChimeraTK::NDRegisterAccessorDecorator<UserType>::doReadTransferSynchronously();
    }
  }

  template<typename UserType>
  bool ExceptionHandlingDecorator<UserType>::doWriteTransfer(ChimeraTK::VersionNumber versionNumber) {
    if(transferAllowed) {
      return ChimeraTK::NDRegisterAccessorDecorator<UserType>::doWriteTransfer(versionNumber);
    }
    else {
      return true; /* data loss */
    }
  }

  template<typename UserType>
  bool ExceptionHandlingDecorator<UserType>::doWriteTransferDestructively(ChimeraTK::VersionNumber versionNumber) {
    if(transferAllowed) {
      return ChimeraTK::NDRegisterAccessorDecorator<UserType>::doWriteTransferDestructively(versionNumber);
    }
    else {
      return true; /* data loss */
    }
  }

  template<typename UserType>
  void ExceptionHandlingDecorator<UserType>::doPreWrite(TransferType type, VersionNumber versionNumber) {
    /* For writable accessors, copy data to the recoveryAcessor before perfroming the write.
     * Otherwise, the decorated accessor may have swapped the data out of the user buffer already.
     * This obtains a shared lock from the DeviceModule, hence, the regular writing happeniin here
     * can be performed in shared mode of the mutex and accessors are not blocking each other.
     * In case of recovery, the DeviceModule thread will take an exclusive lock so that this thread can not
     * modify the recoveryAcessor's user buffer while data is written to the device.
     */
    {
      auto lock{deviceModule.getRecoverySharedLock()};

      if(_recoveryAccessor != nullptr) {
        // Access to _recoveryAccessor is only possible channel-wise
        for(unsigned int ch = 0; ch < _recoveryAccessor->getNumberOfChannels(); ++ch) {
          _recoveryAccessor->accessChannel(ch) = buffer_2D[ch];
        }
        // FIXME: This should be the version number of the owner, which is not set at the moment
        _recoveryHelper->versionNumber = {};
      }
      else {
        throw ChimeraTK::logic_error(
            "ChimeraTK::ExceptionhandlingDecorator: Calling write() on a non-writeable accessor is not supported ");
      }
    } // lock guard goes out of scope

    // #138 Phase 1. Change for phase 2
    // Starting a transfer is only allowed if the status is running (not in initialisation or shutdown)
    transferAllowed = (Application::getInstance().getLifeCycleState() == LifeCycleState::run);
    // the waiting is only necessary as a hack for phase 1 because DeviceModule::startTransfer is not there yet
    if(transferAllowed) deviceModule.waitForRecovery();

    // Now delegate call to the generic decorator, which swaps the buffer, without adding our exception handling with the generic transfer
    // preWrite and postWrite are only delegated if the transfer is allowed.
    if(transferAllowed) {
      ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPreWrite(type, versionNumber);
    }
  }

  template<typename UserType>
  void ExceptionHandlingDecorator<UserType>::setOwner(EntityOwner* owner) {
    _owner = owner;
    if(_direction.dir == VariableDirection::feeding && previousReadFailed) {
      _owner->incrementExceptionCounter(false); // do not write. We are still in the setup phase.
    }
  }

  template<typename UserType>
  void ExceptionHandlingDecorator<UserType>::doPostRead(TransferType type, bool hasNewData) {
    bool hasException = false;
    try {
      // preRead has not been called when the transfer was not allowed. Don't call postRead in this case.
      if(transferAllowed) {
        this->_target->postRead(type, hasNewData);
      }
    }
    catch(ChimeraTK::runtime_error& e) {
      deviceModule.reportException(e.what());
      hasException = true;
      // #138 Phase 1: Remove for phase 2
      // Inform the owner about the failed read (will be informed again after successful recovery
      setOwnerValidity(hasException);
    }
    // #138 Phase 2: change if codition here
    if(hasException || !transferAllowed) {
      // Try to recover and read until it succeeds.
      // We are already behind the delegated postRead, so the transfer in the target is already complemted.
      // So we have to use a complete blocking preRead, readTransfer, postRead, i.e. _tagret->read()
      while(true) {
        deviceModule.waitForRecovery();
        /* //#138 Phase 2
         * if (!deviceModule->startTransfer()) continue;
         * // end  of #138 Phase 2
         */
        try {
          this->_target->read();
          hasException = false;
          hasNewData = true; // if read() returns there is always new data
          /* //#138 Phase 2
           * deviceModule->stopTransfer());
           * // end  of #138 Phase 2
           */
          break;
        }
        catch(ChimeraTK::runtime_error&) {
          /* //#138 Phase 2
           * deviceModule->stopTransfer());
           * // end  of #138 Phase 2
           */
        }
      }
    }
    setOwnerValidity(hasException);
    // only replace the user buffer if there really is new data
    if(hasNewData) {
      for(size_t i = 0; i < buffer_2D.size(); ++i)
        buffer_2D[i].swap(this->_target->accessChannel(static_cast<unsigned int>(i)));
    }
  }

  template<typename UserType>
  void ExceptionHandlingDecorator<UserType>::doPreRead(TransferType type) {
    /* #138 Phase 1. Change this for phase 2 */
    /* Hack for phase 1 because DeviceModule::startTransfer is not there yet. */
    deviceModule.waitForRecovery();
    transferAllowed = true;
    //    std::cout << "recovered" << std::endl;
    //    transferAllowed = (Application::getInstance().getLifeCycleState() == LifeCycleState::run);
    //    auto l = Application::getInstance().getLifeCycleState();
    //    std::cout << "LifeCycleState: "
    //              << (l == LifeCycleState::run ? "run" : (l == LifeCycleState::initialisation ? "init" : "shutdown"))
    //              << std::endl;
    //    assert(transferAllowed); // not true in phase 2 any more

    // only delegate preRead and postRead if  the transfer is allowerd
    if(transferAllowed) {
      ChimeraTK::NDRegisterAccessorDecorator<UserType>::doPreRead(type);
    }
  }

  INSTANTIATE_TEMPLATE_FOR_CHIMERATK_USER_TYPES(ExceptionHandlingDecorator);

} /* namespace ChimeraTK */