#include <libopencm3/stm32/timer.h>
#include <libopencm3/stm32/f1/nvic.h>
#include <libopencm3/stm32/exti.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libLHP_UART.h>
#include <libLHP_D1W.h>

#define MAX_NUM_D1W 3

typedef struct {
  uint32_t timer;
  uint32_t gpio_port;
  uint32_t exti;
  uint32_t exti_irq;  
  uint8_t  gpio_num;
  volatile uint8_t  timer_running;
  volatile uint8_t  exti_fired;
  volatile uint8_t  ignore_initial_ev;
  
} D1W_mgt_struct;

#define MAX_DEVICES 8

uint64_t devices_ids [MAX_DEVICES];
uint8_t  devices_know[MAX_DEVICES];

uint8_t D1W_inited = 0;

//0x88000005599bd528

D1W_mgt_struct D1W_mgt[MAX_NUM_D1W];

//https://www.maximintegrated.com/en/design/technical-documents/app-notes/2/27.html
static unsigned char crc8_8540_table[256] = {
    0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65,
  157,195, 33,127,252,162, 64, 30, 95,  1,227,189, 62, 96,130,220,
   35,125,159,193, 66, 28,254,160,225,191, 93,  3,128,222, 60, 98,
  190,224,  2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255,
   70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89,  7,
  219,133,103, 57,186,228,  6, 88, 25, 71,165,251,120, 38,196,154,
  101, 59,217,135,  4, 90,184,230,167,249, 27, 69,198,152,122, 36,
  248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91,  5,231,185,
  140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205,
   17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80,
  175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238,
   50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115,
  202,148,118, 40,171,245, 23, 73,  8, 86,180,234,105, 55,213,139,
   87,  9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22,
  233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168,
  116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53
};

uint8_t D1W_getcrc(uint8_t* data,uint8_t len){
  uint8_t res = 0;
  while(len--){
    res = crc8_8540_table[res ^ *data];
    data++;
  }
  return res;
}



void D1W_runfor_us(uint8_t num,uint16_t runfor){
  if(runfor==1)
    runfor=2;
  TIM_CNT(D1W_mgt[num].timer)   = runfor-1;
  TIM_ARR(D1W_mgt[num].timer)   = runfor-1;
  TIM_EGR(D1W_mgt[num].timer)  |= TIM_EGR_UG;
  TIM_DIER(D1W_mgt[num].timer) |= TIM_DIER_UIE;
  TIM_CR1(D1W_mgt[num].timer)  |= TIM_CR1_CEN;
  D1W_mgt[num].timer_running = 1;
  
  D1W_mgt[num].ignore_initial_ev = 1;
}

void D1W_blockfor_us(uint8_t num,uint16_t runfor){
  D1W_runfor_us(num,runfor);
  while(D1W_mgt[num].timer_running);
}

void D1W_blockfor_ms(uint8_t num,uint16_t runfor){
  runfor >>=1;
  while(runfor--)
    D1W_blockfor_us(num,1000);
}


void D1W_setup_timer(uint32_t tim){
  switch(tim){
  case TIM2:
    rcc_periph_clock_enable(RCC_TIM2);
    nvic_enable_irq(NVIC_TIM2_IRQ);
    rcc_periph_reset_pulse(RST_TIM2);
  case TIM3:
    rcc_periph_clock_enable(RCC_TIM3);
    nvic_enable_irq(NVIC_TIM3_IRQ);
    rcc_periph_reset_pulse(RST_TIM3);
  case TIM4:
    rcc_periph_clock_enable(RCC_TIM4);
    nvic_enable_irq(NVIC_TIM4_IRQ);
    rcc_periph_reset_pulse(RST_TIM4);
  }
  timer_set_mode(tim, TIM_CR1_CKD_CK_INT ,TIM_CR1_CMS_EDGE, TIM_CR1_DIR_DOWN);
  TIM_PSC(tim) = 71;
}


uint32_t D1W_get_gpio_port(uint8_t num){
  return D1W_mgt[num].gpio_port;
}
uint32_t D1W_get_gpio_num(uint8_t num){
  return D1W_mgt[num].gpio_num;
}


