/*
Copyright 2003/2004, Freie Universitaet Berlin. All rights reserved.

These sources were developed at the Freie Universität Berlin, Computer
Systems and Telematics group.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
 
- Neither the name of Freie Universitaet Berlin (FUB) nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

This software is provided by FUB and the contributors on an "as is"
basis, without any representations or warranties of any kind, express
or implied including, but not limited to, representations or
warranties of non-infringement, merchantability or fitness for a
particular purpose. In no event shall FUB or contributors be liable
for any direct, indirect, incidental, special, exemplary, or
consequential damages (including, but not limited to, procurement of
substitute goods or services; loss of use, data, or profits; or
business interruption) however caused and on any theory of liability,
whether in contract, strict liability, or tort (including negligence
or otherwise) arising in any way out of the use of this software, even
if advised of the possibility of such damage.

This implementation was developed by the CST group at the FUB.
Contributors: Bjoern Lichtblau

For documentation and questions please use the web site
http://scatterweb.mi.fu-berlin.de and the mailinglist
scatterweb@lists.spline.inf.fu-berlin.de (subscription via the Website).
Berlin, 2003/2004/2005
*/

#include "ScatterWeb.System.h"

volatile UINT16 _ticks; ///< Currently accumulated ticks which are not yet subtracted from the timers entries. Increased by Timer_A0 Interrupt and decreased by _consolidate.
UINT16 numberTimers;    ///< Current number of entires in ::timers, ::timers[0] .. ::timers[::numberTimers-1] are valid entries.

typedef struct {
  UINT16 ticks;   ///< ::timers[i].ticks - ::_ticks is the number of ticks until timer fires.
  fp_timer fp;          ///< Function pointer to call when elapsed.  
  UINT16 data;    ///< User data which is passed to the function fp.
} timerEntry;
timerEntry timers[MAX_TIMERS]; ///< Table of timer entries.

COMMAND(tmr, SERIALONLY){
  UINT16 i;
  Comm_print("_ticks: %i t[0].ticks: %i\r\n", _ticks, timers[0].ticks);
  for(i=0; i<numberTimers; i++) Comm_print("%i: FP:%x T:%i D:%x\r\n", i, timers[i].fp, timers[i].ticks, timers[i].data);
}

void _consolidate(){
  UINT16 i;
  // Good and safe way to synchronize with the interrupt without disabling it:
  // First read ticks and then subtract it. Both operations are atomic, if
  // they get interrupted in between no tick will get lost, it will stay
  // in ticks due to the subtract.
  UINT16 ticks = _ticks;
  _ticks -= ticks;
  // Now subtract the ticks from all timers, timers with ticks=0 are expired.
  for(i=0; i<numberTimers; i++){
    if(timers[i].ticks > ticks) timers[i].ticks -= ticks;
    else                        timers[i].ticks = 0; // expired
  }
}

interrupt(TIMERA0_VECTOR) Timer_A0 () {
  extern volatile time_t currentTime; // OK, access is exlusive.
  TACCR0 += 4; // Fire timer again in a 1024/second.
  // Software Timers.
  if(++_ticks > timers[0].ticks) WAKEUPLPM1(Timer_A0, MF_TIMER);
  // System time.
  if(++currentTime.millis>=1024){
    currentTime.millis=0;
    currentTime.secs++;
  }
}

bool Timers_init(){
  UINT16 i;
  // DCO Init
  System_setDCO();
  // Timer Software Init
  for(i=0; i<MAX_TIMERS; i++){ timers[i].ticks = 0; timers[i].fp = NULL; timers[i].data = 0; }
  timers[0].ticks = 0xFFFF;
  numberTimers = 0;
  ////////////////////////////////// Init TIMERA to run continously from the divided ACLK (4096 Hz) and setup TA0 for system ticks.
  TACTL = TASSEL0 + TACLR ;       // Clr timer, select ACLK.
  TACCTL0 = CCIE;                 // Compare mode: enable interrupt.
  TACCR0 = 4;                     // Interrupt every 4 ticks @4096 Hz => 1024 Hz.
  TACTL |= MC1;                   // Start timer in continous mode.
  ////////////////////////////////// Init TIMERB to run continously from DCO at 4,505606 Hz and setup TB6 for DCO checking.
  TBCTL = TBSSEL1 + TBCLR;        // Clr timer, select SMCLK-
  TBCTL |= MC1;                   // Start Timer B in continuous mode
  if(Configuration.firmwareFlags & FF_DCOCHECKER){
    extern unsigned int dcoCap;
    extern void checkDCO();
    TBCCTL6 = CCIS_1 + CM_2 + CAP;    // Set TB6 to capture CCIB (which is ACLK on TB6) on one edge, clears CCIFG
    //////////////////////////////////// Await the first cap to init dcoCap.
    while(!(TBCCTL6 & CCIFG)) _NOP(); // Wait polling for first capture.
    dcoCap = TBCCR6;                  // Save first capture like in isr.
    TBCCTL6 &= ~(CCIFG);              // Clear ifg like in isr:
    TBCCTL6 |= CCIE;                  // Enable tb6 interrupt.
    Timers_add(100, checkDCO, 0xFFFF);
  } else {
    TBCCTL6 = 0x0000;
  }
  return True;
}

