/**
 *******************************************************************************
 * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
 * All rights reserved.
 *
 * This software component is licensed by WCH under BSD 3-Clause license,
 * the "License"; You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at:
 *                        opensource.org/licenses/BSD-3-Clause
 *
 *******************************************************************************
 */

#include "pins_arduino.h"
#include "mik32_hal_adc.h"
#include "wiring_analog.h"
#include "wiring_LL.h" 

bool spiNssPinIsBlocked = false;

// list of pin numbers from both ports with pins inside the port
const HAL_PinsTypeDef digitalPinToGpioPinArray[] =
{
  GPIO_PIN_5, // D0
  GPIO_PIN_6, // D1
  GPIO_PIN_10,// D2
  GPIO_PIN_0, // D3
  GPIO_PIN_8, // D4
  GPIO_PIN_1, // D5
  GPIO_PIN_2, // D6
  GPIO_PIN_8, // D7
  GPIO_PIN_9, // D8
  GPIO_PIN_3, // D9
  GPIO_PIN_3, // D10
  GPIO_PIN_1, // D11
  GPIO_PIN_0, // D12
  GPIO_PIN_2, // D13
  GPIO_PIN_5, // D14/A0
  GPIO_PIN_7, // D15/A1
  GPIO_PIN_4, // D16/A2
  GPIO_PIN_7, // D17/A3
  GPIO_PIN_12,// D18
  GPIO_PIN_13,// D19
  GPIO_PIN_9, // A4 (board pin 20)
  GPIO_PIN_9, // A5 (board pin 21)
  GPIO_PIN_7, // LED(pin 22)  
  GPIO_PIN_6  // BTN(pin 23) 
};

// etermines the address of the port by the board pin number to which this pin belongs on the MCU
GPIO_TypeDef* digitalPinToPort(uint32_t digPinNumber)
{
  GPIO_TypeDef* gpioNum = 0;
  // port 0 - board pins 0...6, 9, 16(A2), 17(A3), 20(A4), 21(A5)
  if ((digPinNumber >= 0 && digPinNumber <= 6) || digPinNumber == 9 || digPinNumber == 16 
      || digPinNumber == 17 || digPinNumber == 20 || digPinNumber == 21) // 12 pieces
    gpioNum = GPIO_0;
  // port 1 - board pins 7, 8, 10...13, 14(А0), 15(А1), 18,19
  else if (digPinNumber == 7 || digPinNumber == 8 || (digPinNumber >= 10 && digPinNumber <= 15) 
          || digPinNumber == 18 || digPinNumber == 19) // 10 pieces
    gpioNum = GPIO_1;
  // port 2 - led and button
  else if (digPinNumber == LED_BUILTIN || digPinNumber == BTN_BUILTIN)
    gpioNum = GPIO_2;

  return gpioNum;
}

// determines the pin address inside the port by the board pin number
HAL_PinsTypeDef digitalPinToBitMask(uint32_t digPinNumber)
{
  if (digPinNumber < pinCommonQty())
  {
    HAL_PinsTypeDef mask;
    // if spi is on default pin 1.3 is needed for spi, D10 is replaced to pin 1.4
    if (spiNssPinIsBlocked && (digPinNumber == 10))
      mask = GPIO_PIN_4;
    else 
      mask = digitalPinToGpioPinArray[digPinNumber];
    return mask;
  }
  else
    return NC; 
}

uint16_t pinCommonQty(void)
{
  return (uint16_t)(sizeof(digitalPinToGpioPinArray)/sizeof(digitalPinToGpioPinArray[0]));
}

// the function returns a reference to the OUTPUT address of the GPIO register
volatile uint32_t* portOutputRegister(GPIO_TypeDef* GPIO_x)
{
  return &GPIO_x->OUTPUT_;
}

// the function returns a reference to the STATE address of the GPIO register
volatile uint32_t* portInputRegister(GPIO_TypeDef* GPIO_x)
{
  return &GPIO_x->STATE;
}

// the function initializes additional MCU pins depending on the specified pin number
void additionalPinsInit(uint32_t PinNumber)
{
  // if we use pin A5, we need to set SELA45 (1.15) to 1 to switch the output from A4 to A5
  if (PinNumber == A5)   
  {
    HAL_GPIO_PinConfig(GPIO_1, GPIO_PIN_15, HAL_GPIO_MODE_GPIO_OUTPUT, HAL_GPIO_PULL_NONE, HAL_GPIO_DS_2MA);
    HAL_GPIO_WritePin(GPIO_1, GPIO_PIN_15, GPIO_PIN_HIGH);
  }
  else if(PinNumber == A4)
  {
    // return the switch to A4 in case A5 was previously read
    HAL_GPIO_PinConfig(GPIO_1, GPIO_PIN_15, HAL_GPIO_MODE_GPIO_OUTPUT, HAL_GPIO_PULL_NONE, HAL_GPIO_DS_2MA);
    HAL_GPIO_WritePin(GPIO_1, GPIO_PIN_15, GPIO_PIN_LOW);
  }
}

