////////////////////////////////////////////////////////////////////////////////
//
// EftAnalyzer.cpp - Embedded Figures Test Results Analyzer
// Copyright (C) 2008 Alan G. Carter
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program 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 General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
//
////////////////////////////////////////////////////////////////////////////////

#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

#include <math.h>

#include <map>
#include <cctype>
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;

////////////////////////////////////////////////////////////////////////////////

enum
{
    ID_QUIT = 1,
    ID_OPEN
};

typedef enum
{
   GRAPH_DISTRIB_BOTH,
   GRAPH_DISTRIB_NAUSEOUS,
   GRAPH_DISTRIB_NON_NAUSEOUS,
   GRAPH_DISTRIB_DRUG,
   GRAPH_STRESSORS_BY_AV_SCORE,
   GRAPH_EXERCISES_BY_AV_SCORE,
   GRAPH_AGE_BY_AV_SCORE
}  GraphType;

typedef enum
{
   DRUG_SSRI = 0,
   DRUG_BENZODIAZEPINES,
   DRUG_RITALIN,
   DRUG_ALCOHOL,
   DRUG_TOBACCO,
   DRUG_MARIJUANA,
   DRUG_COCAINE,
   DRUG_MDMA,
   DRUG_CAFFEINE,
   DRUG_MAX
}  DrugType;

////////////////////////////////////////////////////////////////////////////////

class EftApp;
class StatsFrame;
class Display;
class GraphFrame;
class GraphPanel;
class DistribPanel;
class DrugDistribPanel;
class StressorsByAvScorePanel;
class ExercisesByAvScorePanel;
class AgeByAvScorePanel;
class Entry;

////////////////////////////////////////////////////////////////////////////////

class EftApp: public wxApp
{
   public:
   
   virtual bool OnInit();
};

////////////////////////////////////////////////////////////////////////////////

class StatsFrame: public wxFrame
{
   public:

         StatsFrame(void);
   void  OnQuit(wxCommandEvent &);
   void  OnOpen(wxCommandEvent &);
   void  DeregisterFrame(wxFrame *TheFrame);

   private:
   
   void     Tokenize(const string &Data,
                     const string &Delimiters,
                     vector<string> &Tokens);
   void     Purge(void);
   void     Parse(const string &Line);
   int      EncodeChoice(string TheChoice);
   void     AddReport(string ThePrompt, Display *&TheDisplay);

   vector<wxFrame *>       Frames;
   map<string, Entry *>    Entries;
   wxGridSizer             *GridSizer;
   Display                 *TotalDisplay;
   Display                 *ValidDisplay;
   Display                 *SinglesDisplay;
   Display                 *PairsDisplay;
   Display                 *GeeksDisplay;
   Display                 *NonGeeksDisplay;
   Display                 *MalesDisplay;
   Display                 *NonMalesDisplay;
   Display                 *NauseatorsDisplay;
   Display                 *NonNauseatorsDisplay;
   Display                 *DrugDisplays[DRUG_MAX];

   DECLARE_EVENT_TABLE()
};

////////////////////////////////////////////////////////////////////////////////

class Display : public wxTextCtrl
{
   public:
            Display(StatsFrame *Parent);
   void     SetInt(int Value);
};

////////////////////////////////////////////////////////////////////////////////

class GraphFrame: public wxFrame
{
   public:

         GraphFrame(StatsFrame *TheParent,
                    GraphType TheType,
                    map<string, Entry *> *TheData);
   void  OnClose(wxCloseEvent& Event);

   private:
   
   StatsFrame  *Parent;
   wxPanel     *Panel;

   DECLARE_EVENT_TABLE()
};

////////////////////////////////////////////////////////////////////////////////

class GraphPanel: public wxPanel
{
   public:
                  GraphPanel(wxFrame* Parent, map<string, Entry *> *TheData);
   void           ChooseColoursDrawAxes(wxDC &Dc);
   void           DrawScoreScaleY(wxDC &Dc, bool Both);
   virtual void   OnPaint(wxPaintEvent &Event) = 0;
            
   protected:

   map<string, Entry *> *Data;

   DECLARE_EVENT_TABLE()
};

////////////////////////////////////////////////////////////////////////////////

class DistribPanel: public GraphPanel
{
   public:
   
         DistribPanel(wxFrame* Parent,
                      map<string, Entry *> *TheData,
                      GraphType TheType);
   void  OnPaint(wxPaintEvent &Event);
   
   private:
   
   GraphType   Type;
};

////////////////////////////////////////////////////////////////////////////////

class DrugDistribPanel: public GraphPanel
{
   public:
   
         DrugDistribPanel(wxFrame* Parent, map<string, Entry *> *TheData);
   void  OnPaint(wxPaintEvent &Event);
};

////////////////////////////////////////////////////////////////////////////////

class StressorsByAvScorePanel: public GraphPanel
{
   public:
   
         StressorsByAvScorePanel(wxFrame* Parent, map<string, Entry *> *TheData);
   void  OnPaint(wxPaintEvent &Event);
};

////////////////////////////////////////////////////////////////////////////////

class ExercisesByAvScorePanel: public GraphPanel
{
   public:
   
         ExercisesByAvScorePanel(wxFrame* Parent, map<string, Entry *> *TheData);
   void  OnPaint(wxPaintEvent &Event);
};

////////////////////////////////////////////////////////////////////////////////

class AgeByAvScorePanel: public GraphPanel
{
   public:
   
         AgeByAvScorePanel(wxFrame* Parent, map<string, Entry *> *TheData);
   void  OnPaint(wxPaintEvent &Event);
};

////////////////////////////////////////////////////////////////////////////////

class Entry
{
   public:
            Entry(void);
   int      GetStressScore(void);
   int      GetExerciseScore(void);
   
   string   Id;
   bool     IsMale;
   int      AgeGroup;
   string   Occupation;
   bool     IsGeek;
   
   bool     EveningWalks;
   bool     CulturalActivities;
   bool     ImaginaryFriend;
   bool     History;
   bool     Cooking;
   bool     Meditation;
   bool     ChangeRoutes;
   bool     ChangeJob;
   bool     MoveHome;
   bool     FallInLove;
   bool     BreakUp;
   bool     NewCar;
   bool     Vacation;
   bool     SeeOldFriends;
   bool     GetMoreSleep;
   bool     Disaster;
   
