#include <csignal>
#include <fstream>
#include <iostream>
#include <map>
#include <regex>
#include <vector>
#include <unistd.h>

static const char* led_list[] = {
    "/sys/class/leds/beaglebone:green:usr0",
    "/sys/class/leds/beaglebone:green:usr1",
    "/sys/class/leds/beaglebone:green:usr2",
    "/sys/class/leds/beaglebone:green:usr3"
};

typedef std::vector<std::string> llist_t;
static llist_t leds;

// TODO: make ltriglist_t use ltrig_t
typedef std::pair<std::string, std::string> ltrig_t;
typedef std::map<std::string, std::string> ltriglist_t;
static ltriglist_t pretrigs;

// Captures the current trigger setting for all leds.
static void read_trigs(const llist_t& leds, ltriglist_t& trigs)
{
       for (const auto &led: leds)
       {
              std::string path(led + "/trigger");
              std::ifstream fs(path.c_str(), std::ifstream::in);
              std::string s;
              if (!fs.is_open())
                     continue;

              char trigger[4096];
              fs.getline(trigger, sizeof(trigger));
              
              // Find the active trigger: "[foo]".
              std::string st(trigger);
              std::smatch m;
              std::regex e("\\[[[:alnum:]]+\\]");
              if (std::regex_search(st, m, e))
              {
                     // Remove the brackets, save the result.
                     std::string trig;
                     trig = std::regex_replace(std::string(m[0]),
                                               std::regex("\\[|\\]"), "");
                     ltrig_t lt(led, trig);
                     trigs.insert(lt);
              }
       }
}

// Sets trigger="none" for all leds.
static void clear_trigs(const llist_t& leds)
{
       for (const auto &led: leds)
       {
              const std::string none("none");
              std::string path(led + "/trigger");
              std::ofstream fs(path.c_str(), std::ifstream::out);

              if (!fs.is_open())
                     continue;

              fs.write(none.c_str(), none.length());
              fs.close();
       }
}

// Restores all known led triggers to their original settings.
static void restore_trigs(ltriglist_t& trigs)
{
       for (const auto &led: trigs)
       {
              std::string path(led.first + "/trigger");
              std::ofstream fs(path.c_str(), std::ofstream::out);
              if (!fs.is_open())
                     continue;
              std::string trig(led.second);
              fs.write(trig.c_str(), trig.length());
              fs.close();
       }
}

// Which LED should be on for the specified state.
int state_to_led(int state)
{
       switch (state % 6)
       {
       default: return 0;
       case 1: return 1;
       case 2: return 2;
       case 3: return 3;
       case 4: return 2;
       case 5: return 1;
       }
}

void led_off(const std::string led)
{
       const std::string path(led + "/brightness");
       const std::string brightness("0");
       std::ofstream fs(path.c_str(), std::ofstream::out);
       if (!fs.is_open()) return;
       fs.write(brightness.c_str(), brightness.length());
       fs.close();
}

void led_on(const std::string led)
{
       const std::string path(led + "/brightness");
       const std::string brightness("255");
       std::ofstream fs(path.c_str(), std::ofstream::out);
       if (!fs.is_open()) return;
       fs.write(brightness.c_str(), brightness.length());
       fs.close();
}

// Generates the "cylon" blink pattern.
void cylon(const llist_t& leds, int& state)
{
       int led = state_to_led(state);

       if (led == 0)
       {
              // Turn off all LEDS on "0", i.e. initialize.
              for (const auto& led: leds)
                     led_off(led);
       }
       else
              led_off(leds[led]);

       led_on(leds[state_to_led(++state)]);
       
       return;
}

// Clean up and exit.
void do_exit(int signum)
{
       for (const auto& led: leds)
              led_off(led);
       restore_trigs(pretrigs);
       exit(0);
}


int main(int argc, char** argv)
{
       // Capture the list of leds.
       for (int n = 0; n < sizeof(led_list) / sizeof(*led_list); n++)
              leds.push_back(std::string(led_list[n]));
        
       // Capture their original trigger settings.
       read_trigs(leds, pretrigs);
       // for (const auto &led: pretrigs)
       //    std::cout << led.first << ": " << led.second << std::endl;

       // Clean things up on exit.
       signal(SIGINT, do_exit);
       signal(SIGKILL, do_exit);
       signal(SIGHUP, do_exit);

       // Turn off all led triggers, so that we can drive them ourselves.
       clear_trigs(leds);

       // Run the "cylon" blink pattern.
       int state = 0;
       while (true)
       {
              cylon(leds, state);
              usleep(125000);
       }

       do_exit(0);
       
       return 0;        
}