void D1W_setup(uint8_t num,uint32_t gpio_port, uint8_t gpio_num){
  if(num>=MAX_NUM_D1W){
    return;
  }
  // do not use pins > 4

  uint32_t exti;
  uint32_t exti_irq;
  
  if(D1W_inited == 0){
    for(int i=0;i<MAX_DEVICES;i++){
      devices_know[i]=0;
      devices_ids[i]=0;
    }
    D1W_mgt[0].timer = TIM2;
    D1W_mgt[1].timer = TIM3;
    D1W_mgt[2].timer = TIM4;
    D1W_inited = 1;
  }
  
  
  switch(gpio_port){
  case GPIOA:
    rcc_periph_clock_enable(RCC_GPIOA);
  case GPIOB:
    rcc_periph_clock_enable(RCC_GPIOB);
  case GPIOC:
    rcc_periph_clock_enable(RCC_GPIOC);
  case GPIOD:
    rcc_periph_clock_enable(RCC_GPIOD);
  }
  rcc_periph_clock_enable(RCC_AFIO);
  gpio_set_mode(gpio_port, GPIO_MODE_OUTPUT_2_MHZ,GPIO_CNF_OUTPUT_PUSHPULL, (1<<gpio_num));

  D1W_mgt[num].gpio_port = gpio_port;
  D1W_mgt[num].gpio_num = gpio_num;
  D1W_mgt[num].timer_running = 0;
  D1W_mgt[num].exti_fired =0;
  
 
  
  switch(gpio_num){
  case 0:
    exti = EXTI0;
    exti_irq = NVIC_EXTI0_IRQ;
    break;
  case 1:
    exti=EXTI1;
    exti_irq = NVIC_EXTI1_IRQ;
    break;
  case 2:
    exti=EXTI2;
    exti_irq = NVIC_EXTI2_IRQ;
    break;
  case 3:
    exti=EXTI3;
    exti_irq = NVIC_EXTI3_IRQ;
    break;
  case 4:
    exti=EXTI4;
    exti_irq = NVIC_EXTI4_IRQ;
    break;

  }
  D1W_mgt[num].exti = exti;
  nvic_enable_irq(exti_irq);
  //  nvic_enable_irq(NVIC_EXTI0_IRQ);
  exti_select_source(exti, gpio_port);
  exti_set_trigger(exti, EXTI_TRIGGER_FALLING);

  D1W_setup_timer(D1W_mgt[num].timer);
}


uint8_t d1w_read_bit(uint8_t num){
  /*
    GPIOC_ODR ^= (1<<13);
    GPIOC_ODR ^= (1<<13);
    GPIOC_ODR ^= (1<<13);
    GPIOC_ODR ^= (1<<13);
    GPIOC_ODR ^= (1<<13);
    GPIOC_ODR ^= (1<<13);
  */
  uint8_t ret;
  gpio_set_mode(
		D1W_mgt[num].gpio_port,
		GPIO_MODE_OUTPUT_2_MHZ,GPIO_CNF_OUTPUT_PUSHPULL,
		(1<<D1W_mgt[num].gpio_num)
		);
  
  GPIO_ODR(D1W_mgt[num].gpio_port) &= ~(1<<D1W_mgt[num].gpio_num);
  // block of at least 1us
  D1W_blockfor_us(num,1);
  /* GPIOC_ODR ^= (1<<13);
  GPIOC_ODR ^= (1<<13);
  GPIOC_ODR ^= (1<<13);
  GPIOC_ODR ^= (1<<13);*/
  gpio_set_mode(
		D1W_mgt[num].gpio_port,
		GPIO_MODE_INPUT,
		GPIO_CNF_INPUT_FLOAT,
		(1<<D1W_mgt[num].gpio_num)
		);

  D1W_blockfor_us(num,5);
  /*
  GPIOC_ODR ^= (1<<13);
  GPIOC_ODR ^= (1<<13);
  */
  ret =  (GPIO_IDR(D1W_mgt[num].gpio_port) & (1<< D1W_mgt[num].gpio_num)) >> D1W_mgt[num].gpio_num ;
  if(ret==0)
    D1W_blockfor_us(num,25);
  D1W_blockfor_us(num,1);
  // return the read value
  return ret;

}

uint8_t D1W_read2(uint8_t num){
  uint8_t i = 2;
  uint8_t ret = 0;
  while(i--){
    ret>>=1;
    ret|= ((uint16_t)d1w_read_bit(num))<<7;
  }
  return ret>>6;
}

uint8_t D1W_read8(uint8_t num){
  uint8_t i = 8;
  uint8_t ret = 0;
  while(i--){
    ret>>=1;
    ret|= ((uint16_t)d1w_read_bit(num))<<7;
  }
  return ret;
}
uint16_t D1W_read16(uint8_t num){
  uint8_t i = 16;
  uint16_t ret = 0;
  while(i--){
    ret>>=1;
    ret|= ((uint16_t)d1w_read_bit(num))<<15;
  }
  return ret;
}
uint32_t D1W_read32(uint8_t num){
  uint8_t i = 32;
  uint32_t ret = 0;
  while(i--){
    ret>>=1;
    ret|= ((uint32_t)d1w_read_bit(num))<<31;
  }
  return ret;
}
uint64_t D1W_read64(uint8_t num){
  uint8_t i = 64;
  uint64_t ret = 0;
  while(i--){
    ret>>=1;    
    ret|= ((uint64_t)d1w_read_bit(num))<<63;
  }
  return ret;
}


