#include <avr/io.h>
#include <avr/boot.h>
#include <avr/power.h>

#include "avrtime.h"
#include "avrutil.h"

union lwb time_union;

uint32_t time_sysosc = 0;
uint8_t  time_syspsc = 0;
//uint8_t  time_tnum = 1;
uint8_t  time_tpsc = 0;

uint32_t time_fcpu(void) {
  uint8_t cksel = boot_lock_fuse_bits_get( GET_LOW_FUSE_BITS ) & 0x0f; // Select Clock Source

#ifdef CLKPR
  time_syspsc = clock_prescale_get();
#else
  time_syspsc = 0xff;
#endif

  if (time_syspsc > 0x08) return -1; // reserved values, should not happen

  if (cksel == 0x03) {
    time_sysosc = 128000;
  } else if (cksel == 0x02) {
    time_sysosc = 8000000;
  } else if (time_sysosc) {
    // already set
  } else {
    return 0;
  }

  return time_sysosc >> time_syspsc;
}

int8_t time_getpsc(uint8_t which_timer, uint8_t *mask, uint8_t *mask_sz) {
  uint8_t cs = 0; // prescaler bits
  uint8_t prescaler = -1;  // external clock, unknown freq.
  uint8_t sz = 3;
  uint8_t msk = 0;

  if (which_timer > AVRTIME_SZ) return -1;

  switch (which_timer) {
#if   defined(TCCR0A)
  case 0:
    msk = _BV(CS00) | _BV(CS01) | _BV(CS02);
    cs = TCCR0B & msk;
    break;
#endif
#if   defined(TCCR1A)
  case 1:
#ifdef CS13
    msk = _BV(CS10) | _BV(CS11) | _BV(CS12) | _BV(CS13);
    cs = TCCR1B & msk;
    sz = 4;
#else
    msk = _BV(CS10) | _BV(CS11) | _BV(CS12);
    cs = TCCR1B & msk;
#endif
    break;
#endif
#if   defined(TCCR2A)
  case 2:
    msk =_BV(CS20) | _BV(CS21) | _BV(CS22);
    cs = TCCR2B & msk;
    break;
#endif
#if   defined(TCCR3A)
  case 3:
    msk = _BV(CS30) | _BV(CS31) | _BV(CS32);
    cs = TCCR3B & msk;
    break;
#endif
#if   defined(TCCR4A)
  case 4:
#ifdef CS43
    msk = _BV(CS40) | _BV(CS41) | _BV(CS42) | _BV(CS43);
    cs = TCCR4B & msk;
    sz = 4;
#else
    msk = _BV(CS40) | _BV(CS41) | _BV(CS42);
    cs = TCCR4B & msk;
#endif
    break;
#endif
#if   defined(TCCR5A)
  case 5:
    msk = _BV(CS52) | _BV(CS51) | _BV(CS50);
    cs = TCCR5B & msk;
    break;
#endif
  }

  if (cs == 0) {
    // timer is stopped
  } else {
    cs--;
    // TODO: valid for tiny861 and mega168, others check
#if defined(CS13) || defined(CS43)
    if (sz == 4) { // my guess that this is a 10bit timer
      prescaler = cs;
    } else
#endif
    {
      uint8_t cstab[] = { 0, 3, 6, 8, 10 };
      if (cs < sizeof(cstab)) prescaler = cstab[cs];
    }
  }

  if (mask) *mask = msk;
  if (mask_sz) *mask_sz = sz;
  return prescaler;
}

void time_init(clock_div_t sysclk_ps, uint8_t timer_ps) {
  uint8_t mask;
  uint8_t sz;

  // setup system prescaler (if available)
#ifdef CLKPR
  if (sysclk_ps > 8) sysclk_ps = 8; // silently bound the value
  clock_prescale_set(sysclk_ps);
#else
  (void) sysclk_ps;
#endif
  (void) time_fcpu(); // update time_sysosc and time_syspsc

  (void) time_getpsc(AVRTIME_TIMER, &mask, &sz);
  timer_ps &= mask; // silently bound the value

#if AVRTIME_TIMER == 0
  power_timer0_enable();
  TCCR0B = timer_ps;
#endif
#if AVRTIME_TIMER == 1
  power_timer1_enable();
  TCCR1B = timer_ps;
#endif
#if AVRTIME_TIMER == 2
  power_timer2_enable();
  TCCR2B = timer_ps;
#endif
#if AVRTIME_TIMER == 3
  power_timer3_enable();
  TCCR3B = timer_ps;
#endif
#if AVRTIME_TIMER == 4
  power_timer4_enable();
  TCCR4B = timer_ps;
#endif
#if AVRTIME_TIMER == 5
  power_timer5_enable();
  TCCR5B = timer_ps;
#endif

  AVRTIME_INIT;

  time_tpsc = time_getpsc(AVRTIME_TIMER, NULL, NULL);
  time_reset();
}

uint32_t time_clk2us(uint32_t cnt) {
  uint64_t tt = 1000000UL;
  tt *= cnt;
  tt <<= time_syspsc + time_tpsc;
  tt /= time_sysosc;
  return (uint32_t) tt;
}

uint32_t time_us2clk(uint32_t us) {
  uint64_t tt = us;
  tt *= time_sysosc;
  tt /= 1000000;
  tt >>= time_syspsc + time_tpsc;
  return (uint32_t) tt;
}

// ****************************** time_reset() and time_get() for different timers

void time_reset(void) {
  AVRTIME_RESET; // we could use use TSM bit for sync, but who cares?
  // we can't use TCNT0 = 0 for all mcu's, e.g. t861 low/high byte are not next to each other
  TCNT_H = 0x00;
  TCNT_L = 0x00;
  AVRTIME_TIFR = 0xff; // clear all flags
  time_current = 0L;
}

void time_get(void) {
  uint8_t time_tifr;

  time_union.t8[0] = TCNT_L;  // A
  time_union.t8[1] = TCNT_H;

  time_tifr = AVRTIME_TIFR; // B
  if (time_tifr & _BV(TOV_)) {
    AVRTIME_TIFR = time_tifr | _BV(TOV_);
    // reread timer, since the TOV_ could be set between point A and B
    time_union.t8[0] = TCNT_L;
    time_union.t8[1] = TCNT_H;
    ++time_high;
  }  
}