   int      IEnjoyMyJob;
   int      MyJobIsWellDefined;
   int      MyCoworkersAreCooperative;
   int      MyWorkplaceIsStressful;
   int      MyJobIsStressful;
   int      MoraleIsGoodWhereIWork;
   int      IGetFrustratedAtWork;
   bool     IsNauseous;
   
   bool     Drugs[DRUG_MAX];

   string   OtherMeds;
   string   OtherDrugs;
   
   int      Before;
   int      After;
};

////////////////////////////////////////////////////////////////////////////////
//
// Constants
//
////////////////////////////////////////////////////////////////////////////////

wxColour *DrugColours[DRUG_MAX];
string   DrugNames[DRUG_MAX];

////////////////////////////////////////////////////////////////////////////////
//
// Class EftApp
//
////////////////////////////////////////////////////////////////////////////////

IMPLEMENT_APP(EftApp)

bool EftApp::OnInit()
{
   StatsFrame *Frame = new StatsFrame();

   Frame->Show(true);
   SetTopWindow(Frame);
    
   return true;
}

////////////////////////////////////////////////////////////////////////////////
//
// Class StatsFrame
//
////////////////////////////////////////////////////////////////////////////////

BEGIN_EVENT_TABLE(StatsFrame, wxFrame)
   EVT_MENU (ID_QUIT, StatsFrame::OnQuit)
   EVT_MENU (ID_OPEN, StatsFrame::OnOpen)
END_EVENT_TABLE()

////////////////////////////////////////////////////////////////////////////////

StatsFrame::StatsFrame(void) :
   wxFrame(0, wxID_ANY,"EFT Analyzer", wxDefaultPosition, wxSize(600, 450))
{
   DrugColours[DRUG_SSRI]            = new wxColour(  0,   0, 255);
   DrugColours[DRUG_BENZODIAZEPINES] = new wxColour(  0, 255,   0);
   DrugColours[DRUG_RITALIN]         = new wxColour(255,   0,   0);
   DrugColours[DRUG_ALCOHOL]         = new wxColour(255, 255,   0);
   DrugColours[DRUG_TOBACCO]         = new wxColour(255,   0, 255);
   DrugColours[DRUG_MARIJUANA]       = new wxColour(  0, 255, 255);
   DrugColours[DRUG_COCAINE]         = new wxColour(  0,   0, 127);
   DrugColours[DRUG_MDMA]            = new wxColour(  0, 127,   0);
   DrugColours[DRUG_CAFFEINE]        = new wxColour(127,   0,   0);

   DrugNames[DRUG_SSRI]            = "SSRIs";
   DrugNames[DRUG_BENZODIAZEPINES] = "Benzodiazepines"; 
   DrugNames[DRUG_RITALIN]         = "Ritalin";
   DrugNames[DRUG_ALCOHOL]         = "Alcohol";
   DrugNames[DRUG_TOBACCO]         = "Tobacco";
   DrugNames[DRUG_MARIJUANA]       = "Marijuana";
   DrugNames[DRUG_COCAINE]         = "Cocaine";
   DrugNames[DRUG_MDMA]            = "MDMA";
   DrugNames[DRUG_CAFFEINE]        = "Caffeine";

   wxMenu *MenuFile = new wxMenu;
   MenuFile->Append(ID_OPEN, "Open...");
   MenuFile->Append(ID_QUIT, "Exit");

   wxMenuBar *MenuBar = new wxMenuBar;
   MenuBar->Append(MenuFile, "File");

   SetMenuBar(MenuBar);
   
   GridSizer = new wxGridSizer(2, 0, 5);
    
   AddReport("Total Entries", TotalDisplay);
   AddReport("Valid Entries", ValidDisplay);
   AddReport("Single Values", SinglesDisplay);
   AddReport("Paired Values", PairsDisplay);
   AddReport("Geeks", GeeksDisplay);
   AddReport("Non Geeks", NonGeeksDisplay);
   AddReport("Male", MalesDisplay);
   AddReport("Female", NonMalesDisplay);
   AddReport("Nauseators", NauseatorsDisplay);
   AddReport("Non Nauseators", NonNauseatorsDisplay);

   for(unsigned int Count = 0; Count < DRUG_MAX; Count++)
      AddReport(DrugNames[Count].c_str(), DrugDisplays[Count]);

   SetSizer(GridSizer);
   GridSizer->SetSizeHints(this);   

   Frames.push_back(new GraphFrame(this, GRAPH_DISTRIB_BOTH,          &Entries));
   Frames.push_back(new GraphFrame(this, GRAPH_DISTRIB_NAUSEOUS,      &Entries));
   Frames.push_back(new GraphFrame(this, GRAPH_DISTRIB_NON_NAUSEOUS,  &Entries));
   Frames.push_back(new GraphFrame(this, GRAPH_DISTRIB_DRUG,          &Entries));
   Frames.push_back(new GraphFrame(this, GRAPH_STRESSORS_BY_AV_SCORE, &Entries));
   Frames.push_back(new GraphFrame(this, GRAPH_EXERCISES_BY_AV_SCORE, &Entries));
   Frames.push_back(new GraphFrame(this, GRAPH_AGE_BY_AV_SCORE,       &Entries));
}

////////////////////////////////////////////////////////////////////////////////

