/******************************************************/
/* Copyright (C) 2008 Arch Rock Corporation 2008      */
/* License Agreement: refer to LICENSE.txt            */
/*                                                    */
/******************************************************/

/*!
  @file cmd.c

  @brief This file contains functions to parse the telnet commands

*/

#include <string.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <net/route.h>
#include <netinet/inet.h>
#include <iwconfig.h>
#include "cmd.h"
#include "raven.h"
#include "server.h"

//! simple macro that returns the smaller of two values
#define MIN(x,y) ((x<y)?x:y)

//! shell command helper function type
typedef int cmd_fun_t (char *msg, char *buf, int curindex, int len, int16_t sockfd);

//! function decalarations
int help_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd);
int ledOn_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd);
int ledOff_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd);
int blink_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd);
int lcd_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd);
int temp_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd);
int time_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd);
int rte_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd);
int if_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd);
int exit_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd);

//! shell command structure
typedef struct cmd_desc {
  cmd_fun_t *fun;
  char      *key;
  char      *help;
} cmd_desc_t;

//! data base of supported shell commands
cmd_desc_t cmd_table[] = {
  {help_fun,   "?",     "print cmds"},
  {ledOn_fun,  "lon",   "LED on"},
  {ledOff_fun, "loff",  "LED off"},
  {blink_fun,  "blink", "Blink N"},
  {lcd_fun,    "lcd",   "display msg"},
  {temp_fun,   "temp",  "display temp"},
  {time_fun,   "time",  "time of day"},
  {rte_fun,    "route", "info"},
  {if_fun,     "ifconfig", "IP config"},
  {exit_fun,   "exit",  "exit shell"}
};

//! exit_fun
//! 
//! @brief helper function that closes the TCP connection by returning -1
//!
//! @param msg is a pointer to the response message
//! @param buf is a pointer to the shell command received
//! @param curindex is the starting position of the shell command argument within buf
//! @param len is the total length of the shell command (command + argument)
//! @param sockfd is the socket descriptor
//!
//! @return -1: close command shell
//!
int exit_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd) {
  return -1;
}

//! temp_fun
//! 
//! @brief helper function that samples the temperature using the onboard sensor
//!
//! @param msg is a pointer to the response message
//! @param buf is a pointer to the shell command received
//! @param curindex is the starting position of the shell command argument within buf
//! @param len is the total length of the shell command (command + argument)
//! @param sockfd is the socket descriptor
//!
//! @return 0: ready for next prompt
//!
int temp_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd) {
  sample_temp();
  sprintf(msg, "done\r\n");
  return 0;
}

//! blink_fun
//! 
//! @brief helper function that sets the number of blinks in the blink interval
//!
//! @param msg is a pointer to the response message
//! @param buf is a pointer to the shell command received
//! @param curindex is the starting position of the shell command argument within buf
//! @param len is the total length of the shell command (command + argument)
//! @param sockfd is the socket descriptor
//!
//! @return 0: ready for next prompt
//!
int blink_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd) {
  int i = atoi(&buf[curindex]);
  setBlink(i);
  sprintf(msg, "blink %d\r\n",i);
  return 0;
}

//! ledOn_fun
//! 
//! @brief helper function that turns on the LED
//!
//! @param msg is a pointer to the response message
//! @param buf is a pointer to the shell command received
//! @param curindex is the starting position of the shell command argument within buf
//! @param len is the total length of the shell command (command + argument)
//! @param sockfd is the socket descriptor
//!
//! @return 0: ready for next prompt
//!
int ledOn_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd) {
  setBlink(0);
  led_on();
  sprintf(msg, "Led On\r\n");
  return 0;
}

//! ledOff_fun
//! 
//! @brief helper function that turns off the LED
//!
//! @param msg is a pointer to the response message
//! @param buf is a pointer to the shell command received
//! @param curindex is the starting position of the shell command argument within buf
//! @param len is the total length of the shell command (command + argument)
//! @param sockfd is the socket descriptor
//!
//! @return 0: ready for next prompt
//!
int ledOff_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd) {
  setBlink(0);
  sprintf(msg, "Led Off\r\n");
  return 0;
}

