/*
 * The Versatile Governor
 *

 Copyright (C) 2004  Joseph Pingenot
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version  2.1  of  the  License, or (at your option) any later
 version.
                                                                                                       
 This library is distributed in the hope that it will be  useful,
 but  WITHOUT  ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.
                                                                                                       
 You  should  have  received  a copy of the GNU Lesser General Public
 License along with this library; if not, write  to  the  Free Software
 Foundation,  Inc.,  59  Temple  Place,  Suite 330, Boston, MA 02111-1307  USA

*/

/*
 * Right now, this is a quick hack.  More functionality and completeness as we need.
 * The frequency scaling stuff needs to be moved out to libsys.
 *
 */

#include <math.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <syslog.h>
#include <sys/un.h>
#include <glib.h>
#include <dbus/dbus-glib.h>
#define DBUS_API_SUBJECT_TO_CHANGE
#include <dbus/dbus-glib-lowlevel.h>
/*Our own includes.*/
#include "libproc/libproc.h"
#include "vsgov-locator.h"
#include "vsgov-acpiscan.h"
#include "config.h"

/*Global vars.  Too inefficient to regenerate them whenever they're needed.*/
char *file_cpuMinFreq;
char *file_cpuMaxFreq;
char *file_scaleMinFreq;
char *file_scaleMaxFreq;
char *file_governorFile;
char *file_setSpeedFile;

/*Linked list of frequencies*/
struct freq {
  /*The actual frequency*/
  long unsigned int frequency;
  /*This gives how hard it is to dislodge the cpu from a frequency*/
  unsigned int weight;
  struct freq *next;
};
/*Stores pointers and such for the timer handler.*/
struct data_param {
  struct freq *head;
  long unsigned int min;
  long unsigned int max;
  unsigned int numcpus;
};

int set_frequency(const char *filename, long unsigned int value);
long unsigned int get_frequency(const char *filename);
int scan_frequencies(struct freq **freqs);
void set_files();
void push_freq(struct freq *head, long unsigned int freq, unsigned int weight);
long unsigned int find_nearest_freq(struct freq *head, long unsigned int freq);
void write_pid(pid_t pid);
static DBusHandlerResult handle_signal(DBusConnection *connection, DBusMessage *message, void *user_data);
static gboolean check_n_set(struct data_param *data);

int main(void){
  struct data_param internal_data;
  pid_t pid;
  struct freq *current;
  GMainLoop *loop;
  DBusConnection *bus;
  DBusError error;
  
  set_files();
  scan_frequencies(&(internal_data.head));
  internal_data.numcpus = get_numcpus();

  /*printf("valid frequencies\n");*/
  current = internal_data.head;
  internal_data.min = internal_data.max = 0;
  while(current != NULL){
    /*printf(" %lu\n", current->frequency);*/
    if((current->frequency < internal_data.min) || (internal_data.min == 0)){
      internal_data.min = current->frequency;
    }
    if(current->frequency > internal_data.max){
      internal_data.max = current->frequency;
    }
    current = current->next;
  }
  /*fprintf(stderr, "min freq=%lu, max freq=%lu\n", min, max);*/
  if(internal_data.max < internal_data.min){
    fprintf(stderr, "min freq. greater than max?!\n");
    exit(1);
  }

  /*open the syslog.*/
  openlog(SYSLOGIDENT, SYSLOGOPTS, SYSLOGFAC);
  //syslog(LOG_WARNING, "vsgovernor: number of cpus is %d.\n", numcpus);

  /*Now set up the main loop.*/
  loop = g_main_loop_new(NULL, FALSE);

  dbus_error_init(&error);
  if(!(bus = dbus_bus_get(DBUS_BUS_SYSTEM, &error))) {
    syslog(LOG_ERR, "vsgovernor: unable to listen to the system bus (%s)", error.message);
    dbus_error_free(&error);
    exit(2);
  }
  fprintf(stderr, "bus is %x\n", (int)bus);
  /*Now we can switch to daemon (before this, we should be ready for most stuff!*/
  if((pid = fork()) != 0){
    /*We're the parent.  We write the child's id out to the file, and exit.*/
    write_pid(pid);
    exit(0);
  }
  dbus_connection_setup_with_g_main(bus, NULL);

  dbus_bus_add_match(bus, "type='signal',interface='net.digitasaru.vsgov.Signal'", &error);
  dbus_connection_add_filter(bus, handle_signal, loop, NULL);
  
  /*Set up the timeout*/
  g_timeout_add(DEFSLEEPTIME, (GSourceFunc)check_n_set, &internal_data);

  /*And GO!*/
  g_main_loop_run(loop);

  closelog();
  return 0;
}

/*Handle user requests.*/
static DBusHandlerResult handle_signal(DBusConnection *connection, DBusMessage *message, void *user_data) {
  DBusError error;
  char *msg;
  dbus_error_init(&error);
  if(!(dbus_message_get_args(message, &error, DBUS_TYPE_STRING, &msg, DBUS_TYPE_INVALID))) {
    syslog(LOG_WARNING, "got user message, but got an error decoding it! (%s)", error.message);
    dbus_error_free(&error);
    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
  }
  syslog(LOG_WARNING, "got user message (%s)", msg);
  dbus_free(msg);
  return DBUS_HANDLER_RESULT_HANDLED;
}