void StatsFrame::OnOpen(wxCommandEvent &)
{
   // Clear the current data structures.
   
   Purge();

   // Pick a file to open.

   wxFileDialog FileDialog(this, "EFT Results File", "", "", "*.txt");
   if(FileDialog.ShowModal() != wxID_OK) return;
   
   // Get the user's filename.

   string FileName = FileDialog.GetPath().c_str(); 
   if(FileName.length() == 0) return;

   // Open the file.
   
   ifstream FileStream(FileName.c_str());
   if(FileStream == 0) return;

   // Throw away the first line.

   string Line;
                        
   if(!getline(FileStream, Line)) return;

   // Read and parse the remaining lines.
   
   while(getline(FileStream, Line))
      Parse(Line);
   
   // Report the total number of entries - an entry == many lines.

   ostringstream FormatBuffer;
   
   FormatBuffer << Entries.size();
   TotalDisplay->SetValue(FormatBuffer.str());
   
   // Now remove invalid entries. Valid entries have values:
   //
   //    100 < Before < 10000
   //      0 <= After < 10000
   //
   // After may be 0 to indicate no value.
   
   int ValidCount = 0;
   int SinglesCount = 0;
   int PairsCount = 0;
   int GeeksCount = 0;
   int NonGeeksCount = 0;
   int MalesCount = 0;
   int NonMalesCount = 0;
   int NauseatorsCount = 0;
   int NonNauseatorsCount = 0;
   int DrugCounts[DRUG_MAX];

   for(unsigned int Count = 0; Count < DRUG_MAX; Count++)
      DrugCounts[Count] = 0;

   for(map<string, Entry *>::iterator It = Entries.begin();
       It != Entries.end();
       It++)
   {
      if(It->second->Before <= 100 || It->second->Before > 9999 ||
         It->second->After  <    0 || It->second->After  > 9999)
      {
         delete It->second;
         Entries.erase(It);
      }
      else
      {
         ValidCount++;

         if(It->second->After == 0) SinglesCount++;
         else PairsCount++;

         if(It->second->IsGeek) GeeksCount++;
         else NonGeeksCount++;
         
         if(It->second->IsMale) MalesCount++;
         else NonMalesCount++;

         if(It->second->IsNauseous) NauseatorsCount++; 
         else NonNauseatorsCount++;

         for(unsigned int Count = 0; Count < DRUG_MAX; Count++)
            if(It->second->Drugs[Count])
               DrugCounts[Count]++;
      }
   }
   
   // Report the other statistics.
   
   ValidDisplay->SetInt(ValidCount);
   SinglesDisplay->SetInt(SinglesCount);
   PairsDisplay->SetInt(PairsCount);
   GeeksDisplay->SetInt(GeeksCount);
   NonGeeksDisplay->SetInt(NonGeeksCount);
   MalesDisplay->SetInt(MalesCount);
   NonMalesDisplay->SetInt(NonMalesCount);
   NauseatorsDisplay->SetInt(NauseatorsCount);
   NonNauseatorsDisplay->SetInt(NonNauseatorsCount);
   
   for(unsigned int Count = 0; Count < DRUG_MAX; Count++)
      DrugDisplays[Count]->SetInt(DrugCounts[Count]);

      // With new entries, refresh all existing frames.
   
   for(unsigned int Count = 0; Count < Frames.size(); Count++)
      Frames[Count]->Refresh();

   Refresh();
}

////////////////////////////////////////////////////////////////////////////////

void StatsFrame::OnQuit(wxCommandEvent &)
{
   for(unsigned int Count = 0; Count < Frames.size(); Count++)
      Frames[Count]->Close();

   Close(true);
}

////////////////////////////////////////////////////////////////////////////////

void StatsFrame::DeregisterFrame(wxFrame *TheFrame)
{
   Frames.erase(remove(Frames.begin(), Frames.end(), TheFrame), Frames.end());
}

////////////////////////////////////////////////////////////////////////////////

void StatsFrame::Tokenize(const string &Data,
                          const string &Delimiters,
                          vector<string> &Tokens)
{
   // Clear any previous use.
   
   Tokens.clear();

   // Skip delimiters at beginning.
   
   string::size_type LastPos = Data.find_first_not_of(Delimiters, 0);
      
   // Find first "non-delimiter".
   
   string::size_type Pos = Data.find_first_of(Delimiters, LastPos);

   while(string::npos != Pos || string::npos != LastPos)
   {
      // found a token, add it to the vector.
         
      Tokens.push_back(Data.substr(LastPos, Pos - LastPos));
      
      // skip delimiters.  Note the "not_of"
      
      LastPos = Data.find_first_not_of(Delimiters, Pos);
      
      // find next "non-delimiter"
      
      Pos = Data.find_first_of(Delimiters, LastPos);
   }
}

////////////////////////////////////////////////////////////////////////////////

void StatsFrame::Purge(void)
{
   for(map<string, Entry *>::iterator It = Entries.begin();
       It != Entries.end();
       It++)
      delete It->second;
      
   Entries.clear();
}

////////////////////////////////////////////////////////////////////////////////

