#pragma once

#include "hardware/spi.h"
#include "hardware/gpio.h"
#include "../../common/pimoroni_common.hpp"

namespace pimoroni {

  class PMW3901 {
    spi_inst_t *spi = PIMORONI_SPI_DEFAULT_INSTANCE;

    //--------------------------------------------------
    // Constants
    //--------------------------------------------------
  public:
    static const uint8_t FRAME_SIZE                         = 35;
    static const uint16_t FRAME_BYTES                       = 1225;
    static const uint16_t DEFAULT_MOTION_TIMEOUT_MS         = 5000;
    static const uint16_t DEFAULT_FRAME_CAPTURE_TIMEOUT_MS  = 10000;
  protected:
    static const uint8_t WAIT = -1;

    //--------------------------------------------------
    // Enums
    //--------------------------------------------------
  public:
    enum Degrees {
      DEGREES_0 = 0,
      DEGREES_90,
      DEGREES_180,
      DEGREES_270,
    };


    //--------------------------------------------------
    // Variables
    //--------------------------------------------------
  private:
    // interface pins with our standard defaults where appropriate
    uint cs         = SPI_BG_FRONT_CS;
    uint sck        = SPI_DEFAULT_SCK;
    uint mosi       = SPI_DEFAULT_MOSI;
    uint miso       = SPI_DEFAULT_MISO;
    uint interrupt  = SPI_BG_FRONT_PWM;

    uint32_t spi_baud = 400000;


    //--------------------------------------------------
    // Constructors/Destructor
    //--------------------------------------------------
  public:
    PMW3901() {}

    PMW3901(BG_SPI_SLOT slot) {
      switch(slot) {
        case PICO_EXPLORER_ONBOARD: // Don't read too much into this, the case is just here to avoid a compile error
          cs = SPI_BG_FRONT_CS;
          interrupt = PIN_UNUSED;
          break;
        case BG_SPI_FRONT:
          cs = SPI_BG_FRONT_CS;
          interrupt = SPI_BG_FRONT_PWM;
          break;
        case BG_SPI_BACK:
          cs = SPI_BG_BACK_CS;
          interrupt = SPI_BG_BACK_PWM;
          break;
      }
    }

    PMW3901(spi_inst_t *spi,
            uint cs, uint sck, uint mosi, uint miso, uint interrupt) :
      spi(spi),
      cs(cs), sck(sck), mosi(mosi), miso(miso), interrupt(interrupt) {}

    virtual ~PMW3901() {}


    //--------------------------------------------------
    // Methods
    //--------------------------------------------------
  public:
    bool init();

    spi_inst_t* get_spi() const;
    int get_cs() const;
    int get_sck() const;
    int get_mosi() const;
    int get_miso() const;
    int get_int() const;

    uint8_t get_id();
    uint8_t get_revision();
    void set_rotation(Degrees degrees = DEGREES_0);
    void set_orientation(bool invert_x = true, bool invert_y = true, bool swap_xy = true);
    bool get_motion(int16_t& x_out, int16_t& y_out, uint16_t timeout_ms = DEFAULT_MOTION_TIMEOUT_MS);
    bool get_motion_slow(int16_t& x_out, int16_t& y_out, uint16_t timeout_ms = DEFAULT_MOTION_TIMEOUT_MS);
    bool frame_capture(uint8_t (&data_out)[FRAME_BYTES], uint16_t& data_size_out, uint16_t timeout_ms = DEFAULT_FRAME_CAPTURE_TIMEOUT_MS);

  protected:
    virtual void secret_sauce();

    void cs_select();
    void cs_deselect();
    void write_register(uint8_t reg, uint8_t data);
    void write_buffer(uint8_t *buf, uint16_t len);
    void read_registers(uint8_t reg, uint8_t *buf, uint16_t len);
    uint8_t read_register(uint8_t reg);
    uint32_t millis();
  };

  class PAA5100 : public PMW3901 {
  public:
    virtual ~PAA5100() {}
  protected:
    virtual void secret_sauce();
  };
}
