/******************************************************************
 * lcd_driver.cpp | Arduino Duemilanove w/ ATmega328
 ******************************************************************
 * Author: Jonathan A. Feucht
 * Date created: 25 May 2010
 * 
 * This file contains display drivers for a Crystalfontz 
 * CFA533 16×2 character 6-button LCD display.
 *
 */

#include <WProgram.h>
#include <avr/pgmspace.h>
#include "common.h"
#include "lcd_driver.h"
#include "i2c_driver.h"
#include "eeprom_driver.h"
#include "menu_items.h"

unsigned char LCD_buffer[BUFF_SIZE];

unsigned char packet [MAX_PACKET_SIZE];
unsigned int  packet_size = 0;

char hold_values[BUTTON_CNT];                  // Stores data for each of the buttons
unsigned long  hold_time = 0;         // Times events between held down buttons
unsigned long  poll_time = 0;
boolean new_key_poll = false;

// Menu placemarkers
menu*     root;
menu*     current;
int       sel_menu_item;

void setup_lcd() {
  unsigned char i;

  Serial.begin(19200); 

  // Disable key reporting. We will poll ourselves.
  send_packet(0x17, "\0\0", 2); 

  // Fetch the boot screen out of program memory
  print_prog_str(boot_screen);

  // Reset the LCD packet
  packet_size = 0;
  packet[0] = 0;

  // Set up held button states
  for (i = 0; i < BUTTON_CNT; i++) {
    hold_values[i] = 0; 
  }

  delay(5000);

  // Set up the navigation menu
  set_up_menu();

  return;  
}

void print_prog_str(const prog_char* prog_str) {
  // Fetch the string out of program memory
  strcpy_P((char*)LCD_buffer, prog_str);
  print_to_lcd ();  

  return; 
}

void poll_keys() {
  unsigned long cur_time = millis();
  if (poll_time <= cur_time) {
    // Get key positions
    do {
      send_packet(0x18, "", 0);
      receive_packet();
      // Keep trying until we receive a success code
    } 
    while (packet[0] != 0x58);  
    //send_packet(0x18, "", 0);
    delay(1);
    //receive_packet();
    // Process response packet from key polling
    process_packet();
    // Schedule next poll in 1 ms
    poll_time = cur_time + 1;
  }

  return; 
}

void set_up_menu() {
  // Main menu
  main_menu.menu_items = main_menu_items;
  main_menu.label = main_menu_label;
  main_menu.menu_cnt = MAIN_CNT;
  main_menu.children = main_menu_children;
  main_menu.parent = null;
  main_menu.setting_val_ptr = null;
  main_menu.setting_idx = SET_MAIN;
  // Sound menu
  sound_menu.menu_items = sound_menu_items;
  sound_menu.label = sound_menu_label;
  sound_menu.menu_cnt = SOUND_CNT;
  sound_menu.children = sound_menu_children;
  sound_menu.parent = &main_menu;
  sound_menu.setting_val_ptr = null;
  sound_menu.setting_idx = SET_SOUND;
  // Volume menu
  volume_menu.menu_items = level_menu_items;
  volume_menu.label = volume_menu_label;
  volume_menu.menu_cnt = LEVEL_CNT;
  volume_menu.children = volume_menu_children;
  volume_menu.parent = &sound_menu;
  volume_menu.setting_val_ptr = &volume_setting;
  volume_menu.setting_idx = SET_VOLUME;
  // Reverb menu
  reverb_menu.menu_items = level_menu_items;
  reverb_menu.label = reverb_menu_label;
  reverb_menu.menu_cnt = LEVEL_CNT;
  reverb_menu.children = reverb_menu_children;
  reverb_menu.parent = &sound_menu;
  reverb_menu.setting_val_ptr = &reverb_setting;
  reverb_menu.setting_idx = SET_REVERB;
  // Chorus menu
  chorus_menu.menu_items = level_menu_items;
  chorus_menu.label = chorus_menu_label;
  chorus_menu.menu_cnt = LEVEL_CNT;
  chorus_menu.children = chorus_menu_children;
  chorus_menu.parent = &sound_menu;
  chorus_menu.setting_val_ptr = &chorus_setting;
  chorus_menu.setting_idx = SET_CHORUS;
  // Instrument menu
  instr1_menu.menu_items = instr1_menu_items;
  instr1_menu.label = instr1_menu_label;
  instr1_menu.menu_cnt = INSTR1_CNT;
  instr1_menu.children = instr1_menu_children;
  instr1_menu.parent = &main_menu;
  instr1_menu.setting_val_ptr = &instr1_setting;
  instr1_menu.setting_idx = SET_INSTR1;
  // Set main menu as root node and navigate to first item
  root = &main_menu;
  sel_menu_item = 0;
  current = root;

  return;
}

