#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <getopt.h>
#include <string.h>

#define SMC_FILENAME "/dev/smc"

int smc_fd;

void query_poweron_type(unsigned char *msg)
{
  msg[0] = 0x01;
}

void query_rtc(unsigned char *msg)
{
  msg[0] = 0x04;
}

void query_sensor(unsigned char *msg)
{
  msg[0] = 0x07;
}

void query_tray(unsigned char *msg)
{
  msg[0] = 0x0A;
}

void query_avpack(unsigned char *msg)
{
  msg[0] = 0x0F;
}

void query_version(unsigned char *msg)
{
  msg[0] = 0x12;
}

void query_ir_address(unsigned char *msg)
{
  msg[0] = 0x16;
}

void query_tilt_sensor(unsigned char *msg)
{
  msg[0] = 0x17;
}

void set_standby(unsigned char *msg)
{
  msg[0] = 0x82;
  // TODO: other parameters
}

void set_time(unsigned char *msg, int time)
{
  msg[0] = 0x85;
// msg[1] = ...
}

void set_fan_algorithm(unsigned char *msg, int algorithm)
{
  msg[0] = 0x88;
  msg[1] = algorithm;
}

void set_fan_speed(unsigned char *msg, int cpugpu, int speed)
{
  msg[0] = cpugpu ? 0x94 : 0x89;
  msg[1] = speed | 0x80;
}

void set_dvd_tray(unsigned char *msg, int state)
{
  msg[0] = 0x8B;
  msg[1] = state ? 0x60 : 0x62;
}

void set_power_led(unsigned char *msg, int override, int state, int startanim)
{
  msg[0] = 0x8C;
  msg[1] = (override ? 1 : 0) | (state ? 0 : 2);
  msg[2] = startanim;
}

void set_audio_mute(unsigned char *msg, int mute)
{
  msg[0] = 0x8D;
  msg[1] = mute;
}

void set_ir_address(unsigned char *msg, int ir_address)
{
  msg[0] = 0x95;
  msg[1] = ir_address;
}

void set_dvd_tray_secure(unsigned char *msg)
{
  msg[0] = 0x98;
}

void set_leds(unsigned char *msg, int enable, int red, int green)
{
  msg[0] = 0x99;
  msg[1] = enable;
  msg[2] = red | (green << 4);
}

void set_rtc_wake(unsigned char *msg, int time)
{
  msg[0] = 0x9a;
  // todo
}

void hexdump(unsigned char *msg)
{
  int i;
  for (i=0; i<0x10; ++i)
    printf("%02x ", msg[i]);
  printf("\n");
}

void show_tilt(unsigned char *msg)
{
  if (*msg == 0x83)
    msg++;
  printf("tilt: %s\n", (msg[1]&1) ? "vertical" : "horizontal");
}

void show_tray(unsigned char *msg)
{
  char *states[] = {"open", "opening", "closed", "closing", "pushed"};
  printf("tray: %s\n", states[(msg[1] & 0xF) % 5]);
}

void show_ir(unsigned char *msg)
{
  printf("IR code: %02x\n", msg[3]);
}

void show_avpack(unsigned char *msg)
{
  if (*msg == 0x83)
    ++msg;
  printf("AV pack type: %02x\n", msg[1]);
}

void show_poweron_type(unsigned char *msg)
{
  printf("poweron type: %02x\n", msg[1]);
}

void show_rtc(unsigned char *msg)
{
  int rtc = msg[1] | (msg[2] << 8) | (msg[3] << 16) | (msg[4] << 24);
  printf("rtc: %d.%03d\n", rtc / 1000, rtc % 1000);
}

void show_sensor(unsigned char *msg)
{
  printf("sensor data: ");
  int i;
  for (i=0; i<4; ++i)
  {
    float s = (msg[i * 2 + 1] | (msg[i * 2 + 2] << 8)) / 256.0;
    printf("%3.1f C, ", s);
  }
  printf("\n");
}

void show_version(unsigned char *msg)
{
  printf("version: %d.%d\n", msg[2], msg[3]);
}

void show_ir_address(unsigned char *msg)
{
  printf("ir address: %d\n", msg[1]);
}

void show_response(unsigned char *msg)
{
  switch (msg[0])
  {
  case 0x83:
    switch (msg[1])
    {
    case 0x11:
      printf("poweroff occuring\n");
      break;
    case 0x14:
      show_tilt(msg);
      break;
    case 0x60 ... 0x65:
      show_tray(msg);
      break;
    case 0x23:
      show_ir(msg);
      break;
    case 0x40:
      show_avpack(msg);
      break;
    default:
      hexdump(msg);
      break;
    }
    break;
  case 0x01:
    show_poweron_type(msg);
    break;
  case 0x04:
    show_rtc(msg);
    break;
  case 0x07:
    show_sensor(msg);
    break;
  case 0x0a:
    show_tray(msg);
    break;
  case 0x0F:
    show_avpack(msg);
    break;
  case 0x12:
    show_version(msg);
    break;
  case 0x16:
    show_ir_address(msg);
    break;
  case 0x17:
    show_tilt(msg);
    break;
  default:
    hexdump(msg);  
    break;
  }
}