void StatsFrame::Parse(const string &Line)
{
   vector<string> Tokens;
   
   Tokenize(Line, "\t", Tokens);
   
   // The fourth field contains the value. We don't need to do anything if the
   // value isn't there!
   
   if(Tokens.size() != 4) return;
   
   // The first field is a unique line ID, which we don't need. The second is
   // the record ID the line belongs to. See if we have a record in the map<>,
   // and if not create one.
   
   if(Entries.find(Tokens[1]) == Entries.end())
      Entries[Tokens[1]] = new Entry();
   
   Entry *TheEntry = Entries[Tokens[1]];
   
   // The third field is the question. The questions are collected into groups
   // whose values (in the fourth field) are handled in different ways.
   
   if(Tokens[2] == "Gender")
   {
      TheEntry->IsMale = Tokens[3] == "Male";
   }
   else if(Tokens[2] == "Age")
   {
      if(Tokens[3] == "0 - 9")        TheEntry->AgeGroup = 0;
      else if(Tokens[3] == "10 - 19") TheEntry->AgeGroup = 1;
      else if(Tokens[3] == "20 - 29") TheEntry->AgeGroup = 2;
      else if(Tokens[3] == "30 - 39") TheEntry->AgeGroup = 3;
      else if(Tokens[3] == "40 - 49") TheEntry->AgeGroup = 4;
      else if(Tokens[3] == "50 - 59") TheEntry->AgeGroup = 5;
      else if(Tokens[3] == "60 - 69") TheEntry->AgeGroup = 6;
      else if(Tokens[3] == "70 - 79") TheEntry->AgeGroup = 7;
      else if(Tokens[3] == "80 - 89") TheEntry->AgeGroup = 8;
      else if(Tokens[3] == "90 - 99") TheEntry->AgeGroup = 9;
      else if(Tokens[3] == "100+")    TheEntry->AgeGroup = 10;
   }
   else if(Tokens[2] == "Occupation")
   {
      string Occ = TheEntry->Occupation = Tokens[3];

      // Normalize the occupations to lower case.
      
      for(unsigned int Count = 0; Count < Occ.length(); Count++)
         Occ[Count] = tolower(Occ[Count]);
      
      // Look for geek implying substrings. Not ideal but a guess.

      if(Occ.find("software") != string::npos ||
         Occ.find("programm") != string::npos ||
         Occ.find("system") != string::npos ||
         Occ.find("sysadmin") != string::npos ||
         Occ.find("sysadm") != string::npos ||
         Occ.find("devel") != string::npos ||
         Occ.find("engr") != string::npos ||
         Occ.find("sd") != string::npos ||
         Occ.find("engineer") != string::npos ||
         Occ.find("computer") != string::npos ||
         Occ.find("mathemat") != string::npos ||
         Occ.find("comp-sci") != string::npos ||
         Occ.find("csstudent") != string::npos ||
         Occ.find("dba") != string::npos ||
         Occ.find("web") != string::npos)
         TheEntry->IsGeek = true;
   }
   else if(Tokens[2] == "New Activities")
   {
      if(Tokens[3].find("Evening Walks") != string::npos)
         TheEntry->EveningWalks = true;
      if(Tokens[3].find("Cultural Activities") != string::npos)
         TheEntry->CulturalActivities = true;
      if(Tokens[3].find("Imaginary Friend") != string::npos)
         TheEntry->ImaginaryFriend = true;
      if(Tokens[3].find("History") != string::npos)
         TheEntry->History = true;
      if(Tokens[3].find("Cooking") != string::npos)
         TheEntry->Cooking = true;
      if(Tokens[3].find("Meditation") != string::npos)
         TheEntry->Meditation = true;
      if(Tokens[3].find("Change Routes") != string::npos)
         TheEntry->ChangeRoutes = true;
      if(Tokens[3].find("Change Job") != string::npos)
         TheEntry->ChangeJob = true;
      if(Tokens[3].find("Move Home") != string::npos)
         TheEntry->MoveHome = true;
      if(Tokens[3].find("Fall In Love") != string::npos)
         TheEntry->FallInLove = true;
      if(Tokens[3].find("Break Up") != string::npos)
         TheEntry->BreakUp = true;
      if(Tokens[3].find("New Car") != string::npos)
         TheEntry->NewCar = true;
      if(Tokens[3].find("Vacation") != string::npos)
         TheEntry->Vacation = true;
      if(Tokens[3].find("See Old Friends") != string::npos)
         TheEntry->SeeOldFriends = true;
      if(Tokens[3].find("Get More Sleep") != string::npos)
         TheEntry->GetMoreSleep = true;
      if(Tokens[3].find("Disaster") != string::npos)
         TheEntry->Disaster = true;
   }
   else if(Tokens[2] == "I Enjoy My Job")
      TheEntry->IEnjoyMyJob = EncodeChoice(Tokens[3]);
   else if(Tokens[2] == "My Job Is Well Defined")
      TheEntry->MyJobIsWellDefined = EncodeChoice(Tokens[3]);
   else if(Tokens[2] == "My Co-workers Are Co-operative")
      TheEntry->MyCoworkersAreCooperative = EncodeChoice(Tokens[3]);
   else if(Tokens[2] == "My Workplace Is Stressful")
      TheEntry->MyWorkplaceIsStressful = EncodeChoice(Tokens[3]) * -1;
   else if(Tokens[2] == "My Job Is Stressful")
      TheEntry->MyJobIsStressful = EncodeChoice(Tokens[3]) * -1;
   else if(Tokens[2] == "Morale Is Good Where I Work")
      TheEntry->MoraleIsGoodWhereIWork = EncodeChoice(Tokens[3]);
   else if(Tokens[2] == "I Get Frustrated At Work")
      TheEntry->IGetFrustratedAtWork = EncodeChoice(Tokens[3]) * -1;
   else if(Tokens[2] == "I Feel Nauseous When Very Bored")
      TheEntry->IsNauseous = (EncodeChoice(Tokens[3]) > 0);
   else if(Tokens[2] == "Prescription Meds" ||
           Tokens[2] == "Non-Prescription Drugs")
   {
      for(unsigned int Count = 0; Count < DRUG_MAX; Count++)
         if(Tokens[3].find(DrugNames[Count]) != string::npos)
            TheEntry->Drugs[Count] = true;
   }
   else if(Tokens[2] == "Other Meds")
      TheEntry->OtherMeds = Tokens[3];
   else if(Tokens[2] == "Other Drugs")
   {
      string Temp = TheEntry->OtherDrugs = Tokens[3];
      for(unsigned int Count = 0; Count < Temp.length(); Count++)
         Temp[Count] = tolower(Temp[Count]);

      if(Temp.find("caff") != string::npos)
         TheEntry->Drugs[DRUG_CAFFEINE] = true;
   }
   else if(Tokens[2] == "Before")
      TheEntry->Before = atoi(Tokens[3].c_str());
   else if(Tokens[2] == "After")
      TheEntry->After = atoi(Tokens[3].c_str());
}

////////////////////////////////////////////////////////////////////////////////

int StatsFrame::EncodeChoice(string TheChoice)
{
   if(TheChoice == "Strongly Agree")    return  2;
   if(TheChoice == "Agree")             return  1;
   if(TheChoice == "Disagree")          return -1;
   if(TheChoice == "Strongly Disagree") return -2;

   // Placate compiler
   
   return 0;
}

////////////////////////////////////////////////////////////////////////////////

void StatsFrame::AddReport(string ThePrompt, Display *&TheDisplay)
{
   GridSizer->Add(new wxStaticText(this, wxID_ANY, ThePrompt.c_str()));
   GridSizer->Add(TheDisplay = new Display(this));
}

////////////////////////////////////////////////////////////////////////////////
//
// Class Display
//
////////////////////////////////////////////////////////////////////////////////

Display::Display(StatsFrame *Parent) :
   wxTextCtrl(Parent,
              wxID_ANY,
              "0",
              wxDefaultPosition,
              wxDefaultSize,
              wxTE_READONLY | wxTE_RIGHT)
{
}

////////////////////////////////////////////////////////////////////////////////

void Display::SetInt(int Value)
{
   ostringstream Buffer;
   
   Buffer << Value;
   SetValue(Buffer.str());
}

////////////////////////////////////////////////////////////////////////////////
//
// Class GraphFrame
//
////////////////////////////////////////////////////////////////////////////////

BEGIN_EVENT_TABLE(GraphFrame, wxFrame)
   EVT_CLOSE(GraphFrame::OnClose)
END_EVENT_TABLE()

////////////////////////////////////////////////////////////////////////////////

