#include "communication.h"
#include "uart.h"

struct packet_t
{
  unsigned int index;
  unsigned int length;
  unsigned int position;
};
struct communication_t
{
  unsigned char uc_tx_ring_buf[TX_BUF_LEN];
  unsigned char uc_rx_ring_buf[RX_BUF_LEN];
  unsigned char uc_tx_packet_count;
  unsigned char uc_rx_packet_count;
  struct packet_t tx_packet[TX_PACKET_BUF_LEN];
  struct packet_t rx_packet[RX_PACKET_BUF_LEN];
  int i_rx_next_packet;		 //indice del consumidor (la funcion receive) para el buffer circular de paquetes
  int i_rx_new_packet;		 //indice del productor (la interrupcion) para el buffer circular de paquetes
  int i_tx_new_packet;		 //indice del productor de transmision
  int i_tx_next_packet;		 //indice del consumidor de transmision (interrup) para el buffer circular de paquetes
  unsigned char e_error_status;
  unsigned char uc_status;
  unsigned char THR_empty;
};

static struct communication_t com;

extern void irq_handler(void);

void com_init(void)
{
  com.uc_tx_packet_count=0;
  com.uc_rx_packet_count=0;
  com.i_rx_next_packet=0;
  com.i_rx_new_packet=0;
  com.i_tx_new_packet=0;
  com.i_tx_next_packet=0;
  com.THR_empty= TRUE;

  // setup the UART
  // setup Pin Function Select Register (Pin Connect Block) 
  // make sure old values of Bits 0-4 are masked out and
  // set them according to UART0-Pin-Selection
  
  unsigned char temp = UART_BAUD(38400);

  PCB_PINSEL0 = (PCB_PINSEL0 & ~UART0_PINMASK) | UART0_PINSEL;

  UART0_IER = 0x00;   // disable all interrupts
  UART0_IIR;          // clear interrupt ID register
  UART0_LSR; 		  		// clear line status register
  UART0_RBR;			    // clear receive register

  // set the baudrate - DLAB must be set to access DLL/DLM
  UART0_LCR = (1<<7);         // set divisor latches (DLAB)
  UART0_DLL = temp;           // set for baud low byte
  UART0_DLM = (temp >> 8);    // set for baud high byte

  UART0_LCR = 0x03;           // (UART_FIFO_8 & ~(1<<UART0_LCR_DLAB)); // clear DLAB "on-the-fly"
  UART0_FCR = 0xc7;           // 14 bytes de fifo, resetea ambas y las habilita.

  VICVectCntl4 = 6;           // interrupcion correspondiente a la Uart 0
  VICVectCntl4 |= 0x20;       // Habilita la fuente de interrupcion asiginada para el slot 4
  VICVectAddr4 = (unsigned long)irq_handler; // Carga el puntero a la ISR en el VIC
  VICIntEnable |= (1<<6);     // Habilito la INTERRUPCION

  UART0_IER = 0x1;            // Enable Receive Data Available Interrupt, transmit buffer empty interrupt
}