bool Timers_eventHandler(){
  UINT16 i = 0;
  // Consolidate timer list: subtract timed ticks
  _consolidate();
  while(i<numberTimers){ // Call and remove all expired timers
    if(timers[i].ticks==0){
      // todo maybe better
      fp_timer fp = timers[i].fp;
      UINT16 data = timers[i].data;
      timers[i] = timers[numberTimers-1];
      numberTimers--;
      fp(data);   
    }
    else i++;
  }
  // If timers empty set ticks to 0xFFFF so callTimer will never be set.
  if(numberTimers==0) timers[0].ticks = 0xFFFF;
  // else if more than one find the new min
  else if(numberTimers>1){
    UINT16 min = 0;
    for(i=1; i<numberTimers; i++) if(timers[i].ticks < timers[min].ticks) min = i;
    if(min != 0){ // If min is other than 0 swap them
      timerEntry te = timers[0];
      timers[0] = timers[min];
      timers[min] = te; 
    }
  }
  // If no timer expired until now reset callTimer, else let it stay.
  if(_ticks <= timers[0].ticks) runModule &= ~MF_TIMER;
  return True;
}

bool Timers_add(UINT16 t, fp_timer fp, UINT16 data){
  // Timers empty, first entry.
  if(numberTimers==0){
    _ticks = 0; // Reset ticks.
    timers[0].ticks = t;
    timers[0].fp = fp;
    timers[0].data = data;
    numberTimers++;
    return True;
  }
  // Timers running, add another one.
  else if(numberTimers<MAX_TIMERS){ 
    UINT16 incticks = t + _ticks; // If this timer is just added in it has to get this value, check it for overflow.
    // Can the timer be added by adding its ticks with _ticks? Then no consolidation is needed.
    if(incticks<t){ // Overflow, we will first have to consolidate the list by subtracting _ticks.
      _consolidate();
      incticks = t + _ticks; // If really a tick occured just in between these few lines this tick will be accounted here. _ticks should be never more than 1 !   
    }
    if(incticks < timers[0].ticks){ // New entry is the smallest, put entry 0 away and save this there.
      timers[numberTimers] = timers[0];
      timers[0].ticks = incticks;
      timers[0].fp = fp;
      timers[0].data = data;
    }
    else { // Entry 0 is still the smallest, save this on new place.
      timers[numberTimers].ticks = incticks;
      timers[numberTimers].fp = fp;
      timers[numberTimers].data = data;
    }
    numberTimers++;
    //Comm_log(DEBUG, "timer added: t: %i adr: %x\r\n", incticks, fp);
    return True;
  }
  // No place found to add => timers full  
  else {
    if(Configuration.panicFlags & PANIC_ON_FULL_TIMERS) System_panic("Timers FULL(%x)", fp);
    else Comm_log(LOW, "timers full!! not added adr: %x\r\npos ticks fp\r\n", fp);
  }
  return False;
}

bool Timers_reset(UINT16 t, fp_timer fp, UINT16 data) {
  UINT16 i, j;
  for(j=0; j<numberTimers; j++){
    if((timers[j].fp==fp) && (timers[j].data==data)){
      UINT16 incticks = t + _ticks; // If without consolidation it has to get this value, check it for overflow.
      // Can the timer be added by adding its ticks with _ticks? Then no consolidation is needed.
      if(incticks<t){ // Overflow, we will first have to consolidate the list by subtracting _ticks.
        _consolidate();
        incticks = t + _ticks; // If really a tick occured just in between these few lines this tick will be accounted here. _ticks should be never more than 1 !   
      }
      timers[j].ticks = incticks;
      if(j==0 && numberTimers > 1){ // if it was the first one and more than 1 timer remain search new min and set it
        UINT16 min = 0;
        for(i=1; i<numberTimers; i++) if(timers[i].ticks < timers[min].ticks) min = i;
        if(min != 0){ // If min is other than 0 swap them
          timerEntry te = timers[0];
          timers[0] = timers[min];
          timers[min] = te; 
        }        
      }
      else if(timers[j].ticks < timers[0].ticks){ // if not the first and smaller swap them
        timerEntry te = timers[0];
        timers[0] = timers[j];
        timers[j] = te;       
      }
      return True;
    }
  }
  return False;
}

bool Timers_remove(fp_timer fp, UINT16 data){
  UINT16 i, j;
  for(j=0; j<numberTimers; j++){
    if((timers[j].fp==fp) && (timers[j].data==data /*|| data==0xFFFF*/)){
      // remove it
      timers[j] = timers[numberTimers-1];
      numberTimers--;
      if(j==0 && numberTimers > 1){ // if it was the first one and more than 1 timer remain search new min and set it
        UINT16 min = 0;
        for(i=1; i<numberTimers; i++) if(timers[i].ticks < timers[min].ticks) min = i;
        if(min != 0){ // If min is other than 0 swap them
          timerEntry te = timers[0];
          timers[0] = timers[min];
          timers[min] = te; 
        }        
      }
      return True;
    }
  }
  return False;
}