/*Stuff to do on each cycle*/
static gboolean check_n_set(struct data_param *data) {
  long unsigned freq;
  long unsigned idealfreq;
  long unsigned currentfreq;
  unsigned int cpu;
  unsigned int tstate;
  struct libproc_loadavg *load;
  struct libproc_acpi_cputhrottling* throttle;
  int err;
  /*Get the current load average.*/
  load = libproc_getloadavg();
  if(load == NULL){
    syslog(LOG_WARNING, "vsgovernor: unable to get load average!\n");
    return FALSE;
  }
  currentfreq = get_frequency(file_setSpeedFile);
  idealfreq = (data->max - data->min)*(load->two) + data->min;
  //fprintf(stderr, "  ideal=%lu\n", idealfreq);
  freq = find_nearest_freq(data->head, idealfreq);
  if(load->one > 1.0) load->one = 1.0;
  /*syslog(LOG_INFO, "  ideal=%lu, freq=%lu, current=%lu, load=%f\n", idealfreq, freq, currentfreq, load->two);*/
  if(freq != currentfreq) set_frequency(file_setSpeedFile, freq);
  /*Now for the cpu throttling.  For now, this is keyed off of the first load num.*/
  for(cpu = 0; cpu < data->numcpus; cpu++) {
    //syslog(LOG_WARNING, "vsgovernor: cpu %d\n", cpu);
    /*Get the throttling info for it.*/
    if((throttle = libproc_acpi_get_cputhrottling(cpu, &err)) != NULL) {
      /*got throttle info.  What is the best throttle for this load?*/
      tstate = locate_throttling(throttle, load->one);
      //syslog(LOG_WARNING, "vsgovernor: recommended tstate %d\n", tstate);
      /*Now set that tstate*/
      if(tstate != throttle->active) {
	err = libproc_acpi_set_cpulimit(cpu, 0, tstate);
	//syslog(LOG_WARNING, "vsgovernor: set cpulimit(%d, %d, %d), returned %d.\n", cpu, 0, tstate, err);
	//}else{
	//syslog(LOG_WARNING, "vsgovernor: setting limit unnecessary.\n");
      }
      /*now we don't need the throttle info, so free the struct.*/
      libproc_acpi_freethrottling(throttle);
      //}/*If we couldn't get throttle information, just keep it where it is.*/else{
      //syslog(LOG_WARNING, "vsgovernor: unable to get throttle data\n");
    }
  }
  libproc_destroy_loadavg(load);
  load = NULL;
  return TRUE;
}




void write_pid(pid_t pid){
  FILE *file;
  file = fopen(DEFPIDFILE, "w");
  if(file == NULL){
    /*Error.  Write an error and exit abnormally.*/
    fprintf(stderr, "vsgovernor: error opening pidfile %s for writing (%d: %s)\n", DEFPIDFILE, errno, strerror(errno));
    fprintf(stderr, "vsgovernor: child pid is %u\n", pid);
    exit(2);
  }
  fprintf(file, "%u", pid);
  fclose(file);
}



void set_files(){
  /*For now, we just need a quick and dirty fix.*/
  file_cpuMinFreq = (char *)malloc(1024);
  file_cpuMaxFreq = (char *)malloc(1024);
  file_scaleMinFreq = (char *)malloc(1024);
  file_scaleMaxFreq = (char *)malloc(1024);
  file_governorFile = (char *)malloc(1024);
  file_setSpeedFile = (char *)malloc(1024);

  snprintf(file_cpuMinFreq, 1024, "%s/%s/%s/%s", FREQFILEBASE, DEFAULTCPU, FREQSUBDIR, CPUMINFREQFILE);
  snprintf(file_cpuMaxFreq, 1024, "%s/%s/%s/%s", FREQFILEBASE, DEFAULTCPU, FREQSUBDIR, CPUMAXFREQFILE);
  snprintf(file_scaleMinFreq, 1024, "%s/%s/%s/%s", FREQFILEBASE, DEFAULTCPU, FREQSUBDIR, MINFREQFILE);
  snprintf(file_scaleMaxFreq, 1024, "%s/%s/%s/%s", FREQFILEBASE, DEFAULTCPU, FREQSUBDIR, MAXFREQFILE);
  snprintf(file_governorFile, 1024, "%s/%s/%s/%s", FREQFILEBASE, DEFAULTCPU, FREQSUBDIR, CPUGOVERNORFILE);
  snprintf(file_setSpeedFile, 1024, "%s/%s/%s/%s", FREQFILEBASE, DEFAULTCPU, FREQSUBDIR, SETSPEEDFILE);
}

/*Take ownership*/
void set_governor(){
  int fd;
}