// Navigate to a menu node
void navigate(menu* new_node) {
  unsigned char* list_selection_ptr;
  if (new_node != null) {
    current = new_node;
    list_selection_ptr = new_node->setting_val_ptr;
    if (list_selection_ptr != null) {
      // Navigate to the currently selected list item
      sel_menu_item = *(new_node->setting_val_ptr);
    } 
    else {
      sel_menu_item = 0; 
    }
    update_display();
  }
  return;
}

void menu_driver() {
  int i;
  const prog_char* cur_str_ptr;
  unsigned long cur_time;
  unsigned char* cur_setting_ptr;
  boolean key_event = false;
  char pressed_buttons = 0;
  char held_buttons = 0;
  boolean empty_keypad = true;

  if (new_key_poll) {
    new_key_poll = false;

    // Get current program time in milliseconds
    cur_time = millis();
    if ((cur_time + 10000) < hold_time) {
      hold_time = 0;
    }
    //LCD.print("\n\rRead values: ");
    for (i = 5; i >= 0; i--) {
      pressed_buttons <<= 1;
      held_buttons <<= 1;
      pressed_buttons += (char)(hold_values[i] >= PRESS_THRESHOLD && hold_values[i] < HOLD_THRESHOLD);
      held_buttons += (char)(hold_values[i] >= HOLD_THRESHOLD);
      empty_keypad = empty_keypad && (hold_values[i] == 0); 
    }

    if (pressed_buttons && (cur_time >= hold_time )) {
      // Key was just pressed or released, and is still being held down. 
      // Reset hold counter. Initial hold time is 1 second
      key_event = true;
      hold_time = cur_time + 1000;
    } 
    else if (held_buttons && (cur_time >= hold_time)) {
      // Key is being held. Time key events every 100 milliseconds
      key_event = true;
      hold_time = cur_time + 100;
    } 
    else if (empty_keypad) {
      //LCD.print("Empty keypad.");
      hold_time = 0; 
    }
    if (key_event) {    
      // Process all keys currently being held down
      switch (held_buttons | pressed_buttons) {
      case KP_UP:
        if (sel_menu_item > 0) {
          sel_menu_item--; 
          update_display();
        }
        break;
      case KP_ENTER:
        // Wait at least 2 seconds until the next key event
        hold_time = cur_time + 2000;
        // Find where the setting for the current node is stored
        cur_setting_ptr = current->setting_val_ptr;
        // Does a setting exist for the current node?
        if (cur_setting_ptr != null) {
          // Adjust the setting
          *cur_setting_ptr = sel_menu_item;
          // Relay the new setting to the slave device
          report_setting(current->setting_idx, sel_menu_item);
          // Print success message
          print_prog_str(success_msg);

          delay(1000);
          // Navigate to root menu
          navigate(root);
          break;
        } 
        else if (current->children == null || current->children[sel_menu_item] == null) {
          // There are no children, and there is no configurable setting.
          // Process selection as user menu command.
          // Which menu node are we in?
          // (There are no commands, yet, but we could make some...)
          switch (current->setting_idx) {
          case SET_MAIN:
            // Command was in the main menu
            switch(sel_menu_item) {
            case MAIN_CMD_SAVE:
              // Print success message
              print_prog_str(saved_msg);
              save_eeprom();
              break;
              
            case MAIN_CMD_RESET:
              // Print success message
              print_prog_str(reset_msg);
              reset_eeprom();
              i2c_report_all();
              break;
            }
            break;
          case SET_SOUND:
            break;
          }
          delay(1000);
          navigate(root);
          
          break; 
        }
        // Otherwise, navigate to the child node.
        // (That's why there is no break statement here)
      case KP_RIGHT:
        // Are there children nodes? Follow it.
        if (current->children) {
          navigate(current->children[sel_menu_item]);
        }
        break;
      case KP_CANCEL:
        navigate(root);
        break;
      case KP_LEFT:
        navigate(current->parent);
        break;
      case KP_DOWN:
        if (sel_menu_item < (current->menu_cnt - 1)) {
          sel_menu_item++;
          update_display();
        }
        break;
      default: // Don't do anything
        break;
      }
    }
  }

  return; 
}

void update_display() {
  unsigned int label_length = 0;
  // Fetch the pointer to the next menu string out of program memory
  if (current->label) {
    strcpy_P((char*)LCD_buffer, current->label);
    label_length = strlen((char*)LCD_buffer);
  }
  strcpy_P((char*)&LCD_buffer[label_length], (char*)pgm_read_word(&(current->menu_items[sel_menu_item])));
  print_to_lcd(); 

  return;
}