int main(int argc, char **argv)
{
  int first = 1;
    /* try open SMC. if this doesn't work, bail out. */
  smc_fd = open(SMC_FILENAME, O_RDWR);
  if (smc_fd < 0)
  {
    perror(SMC_FILENAME);
    return 1;
  }
  
  while (1)
  {
    unsigned char msg[16];

    static struct option long_options[] = 
      {
        {"query-poweron-type", no_argument, 0, 'p'},
        {"query-rtc", no_argument, 0, 'r'},
        {"query-sensors", no_argument, 0, 's'},
        {"query-tray", no_argument, 0, 't'},
        {"query-avpack", no_argument, 0, 'a'},
        {"query-version", no_argument, 0, 'v'},
        {"query-ir-address", no_argument, 0, 'i'},
        {"query-tilt-sensor", no_argument, 0, 'n'},
        {"set-standby", required_argument, 0, 'S'},
        {"set-time", required_argument, 0, 'R'},
        {"set-fan-algorithm", required_argument, 0, 'f'},
        {"set-cpu-fan-speed", required_argument, 0, 'C'},
        {"set-gpu-fan-speed", required_argument, 0, 'G'},
        {"set-dvd-tray", required_argument, 0, 'T'},
        {"set-powerled", required_argument, 0, 'P'},
        {"set-audio-mute", required_argument, 0, 'M'},
        {"set-ir-address", required_argument, 0, 'I'},
        {"set-dvd-tray-secure", no_argument, 0, 'O'},
        {"set-leds", required_argument, 0, 'L'},
        {"set-rtc-wakeup", required_argument, 0, 'W'},
        {"poll", no_argument, 0, 'w'},
        {}
      };

    char *help[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
      "speed from 0 to 127", "speed from 0 to 127", "0: open, 1: close", 
      "0: default, 1: on, 2: off, 3: animated", 0, 0, 0, "GR with 1+2+4+8 for the red&green segments", 0, 0};

    int option_index = 0, c;
    c = getopt_long (argc, argv, "prstavinS:R:f:C:G:T:P:M:I:OL:W:w",
        long_options, &option_index);
    
    if ((c == -1) && !first)
      break;

    first = 0;
      
    
    if ((c == '?') || (c == -1))
    {
      struct option *o = long_options;
      char **h = help;
      fprintf(stderr, "usage:\n");
      printf("\t%s <cmd> [<cmd>...]\n", argv[0]);
      while (o->name)
      {
        printf("\t\t--%s, -%c \t%s\n", o->name, o->val, o->has_arg ? "<xxx>" : "");
        if (*h)
          printf("\t\t\t%s\n", *h);
        ++h;
        ++o;
      }
      
      break;
    }
    
      /* prepare message */
    memset(msg, 0, 16);
    
    switch (c)
    {
    case 'p':
      query_poweron_type(msg);
      break;
    case 'r':
      query_rtc(msg);
      break;
    case 's':
      query_sensor(msg);
      break;
    case 't':
      query_tray(msg);
      break;
    case 'a':
      query_avpack(msg);
      break;
    case 'v':
      query_version(msg);
      break;
    case 'i':
      query_ir_address(msg);
      break;
    case 'n':
      query_tilt_sensor(msg);
      break;
    case 'S':
      set_standby(msg);
      break;
    case 'R':
      set_time(msg, atoi(optarg));
      break;
    case 'f':
      set_fan_algorithm(msg, atoi(optarg));
      break;
    case 'C':
      set_fan_speed(msg, 0, atoi(optarg));
      break;
    case 'G':
      set_fan_speed(msg, 1, atoi(optarg));
      break;
    case 'T':
      set_dvd_tray(msg, atoi(optarg));
      break;
    case 'P':
      switch (atoi(optarg))
      {
      case 0: // default
        set_power_led(msg, 0, 0, 0);
        break;
      case 1: // always on
        set_power_led(msg, 1, 1, 0);
        break;
      case 2: // always off
        set_power_led(msg, 1, 0, 0);
        break;
      case 3: // animation
        set_power_led(msg, 1, 0, 1);
        break;
      default:
        break;
      }
      break;
    case 'M':
      set_audio_mute(msg, atoi(optarg));
      break;
    case 'I':
      set_ir_address(msg, atoi(optarg));
      break;
    case 'O':
      set_dvd_tray_secure(msg);
      break;
    case 'L':
    {
      int state = strtoul(optarg, 0, 0x10);
      set_leds(msg, !!state, state & 0xF, (state >> 4) & 0xF);
      break;
    }
    case 'W':
      set_rtc_wake(msg, atoi(optarg));
      break;
    case 'w':
      break;
    default:
    {
      abort();
    }
    }
    
    if (c != 'w')
    {
      printf("sending ");
      int i;
      for (i=0; i<0x10; ++i)
         printf("%02x ", msg[i]);
      printf("\n");
      if (write(smc_fd, msg, 16) != 16)
      {
        perror("write");
        break;
      }
    }
    
    if ((c == 'w') || (msg[0] < 0x80))
    {
      int wait_for = msg[0];
      while (1)
      {
        if (read(smc_fd, msg, 16) != 16)
          perror("read");
        show_response(msg);
        if (msg[0] == wait_for)
          break;
      }
    }
  }

}