GraphFrame::GraphFrame(StatsFrame *TheParent,
                       GraphType TheType,
                       map<string, Entry *> *TheData) :
   wxFrame(TheParent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize)
{
   Parent = TheParent;
   
   wxBoxSizer *TopSizer = new wxBoxSizer(wxVERTICAL);
   
   if(TheType == GRAPH_DISTRIB_BOTH)
      TopSizer->Add(new DistribPanel(this, TheData, GRAPH_DISTRIB_BOTH), 1, wxEXPAND);
   else if(TheType == GRAPH_DISTRIB_NAUSEOUS)
      TopSizer->Add(new DistribPanel(this, TheData, GRAPH_DISTRIB_NAUSEOUS), 1, wxEXPAND);
   else if(TheType == GRAPH_DISTRIB_NON_NAUSEOUS)
      TopSizer->Add(new DistribPanel(this, TheData, GRAPH_DISTRIB_NON_NAUSEOUS), 1, wxEXPAND);
   else if(TheType == GRAPH_DISTRIB_DRUG)
      TopSizer->Add(new DrugDistribPanel(this, TheData), 1, wxEXPAND);
   else if(TheType == GRAPH_STRESSORS_BY_AV_SCORE)
      TopSizer->Add(new StressorsByAvScorePanel(this, TheData), 1, wxEXPAND);
   else if(TheType == GRAPH_EXERCISES_BY_AV_SCORE)
      TopSizer->Add(new ExercisesByAvScorePanel(this, TheData), 1, wxEXPAND);
   else if(TheType == GRAPH_AGE_BY_AV_SCORE)
      TopSizer->Add(new AgeByAvScorePanel(this, TheData), 1, wxEXPAND);
   
   SetSizer(TopSizer);
   
   TopSizer->SetSizeHints(this);
   
   Show();
}

////////////////////////////////////////////////////////////////////////////////

void GraphFrame::OnClose(wxCloseEvent &Event)
{
   Parent->DeregisterFrame(this);
   Destroy();
}

////////////////////////////////////////////////////////////////////////////////
//
// Class GraphPanel
//
////////////////////////////////////////////////////////////////////////////////

BEGIN_EVENT_TABLE(GraphPanel, wxPanel)
   EVT_PAINT(GraphPanel::OnPaint)
END_EVENT_TABLE()

////////////////////////////////////////////////////////////////////////////////

GraphPanel::GraphPanel(wxFrame* Parent, map<string, Entry *> *TheData) :
   wxPanel(Parent, wxID_ANY, wxDefaultPosition, wxSize(600, 600))
{
   Data = TheData;
   
   SetBackgroundColour(wxColour(255, 255, 255));
   ClearBackground();
}

////////////////////////////////////////////////////////////////////////////////

void GraphPanel::ChooseColoursDrawAxes(wxDC &Dc)
{
   Dc.SetPen(*wxBLACK_PEN);
   Dc.SetBrush(*wxGREY_BRUSH);
   Dc.SetTextForeground(*wxBLACK);

   Dc.DrawLine(100, 500, 600, 500);
   Dc.DrawLine(100,   0, 100, 500);
}

////////////////////////////////////////////////////////////////////////////////

void GraphPanel::DrawScoreScaleY(wxDC &Dc, bool Both)
{
   int Offset;
   ostringstream Buffer;
   
   for(int Count = 0; Count < 10; Count++)
   {
      Buffer.str("");
      Buffer << Count;
      Dc.DrawText(Buffer.str().c_str(), 60, 500 - (Count * 50));
   }
   
   if(Both)
   {
      Offset = (500 - Dc.GetTextExtent("Red: Before Green: After (s)").GetWidth()) / 2;
      Dc.DrawRotatedText("Red: Before Green: After (s)", 30, 500 - Offset, 90.0);   
   }
   else
   {
      Offset = (500 - Dc.GetTextExtent("Before (s)").GetWidth()) / 2;
      Dc.DrawRotatedText("Before (s)", 30, 500 - Offset, 90.0);
   }
}

////////////////////////////////////////////////////////////////////////////////
//
// Class DistribPanel
//
////////////////////////////////////////////////////////////////////////////////

static int ScalingTable[] =
{
     5,  1,
    10,  1,
    20,  2,
    25,  5,
    30,  3,
    50,  5,
   100, 10,
   200, 20,
   250, 25,
   500, 50,
     0,  0
};
 
////////////////////////////////////////////////////////////////////////////////

DistribPanel::DistribPanel(wxFrame* Parent,
                           map<string, Entry *> *TheData,
                           GraphType TheType):
   GraphPanel(Parent, TheData)
{
   Type = TheType;
   
   if(Type == GRAPH_DISTRIB_BOTH)
      Parent->SetTitle("Frequency Distribution");
   else if(Type == GRAPH_DISTRIB_NAUSEOUS)
      Parent->SetTitle("Frequency Distribution (Nauseators)");
   else if(Type == GRAPH_DISTRIB_NON_NAUSEOUS)
      Parent->SetTitle("Frequency Distribution (Non-Nauseators)");
}

////////////////////////////////////////////////////////////////////////////////

