// snfGBUdbmgr.cpp
// Copyright (C) 2006 - 2009 ARM Research Labs, LLC.
// See www.armresearch.com for the copyright terms.
//
// See snfGBUdbmgr.hpp for details.

#include "snfGBUdbmgr.hpp"
#include <unistd.h>

using namespace std;

const ThreadType snfGBUdbmgr::Type("snfGBUdbmgr");                              // The thread's type.

snfGBUdbmgr::snfGBUdbmgr() :                                                    // Clean init and start thread.
  Thread(snfGBUdbmgr::Type, "GBUdb Manager"),                                   // XCI Manager type and Name.
  CondenseGuardTime(600000),                                                    // 10 minute guard time by default.
  TimeTriggerOnOff(true),                                                       // By default, condense once per day.
  TimeTrigger(84600000),
  PostsTriggerOnOff(false),                                                     // By default do not trigger on posts.
  PostsTriggerValue(262144),                                                    // but if we do, use a quarter million.
  RecordsTriggerOnOff(false),                                                   // By default do not trigger on records.
  RecordsTriggerValue(150000),                                                  // but if we do, use 150K.
  SizeTriggerOnOff(true),                                                       // By default trigger on size as a
  SizeTriggerValue(150),                                                        // safety valve at 150Mbytes.
  CheckpointOnOff(true),                                                        // By default save a snapshot once
  CheckpointTrigger(3600000),                                                   // every hour.
  MyGBUdb(NULL),                                                                // NULL our links to avoid
  MyLOGmgr(NULL),                                                               // any errors when the thread starts.
  TimeToStop(false) {                                                           // It is not time to stop ;-)
    run();                                                                      // Start our thread.
}

snfGBUdbmgr::~snfGBUdbmgr() {                                                   // Clean shutdown & stop thread.
    stop();                                                                     // Stop the thread if it's not already.
    MyGBUdb = NULL;                                                             // NULL our links and false our
    MyLOGmgr = NULL;                                                            // configuration for safety.
    Configured = false;
}

void snfGBUdbmgr::linkGBUdb(GBUdb& G) {                                         // Connect to our GBUdb
    ScopeMutex JustMe(MyMutex);                                                 // Lock for the config change.
    MyGBUdb = &G;                                                               // Set the new link.
}

void snfGBUdbmgr::linkLOGmgr(snfLOGmgr& L) {                                    // Connect to our LOGmgr
    ScopeMutex JustMe(MyMutex);                                                 // Lock for the config change.
    MyLOGmgr = &L;                                                              // Set the new link.
}

void snfGBUdbmgr::configure(snfCFGData& CFGData) {                              // Establish or change our CFG.
    ScopeMutex JustMe(MyMutex);                                                 // Only when we're not busy.

    // Set up our configuration from the CFGData provided.

    // Being careful not to muck with running timers unless their
    // configuration values have actually changed...

    const int SECsASms = 1000;                                                  // How to convert seconds to milliseconds.

    if(CondenseGuardTime.getDuration() !=                                       // If the condensation guard time is
      (SECsASms * CFGData.gbudb_database_condense_minimum_seconds_between)) {   // new and different then set the
        CondenseGuardTime.setDuration(                                          // condensation guard timer to the
          (SECsASms * CFGData.gbudb_database_condense_minimum_seconds_between)  // new value.
        );
    }

    TimeTriggerOnOff = CFGData.gbudb_database_condense_time_trigger_on_off;     // Time-Trigger On?

    if(TimeTrigger.getDuration() !=                                             // Time-Trigger different?
      (SECsASms * CFGData.gbudb_database_condense_time_trigger_seconds)) {
        TimeTrigger.setDuration(                                                // If it is then adopt the new value.
          SECsASms * CFGData.gbudb_database_condense_time_trigger_seconds
        );
    }

    PostsTriggerOnOff = CFGData.gbudb_database_condense_posts_trigger_on_off;       // Posts trigger on?
    PostsTriggerValue = CFGData.gbudb_database_condense_posts_trigger_posts;        // What is the posts trigger threshold?

    RecordsTriggerOnOff = CFGData.gbudb_database_condense_records_trigger_on_off;   // Records trigger on?
    RecordsTriggerValue = CFGData.gbudb_database_condense_records_trigger_records;  // What is the records trigger threshold?

    SizeTriggerOnOff = CFGData.gbudb_database_condense_size_trigger_on_off;         // Size trigger on?
    SizeTriggerValue = CFGData.gbudb_database_condense_size_trigger_megabytes;      // What is the size trigger threshold?

    // Checkpoint

    CheckpointOnOff = CFGData.gbudb_database_checkpoint_on_off;                     // Checkpoint on?

    if(CheckpointTrigger.getDuration() !=                                           // If the Checkpoint time is
      (SECsASms * CFGData.gbudb_database_checkpoint_secs)) {                        // new and different then
        CheckpointTrigger.setDuration(                                              // adopt the new value.
          (SECsASms * CFGData.gbudb_database_checkpoint_secs)
        );
    }

    // GBUdb file name

    string GBUdbFileName;                                                       // Formulate the correct GBUdb file name
    GBUdbFileName = CFGData.paths_workspace_path +                              // using the CFGData.
      CFGData.node_licenseid + ".gbx";

    if(                                                                         // If the file name for our GBUdb
      NULL == (*MyGBUdb).FileName() ||                                          // is not yet set, or
      0 != GBUdbFileName.compare((*MyGBUdb).FileName())                         // if it is different than the
      ) {                                                                       // formulated file name we have then
        (*MyGBUdb).FileName(GBUdbFileName.c_str());                             // set the GBUdb file name.
    }

    // Safety check to set the Configured bit.

    if(NULL != MyGBUdb && NULL != MyLOGmgr) {                                   // If we have all of our parts
        Configured = true;                                                      // then set our configured flag.
    } else {                                                                    // If anything is missing then
        Configured = false;                                                     // make sure the flag is false.
    }
}