int send_packet(char* buf, int count)
{
  int i_input_buf = 0;
  int aux;

  if(count ==0)
  {
    return 0;
  }

  com.uc_tx_ring_buf[com.tx_packet[com.i_tx_new_packet].index] = INIT_FRAME;// Pone inicio de trama en el
                                                                            // buffer de char.

  if(++com.tx_packet[com.i_tx_new_packet].position >= TX_BUF_LEN)           // Incrementa el indice de apuntador
  {                                                                         // del buffer de chars.
    com.tx_packet[com.i_tx_new_packet].position = 0;						  		      // Chequeando retorno del buffer
  }                                                                         // circular de chars.

  for(i_input_buf=0;i_input_buf < count; i_input_buf++)
  {
    com.uc_tx_ring_buf[com.tx_packet[com.i_tx_new_packet].position] = buf[i_input_buf];
  
    if(++com.tx_packet[com.i_tx_new_packet].position >= TX_BUF_LEN)         // Incrementa el indice de apuntador
                                                                            // del buffer de chars.
    {          
      com.tx_packet[com.i_tx_new_packet].position = 0;								      // chequeando retorno del buffer
                                                                            // circular de chars
    }
  }

  com.uc_tx_ring_buf[com.tx_packet[com.i_tx_new_packet].position] = END_FRAME;// pone final de trama en el buffer de char

  if(++com.tx_packet[com.i_tx_new_packet].position >= TX_BUF_LEN)             // incrementa el indice de apuntador del buffer de chars
  {
    com.tx_packet[com.i_tx_new_packet].position = 0;                          //chequeando retorno del buffer circular de chars
  }

  com.tx_packet[com.i_tx_new_packet].length = i_input_buf + 2;
  aux =com.tx_packet[com.i_tx_new_packet].position;                           // copia indice del buf de char para la siguiente entrada 
                                                                              // vacia, es el index del siguiente paquete
  if(com.THR_empty == TRUE)         //aqui va el famoso "prime the pump"
  {
    UART0_THR = com.uc_tx_ring_buf[com.tx_packet[com.i_tx_new_packet].index];
    com.tx_packet[com.i_tx_new_packet].length--;
    
    com.THR_empty = FALSE;
    com.tx_packet[com.i_tx_new_packet].position =com.tx_packet[com.i_tx_new_packet].index+1;  // resetea punt de buf de chars para este
                                                                                              // paquete, el 1er byte ya fue enviado
  }
  else
  {
    com.tx_packet[com.i_tx_new_packet].position =com.tx_packet[com.i_tx_new_packet].index;    // resetea punt de buf de chars para 
  }                                                                                           // este paquete

  if(++com.i_tx_new_packet >= TX_PACKET_BUF_LEN)       //Incrementar indice apuntador de paquetes para la prox. llamada a esta funcion
  {
    com.i_tx_new_packet = 0;                            //chequeando retorno buffer circular de paquetes
  }
  if(com.i_tx_new_packet == com.i_tx_next_packet)
  {
    if(++com.i_tx_next_packet >= TX_PACKET_BUF_LEN)
    {
      com.i_tx_next_packet = 0;
    }
    if(++com.i_tx_next_packet >= TX_PACKET_BUF_LEN)
    {
      com.i_tx_next_packet = 0;
    }

    com.uc_tx_packet_count-=2;
  }
  com.tx_packet[com.i_tx_new_packet].index = aux;       // Ajusta apuntador del nuevo paquete pa la prox. llamada al sig. 
  com.tx_packet[com.i_tx_new_packet].position = aux;    //char vacio en buf de chars

  //en seccion critica
  com.uc_tx_packet_count++;	
  //fin seccion critica

  UART0_IER |= 0x2; // Se habilita la transmision recien cuando se quiere enviar el 1er dato.
  return i_input_buf - 1;   //retorna la cantidad de bytes escritos
}

int receive_packet(char *buf)
{
  unsigned int c=0;
  unsigned int i_return_buf =0;
  unsigned int i_ring_buf=0;
  
  if(com.uc_rx_packet_count > 0)
  {
    i_ring_buf =com.rx_packet[com.i_rx_next_packet].index;
  
    while(c <com.rx_packet[com.i_rx_next_packet].length)
    {

      buf[i_return_buf] = com.uc_rx_ring_buf[i_ring_buf];

      if(++i_ring_buf >= RX_BUF_LEN)
      {
        i_ring_buf = 0;
      }

      c++;						      //incrementa el indice del buffer de caracteres
      i_return_buf++;       //incrementa el indice del buffer de retorno
    }
    
    //colocar entrada a seccion critica
    com.uc_rx_packet_count--;     //ojo con la interrupcion
    //colocar salida de seccion critica
    
    if(++com.i_rx_next_packet >= RX_PACKET_BUF_LEN)
    {
      com.i_rx_next_packet = 0;
    }

    return i_return_buf;
  }

  return 0;
}