void d1w_write_bit(uint8_t num,uint8_t bit){

    gpio_set_mode(
		D1W_mgt[num].gpio_port,
		GPIO_MODE_OUTPUT_2_MHZ,GPIO_CNF_OUTPUT_PUSHPULL,
		(1<<D1W_mgt[num].gpio_num)
		);
    GPIO_ODR(D1W_mgt[num].gpio_port) &= ~(1<<D1W_mgt[num].gpio_num);
    if(! bit){
      D1W_blockfor_us(num,60);
      gpio_set_mode(
		    D1W_mgt[num].gpio_port,
		    GPIO_MODE_INPUT,
		    GPIO_CNF_INPUT_FLOAT,
		    (1<<D1W_mgt[num].gpio_num)
		    );
    }else{
      D1W_blockfor_us(num,1);
      gpio_set_mode(
		    D1W_mgt[num].gpio_port,
		    GPIO_MODE_INPUT,
		    GPIO_CNF_INPUT_FLOAT,
		    (1<<D1W_mgt[num].gpio_num)
		    );
      D1W_blockfor_us(num,59);
    }
    D1W_blockfor_us(num,1);

}

void d1w_write(uint8_t num,uint8_t bitnum,uint64_t data){
  while(bitnum){
    d1w_write_bit(num,data&1);
    data>>=1;
    bitnum--;
  }
}
void D1W_write8(uint8_t num,uint8_t data){
  d1w_write( num,8,data);
}
void D1W_write16(uint8_t num,uint16_t data){
  d1w_write( num,16,data);
}
void D1W_write32(uint8_t num,uint32_t data){
  d1w_write( num,32,data);
}
void D1W_write64(uint8_t num,uint64_t data){
  d1w_write( num,64,data);
}

uint8_t D1W_detect_presence(uint8_t num){
  D1W_mgt[num].exti_fired=0;
  gpio_set_mode(
		D1W_mgt[num].gpio_port,
		GPIO_MODE_OUTPUT_2_MHZ,GPIO_CNF_OUTPUT_PUSHPULL,
		(1<<D1W_mgt[num].gpio_num)
		);
  GPIO_ODR(D1W_mgt[num].gpio_port) &= ~(1<<D1W_mgt[num].gpio_num);
  D1W_blockfor_us(num,480);
  
  exti_enable_request(D1W_mgt[num].exti);
  
  gpio_set_mode(
		D1W_mgt[num].gpio_port,
		GPIO_MODE_INPUT,
		GPIO_CNF_INPUT_FLOAT,
		(1<<D1W_mgt[num].gpio_num)
		);

  D1W_blockfor_us(num,480);
  exti_disable_request(D1W_mgt[num].exti);
  return( D1W_mgt[num].exti_fired);
}

void d1w_new_rom(uint64_t rom){
  int i=0;
  while((devices_ids[i] != rom) && (i<MAX_DEVICES) && (devices_know[i] != 0) ){
      i++;
  }
  if(devices_ids[i] == rom)
    // already known
    return;
  if(i==MAX_DEVICES){
    // error, no more space
    return;
  }
  devices_know[i] = 1;
  devices_ids[i] = rom;
  

}

void D1W_read_rom(uint8_t num,uint64_t * rom){
  int i=0;
  D1W_write8(0,D1W_READ_ROM);
  *rom=D1W_read64(0);
  d1w_new_rom(*rom);
  
}

uint8_t D1W_search_rom(uint8_t num){
  // this is a bit complicated, it searches for unknown 1W roms
  // can be used to explore a bus
  uint64_t rom = 0;
  uint64_t rom_mask_conflict = 0;
  uint64_t msk = 0;
  uint16_t i = 0;
  uint8_t n=0;
  
  uint8_t r;

  do {
    D1W_detect_presence(0);
    D1W_write8(num,D1W_SEARCH_ROM);
    rom = 0;
    for(i=0;i<64;i++){

      r = D1W_read2(num);
      switch(r){
      case 0:
	/*
	printstr_blocking(USART1,"conflict");
	print_uint(USART1,i);
	printstr_rn(USART1);
	*/
	
	// conflict
	if(rom_mask_conflict &  (1ULL<<i)){
	  // we know this conflict and visited 0
	  rom |= (1ULL<<i);
	  msk = (~(0xffffffffffffffffULL>>(63-i)));
	  if( (rom_mask_conflict & msk) == 0){
	    // we are the last conflict flag left (a leaf in the binary tree walking)
	    rom_mask_conflict &= ~(1ULL<<i); // clear the flag 
	  }
	  d1w_write_bit(num,1);   
	}else{
	  // we don't know this conflict,  visit 0
	  d1w_write_bit(num,0);
	  rom_mask_conflict |=  (1ULL<<i);
	}
	break;
      case 1:
	// all 1, select 1
	d1w_write_bit(num,1);
	rom            |= (1ULL<<i);      
	break;
      case 2:
	// all 0, select 0
	d1w_write_bit(num,0);
	break;
      }
    }
    d1w_new_rom(rom);
    n++;
  }while(rom_mask_conflict);
  return n;
}