void DistribPanel::OnPaint(wxPaintEvent &)
{
   wxPaintDC Dc(this);
   
   // We can't draw the Y axis without data to scale to!
   
   if(Data->size() == 0) return;

   ChooseColoursDrawAxes(Dc);
      
   // Go through the entries counting the number of entries in each 1s band.
   
   int Counts[10];
   int ScalingCounts[10];
   
   for(int i = 0; i < 10; i++) Counts[i] = ScalingCounts[i] = 0;
   
   for(map<string, Entry *>::iterator It = Data->begin();
       It != Data->end();
       It++)
   {
      ScalingCounts[It->second->Before / 1000]++;
      
      if((Type == GRAPH_DISTRIB_BOTH)                                     ||
         (Type == GRAPH_DISTRIB_NAUSEOUS)     && (It->second->IsNauseous) ||
         (Type == GRAPH_DISTRIB_NON_NAUSEOUS) && (!It->second->IsNauseous))
      Counts[It->second->Before / 1000]++;
   }

   // Find the biggest band so we can scale the Y axis.
   
   int MaxNumber = 0;
   int MaxScale = 0;
   int Division = 0;

   for(int Count = 0; Count < 10; Count++)
      if(ScalingCounts[Count] > MaxNumber)
         MaxNumber = ScalingCounts[Count];
         
   // Look through the scaling table to find the smallest subjectively approved
   // scale that will accomodate the biggest band.
   // LATER: Do better if the table is exhausted.
   
   int *Cursor;
   
   for(Cursor = ScalingTable; *Cursor < MaxNumber; Cursor += 2)
      if(*Cursor == 0) return;
      
   MaxScale = *Cursor;
   Division = *(Cursor + 1);

   int PixelsPerNumber = 500 / MaxScale;
   int PixelsPerDivision = PixelsPerNumber * Division;
         
   // Draw the X axis, scale and legend.

   int Offset;
   ostringstream Buffer;
   
   for(int Count = 0; Count < 10; Count++)
   {
      Buffer.str("");
      Buffer << Count;
      Offset = (50 - Dc.GetTextExtent(Buffer.str().c_str()).GetWidth()) / 2;
      Dc.DrawText(Buffer.str().c_str(), 100 + (Count * 50) + Offset, 500 + 20);
   }
   
   Offset = (500 - Dc.GetTextExtent("Before (s)").GetWidth()) / 2;
   Dc.DrawText("Before (s)", 100 + Offset, 500 + 40);
   
   // Draw the Y axis, scale and legend.
   
   for(int Count = 0; Count < MaxScale / Division; Count++)
   {
      Buffer.str("");
      Buffer << Count * Division;
      Dc.DrawText(Buffer.str().c_str(), 60, 500 - (Count * PixelsPerDivision));
   }
   
   Offset = (500 - Dc.GetTextExtent("Respondents").GetWidth()) / 2;
   Dc.DrawRotatedText("Respondents", 30, 500 - Offset, 90.0);

   // Draw the blocks.
   
   for(int Count = 0; Count < 10; Count++)
   {
      Dc.DrawRectangle(100 + (Count * 50),
                       500 - Counts[Count] * PixelsPerNumber,
                       50,
                       Counts[Count] * PixelsPerNumber);
   }
}

////////////////////////////////////////////////////////////////////////////////
//
// Class DrugDistribPanel
//
////////////////////////////////////////////////////////////////////////////////

DrugDistribPanel::DrugDistribPanel(wxFrame* Parent,
                                   map<string, Entry *> *TheData):
   GraphPanel(Parent, TheData)
{
   Parent->SetTitle("Frequency Distribution Per Drug");
}

////////////////////////////////////////////////////////////////////////////////

void DrugDistribPanel::OnPaint(wxPaintEvent &)
{
   wxPaintDC Dc(this);
   
   // We can't draw the Y axis without data to scale to!
   
   if(Data->size() == 0) return;

   ChooseColoursDrawAxes(Dc);
      
   // Go through the entries counting the number of entries in each 1s band.
   
   int Counts[10][DRUG_MAX];
   
   for(unsigned int i = 0; i < 10; i++)
      for(unsigned int j = 0; j < DRUG_MAX; j++)
         Counts[i][j] = 0;
   
   for(map<string, Entry *>::iterator It = Data->begin();
       It != Data->end();
       It++)
      for(unsigned int Drug = 0; Drug < DRUG_MAX; Drug++)
         if(It->second->Drugs[Drug])
            Counts[It->second->Before / 1000][Drug]++;

   // Find the biggest band so we can scale the Y axis.
   
   int MaxNumber = 0;
   int MaxScale = 0;
   int Division = 0;

   for(int Count = 0; Count < 10; Count++)
      for(int Drug = 0; Drug < DRUG_MAX; Drug++)
         if(Counts[Count][Drug] > MaxNumber)
            MaxNumber = Counts[Count][Drug];
         
   // Look through the scaling table to find the smallest subjectively approved
   // scale that will accomodate the biggest band.
   // LATER: Do better if the table is exhausted.
   
   int *Cursor;
   
   for(Cursor = ScalingTable; *Cursor < MaxNumber; Cursor += 2)
      if(*Cursor == 0) return;
      
   MaxScale = *Cursor;
   Division = *(Cursor + 1);

   int PixelsPerNumber = 500 / MaxScale;
   int PixelsPerDivision = PixelsPerNumber * Division;
         
   // Draw the X axis, scale and legend.

   int Offset;
   ostringstream Buffer;
   
   for(int Count = 0; Count < 10; Count++)
   {
      Buffer.str("");
      Buffer << Count;
      Offset = (50 - Dc.GetTextExtent(Buffer.str().c_str()).GetWidth()) / 2;
      Dc.DrawText(Buffer.str().c_str(), 100 + (Count * 50) + Offset, 500 + 20);
   }
   
   Offset = (500 - Dc.GetTextExtent("Before (s)").GetWidth()) / 2;
   Dc.DrawText("Before (s)", 100 + Offset, 500 + 40);
   
   // Draw the Y axis, scale and legend.
   
   for(int Count = 0; Count < MaxScale / Division; Count++)
   {
      Buffer.str("");
      Buffer << Count * Division;
      Dc.DrawText(Buffer.str().c_str(), 60, 500 - (Count * PixelsPerDivision));
   }
   
   Offset = (500 - Dc.GetTextExtent("Respondents").GetWidth()) / 2;
   Dc.DrawRotatedText("Respondents", 30, 500 - Offset, 90.0);

   // Draw the blocks.
   
   for(unsigned int Drug = 0; Drug < DRUG_MAX; Drug++)
   {
      wxPoint Points[10];

      for(int Count = 0; Count < 10; Count++)
      {
         Points[Count].x = 100 + (Count * 50) + 25;
         Points[Count].y = 500 - Counts[Count][Drug] * PixelsPerNumber;
      }

      Dc.SetPen(wxPen(*(DrugColours[Drug])));
      Dc.SetTextForeground(*(DrugColours[Drug]));
      Dc.DrawText(DrugNames[Drug].c_str(), 400, 100 + (Drug * 30));

      Dc.DrawSpline(10, Points);
   }
}

////////////////////////////////////////////////////////////////////////////////
//
// Class StressorsByAvScorePanel
//
////////////////////////////////////////////////////////////////////////////////

StressorsByAvScorePanel::StressorsByAvScorePanel(wxFrame* Parent,
                                                 map<string, Entry *> *TheData):
   GraphPanel(Parent, TheData)
{
   Parent->SetTitle("Stressors By Average Score");
}

////////////////////////////////////////////////////////////////////////////////