/*Scan the frequencies to see what are all available.*/
int scan_frequencies(struct freq **freqs) {
  /*First, get the frequencies available.*/
  long unsigned int current, min, max, readfreq;
  long unsigned int original_min, original_max;
  
  /*First, set min and max frequencies*/
  min = get_frequency(file_cpuMinFreq);
  max = get_frequency(file_cpuMaxFreq);
  original_min = get_frequency(file_scaleMinFreq);
  original_max = get_frequency(file_scaleMaxFreq);
  set_frequency(file_scaleMinFreq, min);
  set_frequency(file_scaleMaxFreq, max);
  
  /*Initialize the list.*/
  *freqs = NULL;
  /*Now, scan through.*/
  for(current = min; current <= max; current += FREQSTEP){
    /*printf("frequency: %lu\n", current);*/
    set_frequency(file_setSpeedFile, current);
    if(get_frequency(file_setSpeedFile) == current){
      /*printf("  recognized!\n");*/
      /*This one is valid; it stuck*/
      if(*freqs == NULL){
	*freqs = (struct freq*)malloc(sizeof(struct freq));
	(*freqs)->frequency = current;
	(*freqs)->weight = 1;
	(*freqs)->next = NULL;
      }else{
	push_freq(*freqs, current, 1);
      }
    }
  }

  /*Now reset the limits.*/
  set_frequency(file_scaleMinFreq, original_min);
  set_frequency(file_scaleMaxFreq, original_max);
  return 0;
}

void push_freq(struct freq *head, long unsigned int freq, unsigned int weight){
  if(head->next == NULL){
    head->next = (struct freq*)malloc(sizeof(struct freq));
    head->next->frequency = freq;
    head->next->weight = weight;
    head->next->next = NULL;
  }else{
    push_freq(head->next, freq, weight);
  }
}

struct freq *_find_nearest_freq(struct freq *head, long unsigned int freq);
/*Fairly simple, yet elegant; we start at the bottom of the list
 * comparing the closest value later down the list to the closest
 * value at this point.  The closer wins and gets passed down.
 */
long unsigned int find_nearest_freq(struct freq *head, long unsigned int freq) {
  /*This is a wrapper function, so that we can modify the details in the actual function
   * with impugnity*/
  long unsigned int frequency;
  if(head == NULL) {
    /*Default to the lowest frequency possible.*/
    return 0;
  }
  //printf("in find_nearest_freq\n");
  frequency= (_find_nearest_freq(head, freq))->frequency;
  //printf(" frequency is found to be %d.\n", frequency);
  return frequency;
}
/*Weight arg is so that the next-down recurse can pass back weight info*/
struct freq *_find_nearest_freq(struct freq *head, long unsigned int freq) {
  struct freq *nearest;
  //fprintf(stderr, "   *freq=%lu, head weight=%u head freq=%lu\n", freq, (head == NULL)?-1:head->weight, (head == NULL)?-1:head->frequency);
  if(head->next == NULL){
    /*This *is* the nearest, at least for now.
     *This way, we have at least one valid value.*/
    //fprintf(stderr, " head; frequency=%lu\n", head->frequency);
    /*We have to make sure we pass back the weight.*/
    return head;
  }else{
    nearest = _find_nearest_freq(head->next, freq);
    //fprintf(stderr, " Not head; freq=%lu, nearest=%lu, frequency=%lu\n", freq, nearest->frequency, head->frequency);
    if((head->weight == 0) || ((nearest->weight != 0) && (abs(freq - (nearest->frequency)/(nearest->weight)) < abs(freq - (head->frequency)/(head->weight))))){
      //fprintf(stderr, " Nearest!\n");
      return nearest;
    }else{
      //fprintf(stderr, " Head!\n");
      return head;
    }
  }
}


long unsigned int get_frequency(const char *filename){
  FILE *file;
  int err = 0;
  long unsigned int value;
  char errstring[1024];
  
  if((file = fopen(filename, "r")) == NULL){
    err=errno;
    if(strerror_r(err, errstring, 1024) == 0){
      fprintf(stderr, "vsgovernor/get_frequency: unable to open file (%s) for reading (error %d: %s)\n", filename, err, errstring);
    }else{
      fprintf(stderr, "vsgovernor/get_frequency: unable to open file (%s) for reading (error %d)\n", filename, err);
    }
    return 0;
  }
  /*Alright; the file's open and we have RAM for it.  Snag the info.*/
  err = fscanf(file, "%lu", &value);
  if(err != 1){
    fprintf(stderr, "vsgovernor/get_frequency: format in file (%s) unrecognized!\n", filename);
    return 0;
  }
  fclose(file);
  return value;
}

int set_frequency(const char *filename, long unsigned int value){
  FILE *file;
  int err;

  if((file = fopen(filename, "w")) == NULL){
    fprintf(stderr, "vsgovernor/set_frequency: unable to open file (%s) for writing (error %d: %s)\n", filename, errno, strerror(errno));
    return 0;
  }
  /*Alright; the file's open and we have RAM for it.  Snag the info.*/
  err = fprintf(file, "%lu", value);
  fclose(file);
  return 0;
}