// ---------------------- ADC ---------------------- //
// determines the ADC channel number by the board pin number
uint32_t analogInputToChannelNumber(uint32_t PinNumber)
{
  uint32_t adcChannel = 0;

  // if get a value 0...5 instead of A0...A5
  if (PinNumber < 4) PinNumber += 14;
  else if (PinNumber < 6) PinNumber += 16;

  switch (PinNumber)
  {
    case PIN_A0:
      adcChannel = ADC_CHANNEL0;
      break;
    case PIN_A1:
      adcChannel = ADC_CHANNEL1;
      break;
    case PIN_A2:
      adcChannel = ADC_CHANNEL3;
      break;
    case PIN_A3:
      adcChannel = ADC_CHANNEL4;
      break;
    case PIN_A4:
    case PIN_A5:
      adcChannel = ADC_CHANNEL5;
      break;
    default:
      adcChannel = NC;
  }
  return adcChannel;
}

// ---------------------- PWM ---------------------- //
// use only if digitalPinHasPWM() == true
#define PWM_PIN_TO_PORT_NUMBER(pin)           (((pin==10)||(pin==11)) ? 1:0)
// use only if digitalPinHasPWM() == true
static inline uint8_t pwmPinToGpioPinShift(uint8_t digitalPin)
{
  if (digitalPin == 3)
    return 0;
  else if ((digitalPin == 5) || (digitalPin == 11))
    return 1;
  else if (digitalPin == 6)
    return 2;
  else // pins 9 10
    return 3; 
}
// use only if digitalPinHasPWM() == true
// return true if digitalPin configured as pwm
bool digitalPinPwmIsOn(uint8_t digitalPin)
{
  uint8_t config = 0;
  uint8_t pinShift = pwmPinToGpioPinShift(digitalPin);

  if (PWM_PIN_TO_PORT_NUMBER(digitalPin) == 0)
    config = PIN_GET_PAD_CONFIG(PORT_0_CFG, pinShift);
  else 
    config = PIN_GET_PAD_CONFIG(PORT_1_CFG, pinShift);

  if (config == 2)
    return true;
  else
    return false;
}

bool digitalPinHasPWM(uint8_t p)   
{
  bool ret = false;
  // if spi is in use D10 cannot work as pwm
  if (spiNssPinIsBlocked && (p == 10))
    ret = false;
  else if ((p) == 3 || (p) == 5 || (p) == 6 || (p) == 9 || (p) == 10 || (p) == 11)
    ret = true;
  return ret;
}

// function is used only if digitalPinHasPWM() is true
TIMER32_TypeDef* pwmPinToTimer(uint32_t digPinNumber)
{
  TIMER32_TypeDef* timerNum = NULL;

  // timer 1
  if (digPinNumber == 3 || digPinNumber == 5 || digPinNumber == 6 || digPinNumber == 9)
    timerNum = TIMER32_1;
  // timer 2
  else if (digPinNumber == 10 || digPinNumber == 11)
    timerNum = TIMER32_2;
  
  return timerNum;
}

// function is used only if digitalPinHasPWM() is true
HAL_TIMER32_CHANNEL_IndexTypeDef pwmPinToTimerChannel(uint32_t digPinNumber)
{
  HAL_TIMER32_CHANNEL_IndexTypeDef channel = 0;
  if      (digPinNumber == 3)                         channel = TIMER32_CHANNEL_0;
  else if (digPinNumber == 5 || digPinNumber == 11)   channel = TIMER32_CHANNEL_1;
  else if (digPinNumber == 6)                         channel = TIMER32_CHANNEL_2;
  else if (digPinNumber == 9 || digPinNumber == 10)   channel = TIMER32_CHANNEL_3;
  return channel;
}

// ---------------------- interrupts ---------------------- //
// interrupt table is stored in ram to improve performance
// index = interrupt number. In each row {digitalPinNumber, IntLineValue, IntMuxValue}
uint8_t interruptInfo[EXTERNAL_INTERRUPTS_QTY][3] = 
{
  {     2,      GPIO_LINE_2,  GPIO_MUX_LINE_2_PORT0_10},  // INT0
  {     3,      GPIO_LINE_0,  GPIO_MUX_LINE_0_PORT0_0},   // INT1
  {     4,      GPIO_LINE_4,  GPIO_MUX_LINE_4_PORT0_8},   // INT2
  {     5,      GPIO_LINE_1,  GPIO_MUX_LINE_1_PORT0_1},   // INT3
  {     8,      GPIO_LINE_5,  GPIO_MUX_LINE_5_PORT1_9},   // INT4
  {     9,      GPIO_LINE_3,  GPIO_MUX_LINE_3_PORT0_3},   // INT5
  {BTN_BUILTIN, GPIO_LINE_6,  GPIO_MUX_LINE_6_PORT2_6},   // INT6 (button)
};