void StressorsByAvScorePanel::OnPaint(wxPaintEvent &)
{
   wxPaintDC Dc(this);

   ChooseColoursDrawAxes(Dc);
   
   // Draw the X axis, scale and legend.

   int Offset;
   int Division = 500 / 30;
   ostringstream Buffer;
   
   for(int Count = 0; Count < 7; Count++)
   {
      Buffer.str("");
      Buffer << (Count - 3) * 5;
      Offset = (Dc.GetTextExtent(Buffer.str().c_str()).GetWidth()) / 2;
      Dc.DrawText(Buffer.str().c_str(), 100 + (Count * Division * 5) - Offset, 500 + 20);
   }
   
   Offset = (500 - Dc.GetTextExtent("Chill Points").GetWidth()) / 2;
   Dc.DrawText("Chill Points", 100 + Offset, 500 + 40);
   
   // Draw the Y scale and legend.
   
   DrawScoreScaleY(Dc, false);

   // Go through the entries and calculate the mean for each stress score.
   
   map<int, int> TotalScores;
   map<int, int> TotalCounts;
   map<int, int> Means;
   map<int, int> SummedSquares;
   
   for(map<string, Entry *>::iterator It = Data->begin();
       It != Data->end();
       It++)
   {
      TotalScores[It->second->GetStressScore()] += It->second->Before;
      TotalCounts[It->second->GetStressScore()]++;
   }
   
   for(map<int, int>::iterator It = TotalCounts.begin();
       It != TotalCounts.end();
       It++)
      Means[It->first] = TotalScores[It->first] / It->second;
   
   // Now make a second pass to calculate the standard deviation.
   
   for(map<string, Entry *>::iterator It = Data->begin();
       It != Data->end();
       It++)
   {
      int Temp = It->second->Before - Means[It->second->GetStressScore()];
      SummedSquares[It->second->GetStressScore()] += (Temp * Temp);
   }
   
   // Now draw the means and standard deviations.

   for(map<int, int>::iterator It = TotalCounts.begin();
       It != TotalCounts.end();
       It++)
   {
      int X = It->first;
      int Y = TotalScores[It->first] / It->second;
      int D = (int)sqrt(SummedSquares[It->first] / It->second);
      Dc.DrawCircle(100 + 250 + (X * Division), 500 - (Y / 20), 5);
      Dc.DrawLine(100 + 250 + (X * Division), 500 - ((Y + D) / 20),
                  100 + 250 + (X * Division), 500 - ((Y - D) / 20));
   }
}

////////////////////////////////////////////////////////////////////////////////
//
// Class ExercisesByAvScorePanel
//
////////////////////////////////////////////////////////////////////////////////

ExercisesByAvScorePanel::ExercisesByAvScorePanel(wxFrame* Parent,
                                                 map<string, Entry *> *TheData):
   GraphPanel(Parent, TheData)
{
   Parent->SetTitle("Exercises By Average Score");
}

////////////////////////////////////////////////////////////////////////////////

void ExercisesByAvScorePanel::OnPaint(wxPaintEvent &)
{
   wxPaintDC Dc(this);

   ChooseColoursDrawAxes(Dc);
   
   // Draw the X axis, scale and legend.

   int Offset;
   int Division = 500 / 18;
   ostringstream Buffer;
   
   for(int Count = 0; Count < 18; Count++)
   {
      Buffer.str("");
      Buffer << Count;
      Offset = (Dc.GetTextExtent(Buffer.str().c_str()).GetWidth()) / 2;
      Dc.DrawText(Buffer.str().c_str(),
                  100 + (Count * Division) + (Division / 2) - Offset,
                  500 + 20);
   }
   
   Offset = (500 - Dc.GetTextExtent("Number of Exercises").GetWidth()) / 2;
   Dc.DrawText("Number of Exercises", 100 + Offset, 500 + 40);
   
   // Draw the Y scale and legend.
   
   DrawScoreScaleY(Dc, true);

   // Go through the entries and calculate the mean before score for each number
   // of exercises.
   
   map<int, int> TotalScores;
   map<int, int> TotalCounts;
   map<int, int> Means;
   map<int, int> SummedSquares;
   
   for(map<string, Entry *>::iterator It = Data->begin();
       It != Data->end();
       It++)
   {
      if(It->second->After > 0)
      {
         TotalScores[It->second->GetExerciseScore()] += It->second->Before;
         TotalCounts[It->second->GetExerciseScore()]++;
      }
   }
   
   for(map<int, int>::iterator It = TotalCounts.begin();
       It != TotalCounts.end();
       It++)
      Means[It->first] = TotalScores[It->first] / It->second;
   
   // Now make a second pass to calculate the before score standard deviation.
   
   for(map<string, Entry *>::iterator It = Data->begin();
       It != Data->end();
       It++)
   {
      if(It->second->After > 0)
      {
         int Temp = It->second->Before - Means[It->second->GetExerciseScore()];
         SummedSquares[It->second->GetExerciseScore()] += (Temp * Temp);
      }
   }
   
   // Now draw the before score means and standard deviations in red.
   
   Dc.SetPen(*wxRED_PEN);
   Dc.SetBrush(*wxRED_BRUSH);

   for(map<int, int>::iterator It = TotalCounts.begin();
       It != TotalCounts.end();
       It++)
   {
      int X = It->first;
      int Y = TotalScores[It->first] / It->second;
      int D = (int)sqrt(SummedSquares[It->first] / It->second);
      
      Dc.DrawCircle(100 + (X * Division) + (Division / 2) - 3, 500 - (Y / 20), 3);
      Dc.DrawLine(100 + (X * Division) + (Division / 2) - 3, 500 - ((Y + D) / 20),
                  100 + (X * Division) + (Division / 2) - 3, 500 - ((Y - D) / 20));
   }

   // Go through the entries and calculate the mean after score for each number
   // of exercises.
   
   TotalScores.clear();
   TotalCounts.clear();
   Means.clear();
   SummedSquares.clear();
   
   for(map<string, Entry *>::iterator It = Data->begin();
       It != Data->end();
       It++)
   {
      if(It->second->After > 0)
      {
         TotalScores[It->second->GetExerciseScore()] += It->second->After;
         TotalCounts[It->second->GetExerciseScore()]++;
      }
   }
   
   for(map<int, int>::iterator It = TotalCounts.begin();
       It != TotalCounts.end();
       It++)
      Means[It->first] = TotalScores[It->first] / It->second;
   
   // Now make a second pass to calculate the before score standard deviation.
   
   for(map<string, Entry *>::iterator It = Data->begin();
       It != Data->end();
       It++)
   {
      if(It->second->After > 0)
      {
         int Temp = It->second->After - Means[It->second->GetExerciseScore()];
         SummedSquares[It->second->GetExerciseScore()] += (Temp * Temp);
      }
   }
   
   // Now draw the before score means and standard deviations in red.
   
   Dc.SetPen(*wxGREEN_PEN);
   Dc.SetBrush(*wxGREEN_BRUSH);

   for(map<int, int>::iterator It = TotalCounts.begin();
       It != TotalCounts.end();
       It++)
   {
      int X = It->first;
      int Y = TotalScores[It->first] / It->second;
      int D = (int)sqrt(SummedSquares[It->first] / It->second);
      
      Dc.DrawCircle(100 + (X * Division) + (Division / 2) + 3, 500 - (Y / 20), 3);
      Dc.DrawLine(100 + (X * Division) + (Division / 2) + 3, 500 - ((Y + D) / 20),
                  100 + (X * Division) + (Division / 2) + 3, 500 - ((Y - D) / 20));
   }

}

