#ifndef GPIO_H
#define GPIO_H

#include "STM32F0x1.h"

/** 
  * @brief General Purpose IO
  */

typedef enum {
  GPIO_Mode_IN   = 0x00, /*!< GPIO Input Mode              */
  GPIO_Mode_OUT  = 0x01, /*!< GPIO Output Mode             */
  GPIO_Mode_AF   = 0x02, /*!< GPIO Alternate function Mode */
  GPIO_Mode_AN   = 0x03  /*!< GPIO Analog In/Out Mode      */
} GPIOMode_TypeDef;

typedef enum {
  GPIO_OType_PP = 0x00,
  GPIO_OType_OD = 0x01
} GPIOOType_TypeDef;

typedef enum {
  GPIO_Speed_Level_1  = 0x01, /*!< Medium Speed */
  GPIO_Speed_Level_2  = 0x02, /*!< Fast Speed   */
  GPIO_Speed_Level_3  = 0x03  /*!< High Speed   */
} GPIOSpeed_TypeDef;

typedef enum {
  GPIO_PuPd_NOPULL = 0x00,
  GPIO_PuPd_UP     = 0x01,
  GPIO_PuPd_DOWN   = 0x02
} GPIOPuPd_TypeDef;
/// Enum pro PortNumber
typedef enum {
  GpioPortA,
  GpioPortB,
  GpioPortC,
  GpioPortD,
  GpioPortF
} GpioPortNum;
/// Asociace port Adress a RCC clock
struct GpioAssocPort {
  GPIOF_Type * const portAdr;
  const uint32_t    clkMask;
};
/** @file
  * @brief Obecný GPIO pin.
  * 
  * @class GpioClass
  * @brief Obecný GPIO pin.
  * 
  *  Ukázka přetížení operátorů. Návratové hodnoty jsou v tomto případě celkem zbytečné,
  * ale umožňují řetězení, takže je možné napsat např.
    @code
    +-+-+-led;
    @endcode
  * a máme na led 3 pulsy. Je to sice blbost, ale funguje.
  * Všechny metody jsou konstantní, protože nemění data uvnitř třídy.
  * Vlastně ani nemohou, protože data jsou konstantní.
*/
class GpioClass {
  public:
    /** Konstruktor
    @param port GpioPortA | GpioPortB | GpioPortC | GpioPortD | GpioPortF
    @param no   číslo pinu na portu
    @param type IN, OUT, AF, AN default OUT 
    */
    explicit GpioClass (GpioPortNum const port, const uint32_t no, const GPIOMode_TypeDef type = GPIO_Mode_OUT) noexcept;
    /// Nastav pin @param b na tuto hodnotu
    const GpioClass& operator<< (const bool b) const {
      if (b) io->BSRR.R = pos;
      else   io->BRR.R  = pos;
      return *this;
    }
//![Gpio example]
    /// Nastav pin na log. H
    const GpioClass& operator+ (void) const {
      io->BSRR.R = (uint32_t) pos;
      return *this;
    }
    /// Nastav pin na log. L
    const GpioClass& operator- (void) const {
      io->BRR.R  = pos;
      return *this;
    }
    /// Změň hodnotu pinu
    const GpioClass& operator~ (void) const {
      io->ODR.R ^= pos;
      return *this;
    };
    /// Načti logickou hodnotu na pinu
    const bool get (void) const {
      if (io->IDR.R & pos) return true;
      else                 return false;
    };
    /// A to samé jako operátor
    const GpioClass& operator>> (bool& b) const {
      b = get();
      return *this;
    }
    operator bool () const {
      return get();
    }
//![Gpio example]
    void setMode (GPIOMode_TypeDef p) {
      uint32_t dno = num * 2;
      io->MODER.R   &= ~(3UL << dno);
      io->MODER.R   |=  (p   << dno);
    }
    void setOType (GPIOOType_TypeDef p) {
      io->OTYPER.R  &= (uint16_t)~(1UL << num);
      io->OTYPER.R  |= (uint16_t) (p   << num);
    }
    void setSpeed (GPIOSpeed_TypeDef p) {
      uint32_t dno = num * 2;
      io->OSPEEDR.R &= ~(3UL << dno);
      io->OSPEEDR.R |=  (p   << dno);
    }
    void setPuPd (GPIOPuPd_TypeDef p) {
      uint32_t dno = num * 2;
      io->PUPDR.R   &= ~(3UL << dno);
      io->PUPDR.R   |=  (p   << dno);
    }
    void setAF (unsigned af) {
      unsigned int pd,pn = num;
      pd = (pn & 7) << 2; pn >>= 3;
      if (pn) {
        io->AFRH.R &= ~(0xFU << pd);
        io->AFRH.R |=  (  af << pd);
      } else {
        io->AFRL.R &= ~(0xFU << pd);
        io->AFRL.R |=  (  af << pd);
      }
    }
  private:
    /// Port.
    GPIOF_Type * const io;
    /// A pozice pinu na něm, stačí 16.bit
    const uint16_t   pos;
    /// pro funkce setXXX necháme i číslo pinu
    const uint16_t   num;
  
};

#endif // GPIO_H