uint8_t D1W_matchrom(uint8_t num,uint64_t rom){
  D1W_write8(num,D1W_MATCH_ROM);
  D1W_write64(num,rom);
  return 1;
}

uint8_t D1W_matchrom_id(uint8_t num,uint8_t id){
  uint64_t rom = D1W_get_rom(id);
  if(rom)
    return D1W_matchrom(num,rom);
  else
    return 0;
}

uint64_t D1W_get_rom(uint8_t id){
  if(id > MAX_DEVICES){
    return 0;
  }
  else
    {
      if(devices_know[id])
	return  devices_ids[id];
      else
	return 0;	
    }
}

#ifdef USING_D1W

void tim2_isr(){
  if(  TIM2_SR & TIM_SR_UIF){
    // update, the downcounter hit 0
    // clear the interrupt
    TIM2_SR &= ~TIM_SR_UIF;
    // this is the initialsation event, gtfo    
    if(D1W_mgt[0].ignore_initial_ev == 1){
      D1W_mgt[0].ignore_initial_ev = 0;
      return;
    }
    // shut down the timer
    TIM2_CR1  &= ~TIM_CR1_CEN;
    D1W_mgt[0].timer_running=0;
  }
}
void tim3_isr(){
  if(  TIM3_SR & TIM_SR_UIF){
    // update, the downcounter hit 0
    // clear the interrupt
    TIM3_SR &= ~TIM_SR_UIF;
    // this is the initialsation event, gtfo    
    if(D1W_mgt[1].ignore_initial_ev == 1){
      D1W_mgt[1].ignore_initial_ev = 0;
      return;
    }
    // shut down the timer
    TIM3_CR1  &= ~TIM_CR1_CEN;
    D1W_mgt[1].timer_running=0;
  }
}
void tim4_isr(){
  if(  TIM4_SR & TIM_SR_UIF){
    // update, the downcounter hit 0
    // clear the interrupt
    TIM4_SR &= ~TIM_SR_UIF;
    // this is the initialsation event, gtfo    
    if(D1W_mgt[2].ignore_initial_ev == 1){
      D1W_mgt[2].ignore_initial_ev = 0;
      return;
    }
    // shut down the timer
    TIM4_CR1  &= ~TIM_CR1_CEN;
    D1W_mgt[2].timer_running=0;
  }
}




void exti0_isr(void)
{
  exti_reset_request(EXTI0);
 GPIOC_ODR ^= (1<<13);
 GPIOC_ODR ^= (1<<13);
  uint8_t i = 0;
  while(i < MAX_NUM_D1W){
    if(D1W_mgt[i].gpio_num == 0){
      D1W_mgt[i].exti_fired = 1;
      exti_disable_request(EXTI0 );
      return;
    }
    i++;
  }
}
void exti1_isr(void)
{
  exti_reset_request(EXTI1);

  uint8_t i = 0;
  while(i < MAX_NUM_D1W){
    if(D1W_mgt[i].gpio_num == 1){ 
      D1W_mgt[i].exti_fired = 1;
      exti_disable_request(EXTI1 );
      return;
    }
    i++;
  }
}
void exti2_isr(void)
{
  exti_reset_request(EXTI2);
  uint8_t i = 0;
  while(i < MAX_NUM_D1W){
    if(D1W_mgt[i].gpio_num == 2){
      D1W_mgt[i].exti_fired = 1;
      exti_disable_request(EXTI2 );
      return;
    }
    i++;
  }
}
void exti3_isr(void)
{
  exti_reset_request(EXTI3);
  uint8_t i = 0;
  while(i < MAX_NUM_D1W){
    if(D1W_mgt[i].gpio_num == 3){

      D1W_mgt[i].exti_fired = 1;
      exti_disable_request(EXTI3 );
      return;
    }
    i++;
  }
}
void exti4_isr(void)
{
  exti_reset_request(EXTI4);
  uint8_t i = 0;
  while(i < MAX_NUM_D1W){
    if(D1W_mgt[i].gpio_num == 4){

      D1W_mgt[i].exti_fired = 1;
      exti_disable_request(EXTI4 );
      return;
    }
    i++;
  }
}

#endif