////////////////////////////////////////////////////////////////////////////////
//
// Class AgeByAvScorePanel
//
////////////////////////////////////////////////////////////////////////////////

AgeByAvScorePanel::AgeByAvScorePanel(wxFrame* Parent,
                                     map<string, Entry *> *TheData):
   GraphPanel(Parent, TheData)
{
   Parent->SetTitle("Age By Average Score");
}

////////////////////////////////////////////////////////////////////////////////

void AgeByAvScorePanel::OnPaint(wxPaintEvent &)
{
   wxPaintDC Dc(this);
   
   ChooseColoursDrawAxes(Dc);
   
   // Draw the X axis, scale and legend.

   int Offset;
   ostringstream Buffer;
   
   for(int Count = 0; Count < 10; Count++)
   {
      Buffer.str("");
      Buffer << Count;
      Offset = (50 - Dc.GetTextExtent(Buffer.str().c_str()).GetWidth()) / 2;
      Dc.DrawText(Buffer.str().c_str(), 100 + (Count * 50) + Offset, 500 + 20);
   }
   
   Offset = (500 - Dc.GetTextExtent("Age (Decade)").GetWidth()) / 2;
   Dc.DrawText("Age (Decade)", 100 + Offset, 500 + 40);
   
   // Draw the Y scale and legend.
   
   DrawScoreScaleY(Dc, false);
   
   // Go through the entries and calculate the mean for each stress score.
   
   int TotalScores[10];
   int TotalCounts[10];
   int Means[10];
   int SummedSquares[10];
   
   for(int Count = 0; Count < 10; Count++)
      TotalScores[Count]   =
      TotalCounts[Count]   =
      Means[Count]         =
      SummedSquares[Count] = 0;
   
   for(map<string, Entry *>::iterator It = Data->begin();
       It != Data->end();
       It++)
   {
      if(It->second->AgeGroup < 10)
      {
         TotalScores[It->second->AgeGroup] += It->second->Before;
         TotalCounts[It->second->AgeGroup]++;
      }
   }
   
   for(int Count = 0; Count < 10; Count++)
      if(TotalCounts[Count] > 0)
         Means[Count] = TotalScores[Count] / TotalCounts[Count];
      
   // Now make a second pass to calculate the standard deviation.
   
   for(map<string, Entry *>::iterator It = Data->begin();
       It != Data->end();
       It++)
   {
      if(It->second->AgeGroup < 10)
      {
         int Temp = It->second->Before - Means[It->second->AgeGroup];
         SummedSquares[It->second->AgeGroup] += (Temp * Temp);
      }
   }
   
   // Now draw the means and standard deviations.
   
   for(int Count = 0; Count < 10; Count++)
   {
      if(TotalCounts[Count] > 0)
      {
         int Y = (TotalScores[Count] / TotalCounts[Count]) / 20;
         int D = (int)sqrt(SummedSquares[Count] / TotalCounts[Count]) / 20;
         Dc.DrawCircle(125 + (Count * 50), 500 - Y, 5);
         Dc.DrawLine(125 + (Count * 50), 500 - (Y + D),
                     125 + (Count * 50), 500 - (Y - D));
      }
   }
}

////////////////////////////////////////////////////////////////////////////////
//
// Class Entry
//
////////////////////////////////////////////////////////////////////////////////

Entry::Entry(void)
{
   IsMale = true;
   AgeGroup = 0;
   Occupation = "";
   IsGeek = false;
   
   EveningWalks = false;
   CulturalActivities = false;
   ImaginaryFriend = false;
   History = false;
   Cooking = false;
   Meditation = false;
   ChangeRoutes = false;
   ChangeJob = false;
   MoveHome = false;
   FallInLove = false;
   BreakUp = false;
   NewCar = false;
   Vacation = false;
   SeeOldFriends = false;
   GetMoreSleep = false;
   Disaster = false;
   
   IEnjoyMyJob = 0;
   MyJobIsWellDefined = 0;
   MyCoworkersAreCooperative = 0;
   MyWorkplaceIsStressful = 0;
   MyJobIsStressful = 0;
   MoraleIsGoodWhereIWork = 0;
   IGetFrustratedAtWork = 0;
   IsNauseous = false;
   
   for(unsigned int Count = 0; Count < DRUG_MAX; Count++)
      Drugs[Count] = false;

   OtherMeds = "";
   OtherDrugs = "";
   
   Before = 0;
   After = 0;
}

////////////////////////////////////////////////////////////////////////////////

int Entry::GetStressScore(void)
{
   return IEnjoyMyJob +
          MyJobIsWellDefined +
          MyCoworkersAreCooperative +
          MyWorkplaceIsStressful +
          MyJobIsStressful +
          MoraleIsGoodWhereIWork +
          IGetFrustratedAtWork;
}

////////////////////////////////////////////////////////////////////////////////

int Entry::GetExerciseScore(void)
{
   int Count = 0;
   
   if(EveningWalks) Count++;
   if(CulturalActivities) Count++;
   if(ImaginaryFriend) Count++;
   if(History) Count++;
   if(Cooking) Count++;
   if(Meditation) Count++;
   if(ChangeRoutes) Count++;
   if(ChangeJob) Count++;
   if(MoveHome) Count++;
   if(FallInLove) Count++;
   if(BreakUp) Count++;
   if(NewCar) Count++;
   if(Vacation) Count++;
   if(SeeOldFriends) Count++;
   if(GetMoreSleep) Count++;
   if(Disaster) Count++;
   
   return Count;
}

////////////////////////////////////////////////////////////////////////////////

