#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include "sysfs.h"
#include "cpufreq.h"
#include "debug.h"
#include "cputemp2maxfreq.h"
#include "failsafe.h"
#include "version.h"

// Valid frequencies are between 100MHz and 10GHz
#define VALID_FREQ_MIN 100000
#define VALID_FREQ_MAX 10000000

// Valid temperatures are between 10 and 150 degrees
#define VALID_TEMP_MIN 10000
#define VALID_TEMP_MAX 150000

struct s_cpudata cpudata;
struct s_config config={"conservative",70000,"/sys/devices/virtual/thermal/thermal_zone0/temp",100000,2000000,10};

void signal_handler(int signum)
{
 printf("Received signal %d, exiting.\n",signum);
 failsafe(0);
}

int main()
{
 long int diff;
 long int newfreq;

 printf("Version %s, buildtime %s %s\n",version(),__DATE__,__TIME__);

// Print configuration
 printf("Configuration:\n");
 printf("Governor: %s\n",config.governor);
 printf("Temperature: %ld\n",config.max_temp);
 printf("Temp input: %s\n",config.temp_input);
 printf("Frequency step: %ld\n",config.freq_step);
 printf("Fallback frquency: %ld\n",config.fallback_freq);
 printf("Interval: %d\n",config.interval);

// Get and validate CPU data
 cpudata.min_freq=sysfs_read_long_int("/sys/devices/system/cpu/cpufreq/policy0/cpuinfo_min_freq");
 cpudata.max_freq=sysfs_read_long_int("/sys/devices/system/cpu/cpufreq/policy0/cpuinfo_max_freq");
 cpudata.scale_max=sysfs_read_long_int("/sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq");

 printf("\nCPU data:\n");
 printf("Minimum frequency: %ld\n",cpudata.min_freq);
 printf("Maximum frequency: %ld\n",cpudata.max_freq);
 printf("Scaling maximum frequency: %ld\n",cpudata.scale_max);

 if ((cpudata.min_freq<VALID_FREQ_MIN) || (cpudata.min_freq>VALID_FREQ_MAX) ||
     (cpudata.max_freq<VALID_FREQ_MIN) || (cpudata.max_freq>VALID_FREQ_MAX) ||
     (cpudata.scale_max<VALID_FREQ_MIN) || (cpudata.scale_max>VALID_FREQ_MAX))
 {
// If we have to fail now, there's not much we can do because we have no data
  printf("Invalid CPU data, exiting.\n");
  exit(1);
 }

// Set the governor
 if (cpufreq_set_str("scaling_governor",config.governor,0)<0)
 {
  printf("Failed to set governor, error %d (%s).\n",errno,strerror(errno));

// We failed to set the governor, call the failsafe
  failsafe(1);
 }

// Set signal handlers
 signal(SIGTERM,signal_handler);
 signal(SIGINT,signal_handler);
 signal(SIGQUIT,signal_handler);

 while(1)
 {
// Get new measurements
  cpudata.cur_freq=sysfs_read_long_int("/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq");
  if ((cpudata.cur_freq<VALID_FREQ_MIN) || (cpudata.cur_freq>VALID_FREQ_MAX))
  {
   printf("Invalid current frequency reported by CPU, exiting.\n");
   failsafe(1);
  }

  cpudata.cur_temp=sysfs_read_long_int(config.temp_input);
  if ((cpudata.cur_temp<VALID_TEMP_MIN) || (cpudata.cur_temp>VALID_TEMP_MAX))
  {
   printf("Invalid current tempature reported by CPU, exiting.\n");
   failsafe(1);
  }

  DEBUG1_MAIN("Data: %ld %ld %ld %ld %ld\n",cpudata.cur_temp,config.max_temp,cpudata.max_freq,cpudata.scale_max,cpudata.cur_freq);

// Check if we should increase
  if ((cpudata.cur_temp<config.max_temp) && (cpudata.scale_max<cpudata.max_freq))
  {
   diff=(config.max_temp-cpudata.cur_temp)/1000;
   newfreq=cpudata.scale_max+(config.freq_step*diff);
   if (newfreq>cpudata.max_freq) newfreq=cpudata.max_freq;
   DEBUG1_MAIN("Increase to %ld\n",newfreq);

// Set new value and validate
   if (cpufreq_set_long_int("scaling_max_freq",newfreq,100)<0)
   {
    printf("Failed to set scaling_max_freq, exiting.\n");
    failsafe(1);
   }
   cpudata.scale_max=sysfs_read_long_int("/sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq");
   if ((cpudata.scale_max<VALID_FREQ_MIN) || (cpudata.scale_max>VALID_FREQ_MAX))
   {
    printf("Invalid scale_max_freq reported, exiting,\n");
    failsafe(1);
   }
  }

// Check if we should decrease
  if ((cpudata.cur_temp>config.max_temp) && (cpudata.scale_max>cpudata.min_freq))
  {
   diff=(cpudata.cur_temp-config.max_temp)/1000;
   newfreq=cpudata.scale_max-(config.freq_step*diff);
   if (newfreq<cpudata.min_freq) newfreq=cpudata.min_freq;
   DEBUG1_MAIN("Decrease to %ld\n",newfreq);

// Set new value and validate
   if (cpufreq_set_long_int("scaling_max_freq",newfreq,100)<0)
   {
    printf("Failed to set scaling_max_freq, exiting.\n");
    failsafe(1);
   }
   cpudata.scale_max=sysfs_read_long_int("/sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq");
   if ((cpudata.scale_max<VALID_FREQ_MIN) || (cpudata.scale_max>VALID_FREQ_MAX))
   {
    printf("Invalid scale_max_freq reported, exiting,\n");
    failsafe(1);
   }
  }

  sleep(config.interval);
 }
}