int8_t digitalPinToGpioIntMux(uint8_t digPinNumber)
{
  for (uint8_t i = 0; i < EXTERNAL_INTERRUPTS_QTY; i++)
  {
    if (interruptInfo[i][0] == digPinNumber)
      return interruptInfo[i][2];
  }
  return NOT_AN_INTERRUPT;
}
int8_t digitalPinToGpioIntLine(uint8_t digPinNumber)
{
  for (uint8_t i = 0; i < EXTERNAL_INTERRUPTS_QTY; i++)
  {
    if (interruptInfo[i][0] == digPinNumber)
      return interruptInfo[i][1];
  }
  return NOT_AN_INTERRUPT;
}

int8_t gpioIntLineToInterrupt(uint32_t gpioIntLine)
{
  for (uint8_t i = 0; i < EXTERNAL_INTERRUPTS_QTY; i++)
  {
    if (interruptInfo[i][1] == gpioIntLine)
      return i;
  }
  return NOT_AN_INTERRUPT;
}

int8_t digitalPinToInterrupt(uint32_t digPinNumber)
{
  for (uint8_t i = 0; i < EXTERNAL_INTERRUPTS_QTY; i++)
  {
    if (interruptInfo[i][0] == digPinNumber)
      return i;
  }
  return NOT_AN_INTERRUPT;
}

// ---------------------- SPI ---------------------- //
void spi_onBegin(void)
{
  // On Elbear Ace-Uno rev1.1.0 there is a seller on pin 1.6 which replace D10 from spi NSS pin 1.3 to pin 1.4, 
  // because spi needs pin 1.3 for correct work

  // replace config from 1.3 to 1.4 
  uint8_t config = PIN_GET_PAD_CONFIG(PORT_1_CFG, PIN_MASK_TO_PIN_NUMBER(GPIO_PIN_3));
  if (config == 0)    // common gpio
  {
    // get info from pin gpio1.3 and set config to gpio1.4
    HAL_GPIO_PinConfig(GPIO_1, GPIO_PIN_4, HAL_GPIO_GetPinDirection(GPIO_1, GPIO_PIN_3), 
                      (HAL_GPIO_PullTypeDef)PIN_GET_PAD_CONFIG(PORT_1_PUPD, PIN_MASK_TO_PIN_NUMBER(GPIO_PIN_3)), 
                      HAL_GPIO_DS_2MA);
    HAL_GPIO_WritePin(GPIO_1, GPIO_PIN_4, (GPIO_PinState)GPIO_GET_PIN_STATE(GPIO_1, PIN_MASK_TO_PIN_NUMBER(GPIO_PIN_3)));
    
    // pin D10 was switched to different gpio and can be used further
  }
  else if(config == 2)  // timer for pwm
  {
    // if D10 (spi NSS pin) was used as pwm, we need to stop timer, because 1.4 don't support it
    analogWriteStop(10);
    ErrorMsgHandler("analogWrite(): D10 cannot be used as PWM pin while SPI is running");
  }
  
  // switch seller to pin 1.4
  HAL_GPIO_PinConfig(GPIO_1, GPIO_PIN_6, HAL_GPIO_MODE_GPIO_OUTPUT, HAL_GPIO_PULL_NONE, HAL_GPIO_DS_2MA);
  HAL_GPIO_WritePin(GPIO_1, GPIO_PIN_6, GPIO_PIN_HIGH);
  spiNssPinIsBlocked = true; // block spi pin
}

void spi_onEnd(void)
{
  // get info from pin gpio1.4 and set config to gpio1.3
  HAL_GPIO_PinConfig(GPIO_1, GPIO_PIN_3, HAL_GPIO_GetPinDirection(GPIO_1, GPIO_PIN_4), 
                      (HAL_GPIO_PullTypeDef)PIN_GET_PAD_CONFIG(PORT_1_PUPD, PIN_MASK_TO_PIN_NUMBER(GPIO_PIN_4)), HAL_GPIO_DS_2MA);
  HAL_GPIO_WritePin(GPIO_1, GPIO_PIN_3,  (GPIO_PinState)GPIO_GET_PIN_STATE(GPIO_1, PIN_MASK_TO_PIN_NUMBER(GPIO_PIN_4)));

  // switch seller back to pin 1.3
  HAL_GPIO_WritePin(GPIO_1, GPIO_PIN_6, GPIO_PIN_LOW);
  spiNssPinIsBlocked = false; // unblock spi pin
}