//! lcd_fun
//! 
//! @brief helper function that prints a message to the LCD
//!
//! @param msg is a pointer to the response message
//! @param buf is a pointer to the shell command received
//! @param curindex is the starting position of the shell command argument within buf
//! @param len is the total length of the shell command (command + argument)
//! @param sockfd is the socket descriptor
//!
//! @return 0: ready for next prompt
//!
int lcd_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd) {
  print_msg(&buf[curindex], len-curindex);
  sprintf(msg, "\r\n");
  return 0;
}

//! Network interface information from iwconfig
static net_device_info_t fs_lpan0_info;

//! file scope socket descriptor
static int16_t fs_sockfd;

//! iwconfig_status_handler
//!
//! @brief callback for iwconfig() invoked with STATUS flag
//!
//! @param event equals to IWCONFIG as this is a iwconfig() STATUS callback
//! @param cbargs passes \em dev_info back from iwconfig() STATUS call
//! @param context passes \em context back from iwconfig() STATUS call 
//!
//! @return none
//!
void iwconfig_status_handler(event_t event, void *cbargs, void * context){
  char tmp[128];
  char msg[256];
  int i;
  
  memset(msg, 0, 256);
  sprintf(msg, "lpan0 Link encap:6LoWPAN \r\nHWaddr:"); 
  for(i=0; i<8; i++){
    if (i < 7)
      sprintf(&tmp[0], "%2x:",fs_lpan0_info.device.dev_addr[i]);
    else
      sprintf(&tmp[0], "%2x",fs_lpan0_info.device.dev_addr[i]);
    strcat(msg,tmp);
  }
  
  strcat(msg, "\r\ninet6addr Global: ");
  inet_ntop(AF_INET6, &fs_lpan0_info.v6_gaddr, tmp,128);
  strcat(msg, tmp);
  
  strcat(msg, "\r\ninet6addr Local: ");
  inet_ntop(AF_INET6, &fs_lpan0_info.v6_laddr, tmp,128);
  strcat(msg, tmp);
  
  sprintf(tmp,"\r\nTX packets %d RX packets %d\r\n", 
	  fs_lpan0_info.packets_sent,
	  fs_lpan0_info.packets_recv);		
  strcat(msg, tmp);
  send(fs_sockfd, msg, strlen(msg), 0);
  prompt(fs_sockfd);
}

//! if_fun
//! 
//! @brief helper function that invokes iwconfig() with option STATUS to get the wireless interface information
//!
//! @param msg is a pointer to the response message
//! @param buf is a pointer to the shell command received
//! @param curindex is the starting position of the shell command argument within buf
//! @param len is the total length of the shell command (command + argument)
//! @param sockfd is the socket descriptor
//!
//! @return 1: waiting for callback from iwconfig_status_handler()
//!
int if_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd) {
  fs_sockfd = sockfd;
  iwconfig(LPAN0, STATUS, 0, NULL, iwconfig_status_handler, &fs_lpan0_info, NULL);
  sprintf(msg, "\r\n");
  return 1;
}


//! rte_fun
//! 
//! @brief helper function that prints the route information
//!
//! @param msg is a pointer to the response message
//! @param buf is a pointer to the shell command received
//! @param curindex is the starting position of the shell command argument within buf
//! @param len is the total length of the shell command (command + argument)
//! @param sockfd is the socket descriptor
//!
//! @return 0: ready for next prompt
//!
int rte_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd) {
  int i;
  rtentry_t rtentry;
  char tmp[32];

  for (i=0; i < num_rtentries(); i++){
    routecntl(READROUTE, i, &rtentry);
    sprintf(msg, "default via ");
    if (inet_ntop(AF_INET6, &rtentry.nxthop, &msg[strlen(msg)], 
		  128 - strlen(msg)) != NULL){
      sprintf(tmp, " metric %d hop %d cost %d", i, 
	      rtentry.hops, rtentry.cost);
      strcat(msg, tmp);
      if (i == num_rtentries() - 1){
	strcat(msg, "\r\n");
      }
      send(sockfd, msg, strlen(msg), 0);
    }
  }
  sprintf(msg,"\r\n");
  return 0;
}