void print_to_lcd() {
  char LCD_output[33];
  int i, j;
  char cur_char;
  int str_size;

  str_size = strlen((char*)LCD_buffer);
  j = 0;

  for (i = 0; i < str_size; i++) {
    cur_char = LCD_buffer[i];
    // Whitespace character? Treat as new line character.
    if (cur_char < ' ') {
      // Fill in the rest of the line with spaces
      for (; j % 16; j++) {
        LCD_output[j] = ' ';
      } 
    } 
    else {
      LCD_output[j] = LCD_buffer[i];
      j++;
    }
    if (j >= 34) {
      break; 
    }
  } 
  for (; j < 32; j++) {
    LCD_output[j] = ' ';
  }
  // Continue trying to print to the LCD until we receive a success message
  while (packet[0] != 0x47) {
    send_packet(0x07, &LCD_output[0], 16);
    receive_packet();
  }
  while (packet[0] != 0x48) {
    send_packet(0x08, &LCD_output[16], 16);
    receive_packet();
  }

  return;
}

void send_packet(char command, char* data, int data_size) {
  unsigned int checksum;
  unsigned int i;

  packet[0] = command;
  packet[1] = data_size;
  for (i = 0; i < data_size; i++) {
    packet[i + 2] = data[i];
  }

  checksum = get_crc(data_size + 2);

  packet[2 + data_size] = checksum & 0xFF;
  packet[3 + data_size] = (checksum >> 8) & 0xFF;

  for (i = 0; i <= (3 + data_size); i++) {
    Serial.print(packet[i], BYTE);
  }

  // Clear the outgoing buffer
  packet[0] = '\0';

  return;
}

void receive_packet() {
  char retval;
  char buffer[20];
  int checksum;
  int data_size;

  // Read in any incoming packet // (retval = Serial.read()) != (char)0xFF
  for (packet_size = 0; Serial.available(); packet_size++) {
    retval = Serial.read();
    if (packet_size >= MAX_PACKET_SIZE) {
      break;
    } 
    packet[packet_size] = retval;
    //LCD.print((char)packet[packet_size]);
  }
  // The min packet size is four
  if ((packet_size >= 4) && (packet_size <= MAX_PACKET_SIZE)) {
    // Find the size of the data
    data_size = (unsigned int)packet[1];
    // Is the data size a valid length?
    if (data_size <= (MAX_PACKET_SIZE - 4)) {
      checksum = get_crc(2 + data_size);
      // Validate the packet's checksum
      if ((packet[2 + data_size] == (char)(checksum)) && 
        (packet[3 + data_size] == (char)((checksum >> 8)))) {
        // We have a valid packet from the LCD screen
        process_packet();
      } 
      else {
        // Mark the packet as empty
        packet_size = 0; 
      }
    }
  }
  else {
    // Delete the packet
    packet[0] = 0;
    packet_size = 0;
  }
  return;
}

void process_packet() {
  int i;
  char command = packet[0];
  unsigned int data_size = (int)packet[1];
  unsigned char* data = &(packet[2]);

  if (data_size <= MAX_PACKET_SIZE - 4) {

    command = packet[0];
    data_size = (int)packet[1];

    // For our allpication, we only process key command 0x80
    switch (command) {
    case 0x58: // Read keypad
      // Expect exactly three bytes of data
      if (data_size == 3) {
        for (i = 0; i < 6; i++) {
          hold_values[i] += ((data[0] % 2) ? 1 : -1);
          if (hold_values[i] < 0) {
            hold_values[i] = 0;
          } 
          else if (hold_values[i] > HOLD_MAX) {
            hold_values[i] = HOLD_MAX; 
          }
          new_key_poll = true;

          data[0] >>= 1;
        } 
      }

      break;
    default:
      break; 
    }
  }

  return;  
}

unsigned int get_crc(unsigned char count) {
  unsigned short crc; //Calculated CRC
  unsigned char i; //Loop count, bits in byte
  unsigned char data; //Current byte being shifted
  unsigned char* buffer = packet;
  crc = 0xFFFF; // Preset to all 1's, prevent loss of leading zeros

  while(count--) {
    data = *buffer++;
    i = 8;
    do {
      if((crc ^ data) & 0x01)	{
        crc >>= 1;
        crc ^= 0x8408;
      }
      else
        crc >>= 1;
      data >>= 1;
    } 
    while(--i != 0);
  }
  return (~crc);
}