//// The snfGBUdbmgr::load() method isn't exactly what you would expect. It
// will load the rulebase file if that file exists, but if not it does nothing.
// The intention is that a new GBUdb will alread have been created. If a
// pre-existing GBUdb is available then that one will be loaded for use. If
// it does not exist, then the new, empty GBUdb will be used instead and will
// eventually be saved for later re-use.

void snfGBUdbmgr::load() {                                                      // Load the GBUdb as configured.
    ScopeMutex JustMe(MyMutex);                                                 // Just me while I do this.
    if(                                                                         // Perform some sanity checks.
      NULL != MyGBUdb &&                                                        // If we have a GBUdb and
      0 < string(MyGBUdb->FileName()).length() &&                               // it has a file name and
      0 == access(MyGBUdb->FileName(),R_OK)                                     // the file can be accessed
      ) {                                                                       // then we can proceed:
        MyGBUdb->load();                                                        // Load the GBUdb from disk.
    }                                                                           // If that didn't work we'll assume
}                                                                               // we're starting up a new gbx file ;-)

// DoMaintenanceWork encapsulates all of our maintenance functions. It runs
// with the mutex locked so that the configuration is stable during each pass.

void snfGBUdbmgr::DoMaintenanceWork() {                                         // Do our watchdog work.

    if(!Configured) return;                                                     // Do nothing if we're not configured.

    ScopeMutex JustMe(MyMutex);                                                 // No CFG changes while I'm busy.

    if(CondenseGuardTime.isExpired()) {                                         // If we are allowed to condense
        bool CondenseTriggered = false;                                         // check to see if we should.

        // time-trigger

        if(
          TimeTriggerOnOff &&                                                   // If the time-trigger is on
          TimeTrigger.isExpired()                                               // and the time has expired
          ) {                                                                   // then it is time to condense.
            CondenseTriggered = true;                                           // Set the condense flag and
            TimeTrigger.restart();                                              // restart the timer.
        }

        // posts-trigger

        if(
          PostsTriggerOnOff &&                                                  // If posts-trigger is on
          (*MyGBUdb).Posts() >= PostsTriggerValue                               // and the Posts() count is high
          ) {                                                                   // enough then trigger the
            CondenseTriggered = true;                                           // condense operation.
        }

        // records-trigger

        if(
          RecordsTriggerOnOff &&                                                // If records-trigger is on
          (*MyGBUdb).IPCount() >= RecordsTriggerValue                           // and the number of IPs is high
          ) {                                                                   // enough then trigger the
            CondenseTriggered = true;                                           // condense operation.
        }

        // size-trigger

        const int MByte = 1048576;                                              // How big is a megabyte anyway?

        if(
          SizeTriggerOnOff &&                                                   // If size-trigger is on
          ((*MyGBUdb).Size()/MByte) >= SizeTriggerValue                         // and the size of the db is high
          ) {                                                                   // enough then trigger
            CondenseTriggered = true;                                           // the condense operation.
        }

        if(CondenseTriggered) {                                                 // If we need to condense then
            (*MyGBUdb).reduce();                                                // reduce all counts in the db
            (*MyGBUdb).compress();                                              // and elminate any that drop to zero.
            CondenseGuardTime.restart();                                        // That done, reset the guard timer.
            (*MyLOGmgr).RecordCondenseEvent();                                  // Log the event.
        }
    }

    // Time to save a snapshot?

    if(
      CheckpointOnOff &&                                                        // If checkpoints are turned on
      CheckpointTrigger.isExpired()                                             // and it is time to create one
      ) {
        (*MyGBUdb).saveSnapshot();                                              // then save a snapshot and
        CheckpointTrigger.restart();                                            // restart the timer.
        (*MyLOGmgr).RecordSaveEvent();                                          // Log the event.
    }
}

// Stopping the thread...

void snfGBUdbmgr::stop() {                                                      // To stop the manager thread we
    if(!TimeToStop) {                                                           // check to see we need to then
        TimeToStop = true;                                                      // set the time to stop flag
        join();                                                                 // and join the thread.
    }
}

// The thread's task is to call DoMaintenanceWork() once every second.

void snfGBUdbmgr::myTask() {                                                    // This is what our thread does.
    Sleeper WaitATic(1000);                                                     // We need a 1 second sleeper.
    while(!TimeToStop) {                                                        // While it's not time to stop
        WaitATic();                                                             // wait a tic and then do work.
        DoMaintenanceWork();
    }
}

void snfGBUdbmgr::GetAlertsForSync(list<GBUdbAlert>& AlertList) {               // Fill AlertList w/ outgoing alerts.
    (*MyGBUdb).GetAlerts(AlertList);                                            // For now, just pass this through.
}

void snfGBUdbmgr::ProcessReflections(list<GBUdbAlert>& Reflections) {           // Integrate returning reflections.
    (*MyGBUdb).ImportAlerts(Reflections);                                       // For now, just pass this through.
}
