/// @file movable_base.h
#pragma once
#include <stdint.h>
#include "../config/axis.h"
#include "../hal/tmc2130.h"

namespace modules {
namespace motion {

/// Base class for movable modules - #modules::idler::Idler and #modules::selector::Selector contains the common code
class MovableBase {
public:
    /// Internal states of the state machine
    enum {
        Ready = 0, // intentionally set as zero in order to allow zeroing the Idler structure upon startup -> avoid explicit initialization code
        Moving,
        PlannedHome,
        HomeForward,
        HomeBack,
        TMCFailed,
        HomingFailed
    };

    /// Operation (Engage/Disengage/MoveToSlot) return values
    enum class OperationResult : uint8_t {
        Accepted, ///< the operation has been successfully started
        Refused, ///< another operation is currently underway, cannot start a new one
        Failed ///< the operation could not been started due to HW issues
    };

    inline constexpr MovableBase(config::Axis axis)
        : state(Ready)
        , plannedSlot(-1)
        , currentSlot(-1)
        , homingValid(false)
        , axis(axis)
        , axisStart(0) {}

    /// virtual ~MovableBase(); intentionally disabled, see description in logic::CommandBase

    /// @returns currently active slot
    /// This state is updated only when a planned move is successfully finished, so it is safe for higher-level
    /// state machines to use this call as a waiting condition for the desired state of the derive class (idler/selector)
    /// While homing, Slot() returns 0xff as the current slot index is invalid.
    inline uint8_t Slot() const { return currentSlot; }

    /// @returns internal state of the state machine
    inline uint8_t State() const { return state; }

    inline hal::tmc2130::ErrorFlags TMCErrorFlags() const { return tmcErrorFlags; }

    /// Invalidates the homing flag - that is now used to inform the movable component (Idler or Selector)
    /// that their current coordinates may have been compromised and a new homing move is to be performed.
    /// Each movable component performs the homing move immediately after it is possible to do so:
    /// - Idler immediately (and then moves to desired slot again)
    /// - Selector once there is no filament stuck in it (and then moves to desired slot again)
    /// Homing procedure therefore becomes completely transparent to upper layers
    /// and it will not be necessary to call it explicitly.
    /// Please note this method does not clear any planned move on the component
    /// - on the contrary - the planned move will be peformed immediately after homing
    /// (which makes homing completely transparent)
    inline void InvalidateHoming() { homingValid = false; }

    /// Prepare a homing move of the axis
    void PlanHome();

    inline bool HomingValid() const { return homingValid; }

    inline config::Axis Axis() const { return axis; }

protected:
    /// internal state of the automaton
    uint8_t state;

    /// planned slot - where to move to
    uint8_t plannedSlot;

    /// current slot
    uint8_t currentSlot;

    /// true if the axis is considered as homed
    bool homingValid;

    /// cached TMC2130 error flags - being read only if the axis is enabled and doing something (moving)
    hal::tmc2130::ErrorFlags tmcErrorFlags;

    config::Axis axis;

    int32_t axisStart;

    virtual void PrepareMoveToPlannedSlot() = 0;
    virtual void PlanHomingMoveForward() = 0;
    virtual void PlanHomingMoveBack() = 0;
    /// @returns true if the measured axis length is within the expected range, false otherwise
    virtual bool FinishHomingAndPlanMoveToParkPos() = 0;
    virtual void FinishMove() = 0;

    /// Initializes movement of a movable module.
    /// Beware: this operation reinitializes the axis/TMC driver as well (may introduce axis creep as we have seen on the Idler)
    OperationResult InitMovement();

    /// Initializes movement of a movable module without reinitializing the axis/TMC driver
    OperationResult InitMovementNoReinitAxis();

    void PerformMove();

    void PerformHomeForward();
    void PerformHomeBack();

    void HomeFailed();

    void CheckTMC();
};

} // namespace motion
} // namespace modules