//! time_fun
//! 
//! @brief helper function that prints the local time at the raven
//!
//! @param msg is a pointer to the response message
//! @param buf is a pointer to the shell command received
//! @param curindex is the starting position of the shell command argument within buf
//! @param len is the total length of the shell command (command + argument)
//! @param sockfd is the socket descriptor
//!
//! @return 0: ready for next prompt
//!
int time_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd) {
  struct timeval tv;
  int16_t error = gettimeofday (&tv);
  if (error) {
    sprintf(msg, "???\r\n");
  } else {
    sprintf(msg, "%7ld\r\n",tv.tv_sec);
  }
  return 0;
}

//! help_fun
//! 
//! @brief helper function that prints the help text at the prompt
//!
//! @param msg is a pointer to the response message
//! @param buf is a pointer to the shell command received
//! @param curindex is the starting position of the shell command argument within buf
//! @param len is the total length of the shell command (command + argument)
//! @param sockfd is the socket descriptor
//!
//! @return 0: ready for next prompt
//!
int help_fun(char *msg, char *buf, int curindex, int len, int16_t sockfd) {
  int c;
  for (c = 0; c < sizeof(cmd_table)/sizeof(cmd_desc_t); c++) {
    sprintf(msg, "%12s:\t%s\r\n",cmd_table[c].key,cmd_table[c].help);
    send(sockfd, msg, strlen(msg), 0);	
  }	
  memset(msg,0,SERIALMSGSIZE);	
  return 0;
}


//! prompt
//!
//! @brief this function sends a prompt to a TCP socket
//!
//! @param sockfd is the descriptor of the socket
//!
//! @return none
//!
void prompt(int16_t sockfd) {
  send(sockfd, "> ", 2, 0);
}


//! skip
//! 
//! @brief Skip over chars 
//! 
//! @param str is the pointer to the string to process (skip)
//! @param i is the starting position within str
//! @param e is the length of the str
//! @param skips is a pointer to the array of characters to stop on
//!
//! @return curindex of first non-skip char
//! @return -1 if none encountered
//!
uint8_t skip(char *str, uint8_t i, uint8_t e, char *skips) {
  while (i < e) {
    if (str[i] == '\0') return -1;
    if (strchr(skips,str[i]) == NULL) return i;
    i++;
  }
  return -1;
}

//! processCmd
//!
//! @brief process shell command
//!
//! Process command in command buffer cmd[] 
//! starting at cmd[curindex] and ending at cmd[len]
//! Dispatches to individual command handlers
//!
//! @param cmd is the pointer to the string of shell command
//! @param curindex is always zero
//! @param len is the number of characters in the shell command
//! @param sockfd is the TCP socket descriptor
//!
//! @return 0 or positive integers : Normal
//! @return negative integers      : Close
//!
int processCmd(char *cmd, int curindex, int len, int16_t sockfd)
{
  int c, mc, mx, nindex;
  char msg[SERIALMSGSIZE];
  int status = 0;

  memset(msg,0,SERIALMSGSIZE);
  nindex = skip(cmd, curindex, len, " \r\n\t");
  if ((nindex >= 0) && (nindex < len-1)) {
    for (c = 0; c < sizeof(cmd_table)/sizeof(cmd_desc_t); c++) {
      mc = strlen(cmd_table[c].key);
      mx = len - nindex;
      if ((mc <= mx) && (strncmp(&cmd[nindex],cmd_table[c].key,mc) == 0)) {
	status = cmd_table[c].fun(msg, cmd, nindex+mc+1,len, sockfd);
	send(sockfd, msg, strlen(msg), 0);
	if (status == 0) prompt(sockfd);
	return status;
      }
    }
    sprintf(msg, "bad cmd: %s",&cmd[nindex]);
    send(sockfd, msg, strlen(msg), 0);
  }
  prompt(sockfd);
  return 0;
}