void irq_communication(void)
{
  unsigned char temp;
  int thr_count;
  unsigned char iir_temp=0;

  while (((iir_temp = UART0_IIR) & (1<<0)) == 0)
  {
    switch( iir_temp & INTERRUPT_SOURCE_MASK )
    { 
      case SOURCE_ERROR :     // Not handling this, but clear the interrupt. 
        UART0_LSR;
        break;
 
      case SOURCE_RX_TIMEOUT:
      case SOURCE_RX:          // A character was received.  Place it in the queue of received characters.
        do 
        { 
          temp = UART0_RBR;

          switch(temp)
          {
            case INIT_FRAME:
              com.rx_packet[com.i_rx_new_packet].length = 0;
              com.rx_packet[com.i_rx_new_packet].position = com.rx_packet[com.i_rx_new_packet].index;
              break;

            case END_FRAME:
              com.uc_rx_packet_count++;

              if(++com.i_rx_new_packet >= RX_PACKET_BUF_LEN)
              {
                com.i_rx_new_packet = 0;
                com.rx_packet[com.i_rx_new_packet].index = com.rx_packet[RX_PACKET_BUF_LEN-1].position;
              }
              else
              {
                com.rx_packet[com.i_rx_new_packet].index = com.rx_packet[com.i_rx_new_packet-1].position;
              }
              if(com.i_rx_new_packet == com.i_rx_next_packet)
              {
                if(++com.i_rx_next_packet >= RX_PACKET_BUF_LEN)
                {
                  com.i_rx_next_packet = 0;
                }
           /*     if(++com.i_rx_next_packet >= RX_PACKET_BUF_LEN)
                {
                  com.i_rx_next_packet = 0;
                }*/

                com.uc_rx_packet_count--;
              }
              break;

            default:
              com.uc_rx_ring_buf[com.rx_packet[com.i_rx_new_packet].position] = temp;

              if(++com.rx_packet[com.i_rx_new_packet].position >= RX_BUF_LEN) //deberia chequear el llenado del buffer circular
              {
                com.rx_packet[com.i_rx_new_packet].position = 0;
              }
              com.rx_packet[com.i_rx_new_packet].length++;
              break;
          }
        }while(UART0_LSR & 0x1);  //BIT 0 DE LSR EN UNO INDICA DATOS DISPONIBLES EN EL RBR
        break;

      case SOURCE_THRE :      // The THRE is empty. If there is another character in the Tx queue, send it now. 
        thr_count =16;

        while(thr_count)
        {
         if(com.tx_packet[com.i_tx_next_packet].length)
          {
           UART0_THR = com.uc_tx_ring_buf[com.tx_packet[com.i_tx_next_packet].position]; //copia desde el buffer
                                                                                         //circular al thr
           if(++com.tx_packet[com.i_tx_next_packet].position >= TX_BUF_LEN)
           {
             com.tx_packet[com.i_tx_next_packet].position = 0;
           }

          com.tx_packet[com.i_tx_next_packet].length--;
          thr_count--;
          }
         else   // There are no further characters queued to send so we can indicate that the THRE is available.
         {
           if(++com.i_tx_next_packet >= TX_PACKET_BUF_LEN)
           {
             com.i_tx_next_packet=0;
           }

           com.uc_tx_packet_count--;

           if(com.tx_packet[com.i_tx_next_packet].length == 0)
           {
             com.THR_empty = TRUE;
             UART0_IER &= ~(0x02);
             break;	          //rompe el bucle del thr_count
           }
         }
       }
      break;

      default: /* There is nothing to do, leave the ISR. */
      UART0_LSR;
      UART0_RBR;
      break;
    }
  }
  VICVectAddr = 0x00;   // Escritura que señala el fin de una interrupcion
}
