/*
 * brewtarget.cpp is part of Brewtarget, and is Copyright the following
 * authors 2009-2014
 * - A.J. Drobnich <aj.drobnich@gmail.com>
 * - Dan Cavanagh <dan@dancavanagh.com>
 * - Maxime Lavigne <duguigne@gmail.com>
 * - Mik Firestone <mikfire@gmail.com>
 * - Philip Greggory Lee <rocketman768@gmail.com>
 * - Rob Taylor <robtaylor@floopily.org>
 * - Ted Wright <unsure>
 *
 * Brewtarget 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.
 *
 * Brewtarget 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 <iostream>
#include <QFile>
#include <QIODevice>
#include <QString>
#include <QDomNode>
#include <QDomElement>
#include <QDomText>
#include <QDomNodeList>
#include <QTextStream>
#include <QObject>
#include <QLocale>
#include <QLibraryInfo>
#include <QtNetwork/QNetworkAccessManager>
#include <QEventLoop>
#include <QUrl>
#include <QtNetwork/QNetworkReply>
#include <QObject>
#include <QMessageBox>
#include <QDesktopServices>
#include <QSharedPointer>
#include <QtNetwork/QNetworkRequest>
#include <QPixmap>
#include <QSplashScreen>
#include <QSettings>

#include "brewtarget.h"
#include "config.h"
#include "database.h"
#include "Algorithms.h"
#include "fermentable.h"
#include "UnitSystem.h"
#include "UnitSystems.h"
#include "unit.h"
#include "USWeightUnitSystem.h"
#include "USVolumeUnitSystem.h"
#include "FahrenheitTempUnitSystem.h"
#include "TimeUnitSystem.h"
#include "SIWeightUnitSystem.h"
#include "SIVolumeUnitSystem.h"
#include "CelsiusTempUnitSystem.h"
#include "ImperialVolumeUnitSystem.h"

#include "EbcColorUnitSystem.h"
#include "SrmColorUnitSystem.h"
#include "SgDensityUnitSystem.h"
#include "PlatoDensityUnitSystem.h"

#include "BtSplashScreen.h"
#include "MainWindow.h"
#include "mash.h"
#include "instruction.h"
#include "water.h"

MainWindow* Brewtarget::_mainWindow = 0;
QDomDocument* Brewtarget::optionsDoc;
QTranslator* Brewtarget::defaultTrans = new QTranslator();
QTranslator* Brewtarget::btTrans = new QTranslator();
QTextStream* Brewtarget::logStream = 0;
QFile* Brewtarget::logFile = 0;
bool Brewtarget::userDatabaseDidNotExist = false;
QFile Brewtarget::pidFile;
QDateTime Brewtarget::lastDbMergeRequest = QDateTime::fromString("1986-02-24T06:00:00", Qt::ISODate);

QString Brewtarget::currentLanguage = "en";
QString Brewtarget::userDataDir = getConfigDir();

bool Brewtarget::checkVersion = true;

iUnitSystem Brewtarget::weightUnitSystem = SI;
iUnitSystem Brewtarget::volumeUnitSystem = SI;

TempScale Brewtarget::tempScale = Celsius;
Unit::unitDisplay Brewtarget::dateFormat = Unit::displaySI;

Brewtarget::ColorType Brewtarget::colorFormula = Brewtarget::MOREY;
Brewtarget::IbuType Brewtarget::ibuFormula = Brewtarget::TINSETH;
Brewtarget::ColorUnitType Brewtarget::colorUnit = Brewtarget::SRM;
Brewtarget::DensityUnitType Brewtarget::densityUnit = Brewtarget::SG;

QHash<int, UnitSystem*> Brewtarget::thingToUnitSystem;

bool Brewtarget::ensureDirectoriesExist()
{
   bool success;
   QDir dir;

   QString errTitle(QObject::tr("Directory Problem"));
   QString errText(QObject::tr("\"%1\" cannot be read."));

   // Check data dir
   dir.setPath(getDataDir());
   if( ! dir.exists() || ! dir.isReadable() )
   {
      QMessageBox::information(
         0,
         errTitle,
         errText.arg(dir.path())
      );
      return false;
   }

   // Check doc dir
   dir.setPath(getDocDir());
   if( ! dir.exists() || ! dir.isReadable() )
   {
      QMessageBox::information(
         0,
         errTitle,
         errText.arg(dir.path())
      );
      return false;
   }

   // Check config dir
   dir.setPath(getConfigDir(&success));
   if( !success || ! dir.exists() || ! dir.isReadable() )
   {
      QMessageBox::information(
         0,
         errTitle,
         errText.arg(dir.path())
      );
      return false;
   }

   // Check/create user data directory
   dir.setPath(getUserDataDir());
   if( !dir.exists() && !dir.mkpath(".") )
   {
      QMessageBox::information(
         0,
         errTitle,
         errText.arg(dir.path())
      );
      return false;
   }

   return true;
}

void Brewtarget::checkForNewVersion(MainWindow* mw)
{

   // Don't do anything if the checkVersion flag was set false
   if ( checkVersion == false )
      return;

   QNetworkAccessManager manager;
   QUrl url("http://brewtarget.sourceforge.net/version");
   QNetworkReply* reply = manager.get( QNetworkRequest(url) );
   QObject::connect( reply, SIGNAL(finished()), mw, SLOT(finishCheckingVersion()) );
}

bool Brewtarget::copyDataFiles(QString newPath)
{
   QString dbFileName;
   bool success = true;

   // Database files.
   dbFileName = getUserDataDir() + "database.sqlite";
   success &= QFile::copy(dbFileName, newPath + "database.sqlite");

   return success;
}

bool Brewtarget::ensureDataFilesExist()
{
   QString logFileName;
   bool success = true;

   logFile = new QFile();

   // Log file
   logFile->setFileName(getUserDataDir() + "brewtarget_log.txt");
   if( logFile->open(QFile::WriteOnly | QFile::Truncate) )
      logStream = new QTextStream(logFile);
   else
   {
      // Put the log in a temporary directory.
      logFile->setFileName(QDir::tempPath() + "/brewtarget_log.txt");
      if( logFile->open(QFile::WriteOnly | QFile::Truncate ) )
      {
         logW(QString("Log is in a temporary directory: %1").arg(logFile->fileName()) );
         logStream = new QTextStream(logFile);
      }
      else
         logW(QString("Could not create a log file."));
   }

   return success;
}

const QString& Brewtarget::getSystemLanguage()
{
   // QLocale::name() is of the form language_country,
   // where 'language' is a lowercase 2-letter ISO 639-1 language code,
   // and 'country' is an uppercase 2-letter ISO 3166 country code.
   return QLocale::system().name().split("_")[0];
}

void Brewtarget::loadTranslations()
{
   if( qApp == 0 )
      return;

   // Load translators.
   defaultTrans->load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
   if( getCurrentLanguage().isEmpty() )
      setLanguage(getSystemLanguage());
   //btTrans->load("bt_" + getSystemLanguage());

   // Install translators.
   qApp->installTranslator(defaultTrans);
   //qApp->installTranslator(btTrans);
}

void Brewtarget::setLanguage(QString twoLetterLanguage)
{
   currentLanguage = twoLetterLanguage;
   qApp->removeTranslator(btTrans);

   QString filename = QString("bt_%1").arg(twoLetterLanguage);
   QString dir = QString("%1translations_qm/").arg(getDataDir());
   if( btTrans->load( filename, dir ) )
      qApp->installTranslator(btTrans);

}

const QString& Brewtarget::getCurrentLanguage()
{
   return currentLanguage;
}

iUnitSystem Brewtarget::getWeightUnitSystem()
{
   return weightUnitSystem;
}

iUnitSystem Brewtarget::getVolumeUnitSystem()
{
   return volumeUnitSystem;
}

Unit::unitDisplay Brewtarget::getColorUnit()
{
   if ( colorUnit == Brewtarget::SRM )
      return Unit::displaySrm;

   return Unit::displayEbc;
}

Unit::unitDisplay Brewtarget::getDateFormat()
{
   return dateFormat;
}

Unit::unitDisplay Brewtarget::getDensityUnit()
{
   if ( densityUnit == Brewtarget::SG )
      return Unit::displaySg;

   return Unit::displayPlato;
}

TempScale Brewtarget::getTemperatureScale()
{
   return tempScale;
}

QString Brewtarget::getDataDir()
{
   QString dir = qApp->applicationDirPath();
#if defined(Q_OS_LINUX) // Linux OS.

   dir = QString(CONFIGDATADIR);

#elif defined(Q_OS_MAC) // MAC OS.

   // We should be inside an app bundle.
   dir += "/../Resources/";

#elif defined(Q_OS_WIN) // Windows OS.

   dir += "/../data/";

#else
# error "Unsupported OS"
#endif

   if( ! dir.endsWith('/') )
      dir += "/";

   return dir;
}

QString Brewtarget::getDocDir()
{
   QString dir = qApp->applicationDirPath();
#if defined(Q_OS_LINUX) // Linux OS.

   dir = QString(CONFIGDOCDIR);

#elif defined(Q_OS_MAC) // MAC OS.

   // We should be inside an app bundle.
   dir += "/../Resources/en.lproj/";

#elif defined(Q_OS_WIN) // Windows OS.

   dir += "/../doc/";

#else
# error "Unsupported OS"
#endif

   if( ! dir.endsWith('/') )
      dir += "/";

   return dir;
}

QString Brewtarget::getConfigDir(bool *success)
{
#if defined(Q_OS_LINUX) || defined(Q_OS_MAC) // Linux OS or Mac OS.

   QDir dir;
   QFileInfo fileInfo;
   char* xdg_config_home = getenv("XDG_CONFIG_HOME");
   bool tmp;
   QFile::Permissions sevenFiveFive = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner |
                                      QFile::ReadGroup |                     QFile::ExeGroup |
                                      QFile::ReadOther |                     QFile::ExeOther;
   // First, try XDG_CONFIG_HOME.
   // If that variable doesn't exist, create ~/.config
   if (xdg_config_home)
   {
      dir = xdg_config_home;
   }
   else
   {
      // Creating config directory.
      dir = QDir::home();
      if( !dir.exists(".config") )
      {
         logW( QString("Config dir \"%1\" did not exist...").arg(dir.absolutePath() + "/.config") );
         tmp = dir.mkdir(".config");
         logW( QString( tmp ? "...created it." : "...could not create it.") );
         if( !tmp )
         {
            // Failure.
            if( success != 0 )
               *success = false;
            return "";
         }

         // chmod 755 ~/.config
         QFile::setPermissions( dir.absolutePath() + "/.config", sevenFiveFive );
      }

      // CD to config directory.
      if( ! dir.cd(".config") )
      {
         logE( QString("Could not CD to \"%1\".").arg(dir.absolutePath() + "/.config") );
         if( success != 0 )
            *success = false;
         return "";
      }
   }

   // See if brewtarget dir exists.
   if( !dir.exists("brewtarget") )
   {
      logW( QString("\"%1\" does not exist...creating.").arg(dir.absolutePath() + "/brewtarget") );

      // Try to make brewtarget dir.
      if( ! dir.mkdir("brewtarget") )
      {
         logE( QString("Could not create \"%1\"").arg(dir.absolutePath() + "/brewtarget") );
         if( success != 0 )
            *success = false;
         return "";
      }

      // chmod 755 ~/.config/brewtarget
      QFile::setPermissions( dir.absolutePath() + "/brewtarget", sevenFiveFive );
   }

   if( ! dir.cd("brewtarget") )
   {
      logE(QString("Could not CD into \"%1\"").arg(dir.absolutePath() + "/brewtarget"));
      if( success != 0 )
         *success = false;
      return "";
   }

   if( success != 0 )
      *success = true;
   return dir.absolutePath() + "/";

#elif defined(Q_OS_WIN) // Windows OS.

   QDir dir;
   // This is the bin/ directory.
   dir = QDir(qApp->applicationDirPath());
   dir.cdUp();
   // Now we should be in the base directory (i.e. Brewtarget-2.0.0/)

   dir.cd("data");
   if( success != 0 )
      *success = true;
   return dir.absolutePath() + "/";

#else
# error "Unsupported OS"
#endif

}

QString Brewtarget::getUserDataDir()
{
   if( userDataDir.endsWith('/') || userDataDir.endsWith('\\') )
      return userDataDir;
   else
      return userDataDir + "/";
}

bool Brewtarget::initialize()
{
   // Need these for changed(QMetaProperty,QVariant) to be emitted across threads.
   qRegisterMetaType<QMetaProperty>();
   qRegisterMetaType<Equipment*>();
   qRegisterMetaType<Mash*>();
   qRegisterMetaType<Style*>();
   qRegisterMetaType<Brewtarget::DBTable>();
   qRegisterMetaType< QList<BrewNote*> >();
   qRegisterMetaType< QList<Hop*> >();
   qRegisterMetaType< QList<Instruction*> >();
   qRegisterMetaType< QList<Fermentable*> >();
   qRegisterMetaType< QList<Misc*> >();
   qRegisterMetaType< QList<Yeast*> >();
   qRegisterMetaType< QList<Water*> >();

   // In Unix, make sure the user isn't running 2 copies.
#if defined(Q_OS_LINUX)
   pidFile.setFileName(QString("%1.pid").arg(getUserDataDir()));
   if( pidFile.exists() )
   {
      // Read the pid.
      qint64 pid;
      pidFile.open(QIODevice::ReadOnly);
      {
         QTextStream pidStream(&pidFile);
         pidStream >> pid;
      }
      pidFile.close();

      // If the pid is in the proc filesystem, another instance is running.
      // Have to check /proc, because perhaps the last instance crashed without
      // cleaning up after itself.
      QDir procDir(QString("/proc/%1").arg(pid));
      if( procDir.exists() )
      {
         std::cerr << "Brewtarget is already running. PID: " << pid << std::endl;
         return false;
      }
   }

   // Open the pidFile, erasing any contents, and write our pid.
   pidFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
   {
      QTextStream pidStream(&pidFile);
      pidStream << QCoreApplication::applicationPid();
   }
   pidFile.close();
#endif
   userDataDir = getConfigDir();

   // If the old options file exists, convert it. Otherwise, just get the
   // system options. I *think* this will work. The installer copies the old
   // one into the new place on Windows.
   if ( option("hadOldConfig", false).toBool() )
      convertPersistentOptions();

   readSystemOptions();
   loadMap();

   // Make sure all the necessary directories and files we need exist before starting.
   bool success;
   success = ensureDirectoriesExist() && ensureDataFilesExist();
   if(!success)
      return false;

   loadTranslations(); // Do internationalization.

#if defined(Q_OS_MAC)
   qt_set_sequence_auto_mnemonic(true); // turns on Mac Keyboard shortcuts
#endif

   // Check if the database was successfully loaded before
   // loading the main window.
   if (Database::instance().loadSuccessful())
   {
      if ( ! QSettings().contains("converted") )
         Database::instance().convertFromXml();

      return true;
   }
   else
      return false;

}

void Brewtarget::cleanup()
{
   // Close log file.
   if( logStream )
   {
      delete logStream;
      logStream = 0;
   }
   if( logFile != 0 && logFile->isOpen() )
   {
      logFile->close();
      delete logFile;
      logFile = 0;
   }

   // Should I do qApp->removeTranslator() first?
   delete defaultTrans;
   delete btTrans;
   delete _mainWindow;

   Database::dropInstance();
#if defined(Q_OS_LINUX)
   pidFile.remove();
#endif

}

int Brewtarget::run()
{
   int ret = 0;

   BtSplashScreen splashScreen;
   splashScreen.show();
   qApp->processEvents();
   if( !initialize() )
   {
      cleanup();
      return 1;
   }

   _mainWindow = new MainWindow();
   _mainWindow->setVisible(true);
   splashScreen.finish(_mainWindow);

   checkForNewVersion(_mainWindow);
   do {
      ret = qApp->exec();
   } while (ret == 1000);

   cleanup();

   return ret;
}

// Read the old options.xml file one more time, then move it out of the way.
void Brewtarget::convertPersistentOptions()
{
   QDir cfgDir = QDir(getConfigDir());
   QFile xmlFile(getConfigDir() + "options.xml");
   optionsDoc = new QDomDocument();
   QDomElement root;
   QString err;
   QString text;
   int line;
   int col;
   bool hasOption;

   // Try to open xmlFile.
   if( ! xmlFile.open(QIODevice::ReadOnly) )
   {
      // Now we know we can't open it.
      logW(QString("Could not open %1 for reading.").arg(xmlFile.fileName()));
      // Try changing the permissions
      return;
   }

   if( ! optionsDoc->setContent(&xmlFile, false, &err, &line, &col) )
      logW(QString("Bad document formatting in %1 %2:%3").arg(xmlFile.fileName()).arg(line).arg(col));

   root = optionsDoc->documentElement();

   //================Version Checking========================
   text = getOptionValue(*optionsDoc, "check_version");
   if( text == "true" )
      checkVersion = true;
   else
      checkVersion = false;

   //=====================Last DB Merge Request======================
   text = getOptionValue(*optionsDoc, "last_db_merge_req", &hasOption);
   if( hasOption )
      lastDbMergeRequest = QDateTime::fromString(text, Qt::ISODate);

   //=====================Language====================
   text = getOptionValue(*optionsDoc, "language", &hasOption);
   if( hasOption )
      setLanguage(text);

   //=======================Weight=====================
   text = getOptionValue(*optionsDoc, "weight_unit_system", &hasOption);
   if( hasOption )
   {
      if( text == "Imperial" )
      {
         weightUnitSystem = Imperial;
         thingToUnitSystem.insert(Unit::Mass,UnitSystems::usWeightUnitSystem());
      }
      else if (text == "USCustomary")
      {
         weightUnitSystem = USCustomary;
         thingToUnitSystem.insert(Unit::Mass,UnitSystems::usWeightUnitSystem());
      }
      else
      {
         weightUnitSystem = SI;
         thingToUnitSystem.insert(Unit::Mass,UnitSystems::siWeightUnitSystem());
      }
   }

   //===========================Volume=======================
   text = getOptionValue(*optionsDoc, "volume_unit_system", &hasOption);
   if( hasOption )
   {
      if( text == "Imperial" )
      {
         volumeUnitSystem = Imperial;
         thingToUnitSystem.insert(Unit::Volume,UnitSystems::imperialVolumeUnitSystem());
      }
      else if (text == "USCustomary")
      {
         volumeUnitSystem = USCustomary;
         thingToUnitSystem.insert(Unit::Volume,UnitSystems::usVolumeUnitSystem());
      }
      else
      {
         volumeUnitSystem = SI;
         thingToUnitSystem.insert(Unit::Volume,UnitSystems::siVolumeUnitSystem());
      }
   }

   //=======================Temp======================
   text = getOptionValue(*optionsDoc, "temperature_scale", &hasOption);
   if( hasOption )
   {
      if( text == "Fahrenheit" )
      {
         tempScale = Fahrenheit;
         thingToUnitSystem.insert(Unit::Temp,UnitSystems::fahrenheitTempUnitSystem());
      }
      else
      {
         tempScale = Celsius;
         thingToUnitSystem.insert(Unit::Temp,UnitSystems::celsiusTempUnitSystem());
      }
   }

   //======================Time======================
   // Set the one and only time system.
   thingToUnitSystem.insert(Unit::Time,UnitSystems::timeUnitSystem());

   //===================IBU===================
   text = getOptionValue(*optionsDoc, "ibu_formula", &hasOption);
   if( hasOption )
   {
      if( text == "tinseth" )
         ibuFormula = TINSETH;
      else if( text == "rager" )
         ibuFormula = RAGER;
      else if( text == "noonan")
         ibuFormula = NOONAN;
      else
      {
         Brewtarget::logE(QString("Bad ibu_formula type: %1").arg(text));
      }
   }

   //========================Color======================
   text = getOptionValue(*optionsDoc, "color_formula", &hasOption);
   if( hasOption )
   {
      if( text == "morey" )
         colorFormula = MOREY;
      else if( text == "daniel" )
         colorFormula = DANIEL;
      else if( text == "mosher" )
         colorFormula = MOSHER;
      else
      {
         Brewtarget::logE(QString("Bad color_formula type: %1").arg(text));
      }
   }

   //========================Density==================
   text = getOptionValue(*optionsDoc, "use_plato", &hasOption);
   if( hasOption )
   {
      if( text == "true" )
      {
         densityUnit = PLATO;
         thingToUnitSystem.insert(Unit::Density,UnitSystems::platoDensityUnitSystem());
      }
      else if( text == "false" )
      {
         densityUnit = SG;
         thingToUnitSystem.insert(Unit::Density,UnitSystems::sgDensityUnitSystem());
      }
      else
      {
         Brewtarget::logW(QString("Bad use_plato type: %1").arg(text));
      }
   }

   //=======================Color unit===================
   text = getOptionValue(*optionsDoc, "color_unit", &hasOption);
   if( hasOption )
   {
      if( text == "srm" )
      {
         colorUnit = SRM;
         thingToUnitSystem.insert(Unit::Color,UnitSystems::srmColorUnitSystem());
      }
      else if( text == "ebc" )
      {
         colorUnit = EBC;
         thingToUnitSystem.insert(Unit::Color,UnitSystems::ebcColorUnitSystem());
      }
      else
         Brewtarget::logW(QString("Bad color_unit type: %1").arg(text));
   }

   delete optionsDoc;
   optionsDoc = 0;
   xmlFile.close();

   // Don't do this on Windows. We have extra work to do and creating the
   // obsolete directory mess it all up. Not sure why that test is still in here
#ifndef Q_OS_WIN
   // This shouldn't really happen, but lets be sure
   if( !cfgDir.exists("obsolete") )
      cfgDir.mkdir("obsolete");

   // copy the old file into obsolete and delete it
   cfgDir.cd("obsolete");
   if( xmlFile.copy(cfgDir.filePath("options.xml")) )
      xmlFile.remove();

#endif
   // And remove the flag
   QSettings().remove("hadOldConfig");
}

QString Brewtarget::getOptionValue(const QDomDocument& optionsDoc, const QString& option, bool* hasOption)
{
   QDomNode node, child;
   QDomText textNode;
   QDomNodeList list;

   list = optionsDoc.elementsByTagName(option);
   if(list.length() <= 0)
   {
      Brewtarget::logW(QString("Could not find the <%1> tag in the option file.").arg(option));
      if( hasOption != 0 )
         *hasOption = false;
      return "";
   }
   else
   {
      node = list.at(0);
      child = node.firstChild();
      textNode = child.toText();

      if( hasOption != 0 )
         *hasOption = true;

      return textNode.nodeValue();
   }
}

void Brewtarget::readSystemOptions()
{
   QString text;

   //================Version Checking========================
   checkVersion = option("check_version", QVariant(false)).toBool();

   //=====================Last DB Merge Request======================
   if( hasOption("last_db_merge_req"))
      lastDbMergeRequest = QDateTime::fromString(option("last_db_merge_req","").toString(), Qt::ISODate);

   //=====================Language====================
   if( hasOption("language") )
      setLanguage(option("language","").toString());

   //=======================Data Dir===========================
   if( hasOption("user_data_dir") )
      userDataDir = option("user_data_dir","").toString();

   //=======================Weight=====================
   text = option("weight_unit_system", "SI").toString();
   if( text == "Imperial" )
   {
      weightUnitSystem = Imperial;
      thingToUnitSystem.insert(Unit::Mass,UnitSystems::usWeightUnitSystem());
   }
   else if (text == "USCustomary")
   {
      weightUnitSystem = USCustomary;
      thingToUnitSystem.insert(Unit::Mass,UnitSystems::usWeightUnitSystem());
   }
   else
   {
      weightUnitSystem = SI;
      thingToUnitSystem.insert(Unit::Mass,UnitSystems::siWeightUnitSystem());
   }

   //===========================Volume=======================
   text = option("volume_unit_system", "SI").toString();
   if( text == "Imperial" )
   {
      volumeUnitSystem = Imperial;
      thingToUnitSystem.insert(Unit::Volume,UnitSystems::imperialVolumeUnitSystem());
   }
   else if (text == "USCustomary")
   {
      volumeUnitSystem = USCustomary;
      thingToUnitSystem.insert(Unit::Volume,UnitSystems::usVolumeUnitSystem());
   }
   else
   {
      volumeUnitSystem = SI;
      thingToUnitSystem.insert(Unit::Volume,UnitSystems::siVolumeUnitSystem());
   }

   //=======================Temp======================
   text = option("temperature_scale", "SI").toString();
   if( text == "Fahrenheit" )
   {
      tempScale = Fahrenheit;
      thingToUnitSystem.insert(Unit::Temp,UnitSystems::fahrenheitTempUnitSystem());
   }
   else
   {
      tempScale = Celsius;
      thingToUnitSystem.insert(Unit::Temp,UnitSystems::celsiusTempUnitSystem());
   }

   //======================Time======================
   // Set the one and only time system.
   thingToUnitSystem.insert(Unit::Time,UnitSystems::timeUnitSystem());

   //===================IBU===================
   text = option("ibu_formula", "tinseth").toString();
   if( text == "tinseth" )
      ibuFormula = TINSETH;
   else if( text == "rager" )
      ibuFormula = RAGER;
   else if( text == "noonan" )
       ibuFormula = NOONAN;
   else
   {
      Brewtarget::logE(QString("Bad ibu_formula type: %1").arg(text));
   }

   //========================Color Formula======================
   text = option("color_formula", "morey").toString();
   if( text == "morey" )
      colorFormula = MOREY;
   else if( text == "daniel" )
      colorFormula = DANIEL;
   else if( text == "mosher" )
      colorFormula = MOSHER;
   else
   {
      Brewtarget::logE(QString("Bad color_formula type: %1").arg(text));
   }

   //========================Density==================

   if ( option("use_plato", false).toBool() )
   {
      densityUnit = PLATO;
      thingToUnitSystem.insert(Unit::Density,UnitSystems::platoDensityUnitSystem());
   }
   else
   {
      densityUnit = SG;
      thingToUnitSystem.insert(Unit::Density,UnitSystems::sgDensityUnitSystem());
   }

   //=======================Color unit===================
   text = option("color_unit", "srm").toString();
   if( text == "srm" )
   {
      colorUnit = SRM;
      thingToUnitSystem.insert(Unit::Color,UnitSystems::srmColorUnitSystem());
   }
   else if( text == "ebc" )
   {
      colorUnit = EBC;
      thingToUnitSystem.insert(Unit::Color,UnitSystems::ebcColorUnitSystem());
   }
   else
      Brewtarget::logW(QString("Bad color_unit type: %1").arg(text));

   //=======================Date format===================
   dateFormat = (Unit::unitDisplay)option("date_format",Unit::displaySI).toInt();
}

void Brewtarget::saveSystemOptions()
{
   QString text;

   setOption("check_version", checkVersion);
   setOption("last_db_merge_req", lastDbMergeRequest.toString(Qt::ISODate));
   setOption("language", getCurrentLanguage());
   setOption("user_data_dir", userDataDir);
   setOption("weight_unit_system", thingToUnitSystem.value(Unit::Mass)->unitType());
   setOption("volume_unit_system",thingToUnitSystem.value(Unit::Volume)->unitType());
   setOption("temperature_scale", thingToUnitSystem.value(Unit::Temp)->unitType());
   setOption("use_plato", densityUnit == PLATO);
   setOption("date_format", dateFormat);

   switch(ibuFormula)
   {
      case TINSETH:
         setOption("ibu_formula", "tinseth");
         break;
      case RAGER:
         setOption("ibu_formula", "rager");
         break;
      case NOONAN:
         setOption("ibu_formula", "noonan");
         break;
   }

   switch(colorFormula)
   {
      case MOREY:
         setOption("color_formula", "morey");
         break;
      case DANIEL:
         setOption("color_formula", "daniel");
         break;
      case MOSHER:
         setOption("color_formula", "mosher");
         break;
   }

   switch(colorUnit)
   {
      case SRM:
         setOption("color_unit", "srm");
         break;
      case EBC:
         setOption("color_unit", "ebc");
         break;
   }
}

// the defaults come from readSystemOptions. This just fleshes out the hash
// for later use.
void Brewtarget::loadMap()
{
   // ==== mass ====
   thingToUnitSystem.insert(Unit::Mass | Unit::displaySI, UnitSystems::siWeightUnitSystem() );
   thingToUnitSystem.insert(Unit::Mass | Unit::displayUS, UnitSystems::usWeightUnitSystem() );
   thingToUnitSystem.insert(Unit::Mass | Unit::displayImp,UnitSystems::usWeightUnitSystem() );

   // ==== volume ====
   thingToUnitSystem.insert(Unit::Volume | Unit::displaySI, UnitSystems::siVolumeUnitSystem() );
   thingToUnitSystem.insert(Unit::Volume | Unit::displayUS, UnitSystems::usVolumeUnitSystem() );
   thingToUnitSystem.insert(Unit::Volume | Unit::displayImp,UnitSystems::imperialVolumeUnitSystem() );

   // ==== time is empty ==== (this zen moment was free)

   // ==== temp ====
   thingToUnitSystem.insert(Unit::Temp | Unit::displaySI,UnitSystems::celsiusTempUnitSystem() );
   thingToUnitSystem.insert(Unit::Temp | Unit::displayUS,UnitSystems::fahrenheitTempUnitSystem() );

   // ==== color ====
   thingToUnitSystem.insert(Unit::Color | Unit::displaySrm,UnitSystems::srmColorUnitSystem() );
   thingToUnitSystem.insert(Unit::Color | Unit::displayEbc,UnitSystems::ebcColorUnitSystem() );

   // ==== density ====
   thingToUnitSystem.insert(Unit::Density | Unit::displaySg,   UnitSystems::sgDensityUnitSystem() );
   thingToUnitSystem.insert(Unit::Density | Unit::displayPlato,UnitSystems::platoDensityUnitSystem() );
}

void Brewtarget::log(LogType lt, QString message)
{
   QString m;

   if( lt == LogType_WARNING )
      m = QString("WARNING: %1").arg(message);
   else if( lt == LogType_ERROR )
      m = QString("ERROR: %1").arg(message);
   else
      m = message;

   // First, write out to stderr.
   std::cerr << m.toUtf8().constData() << std::endl;
   // Then display it in the GUI's status bar.
   if( _mainWindow && _mainWindow->statusBar() )
      _mainWindow->statusBar()->showMessage(m, 3000);

   // Now, write it to the log file if there is one.
   if( logStream != 0 )
      *logStream << m << "\n";
}

void Brewtarget::logE( QString message )
{
   log( LogType_ERROR, message );
}

void Brewtarget::logW( QString message )
{
   log( LogType_WARNING, message );
}

/* Qt5 changed how QString::toDouble() works in that it will always convert
   in the C locale. We are instructed to use QLocale::toDouble instead, except
   that will never fall back to the C locale. This doesn't really work for us,
   so I am writing a convenience function that emulates the old behavior.
*/
double Brewtarget::toDouble(QString text, bool* ok)
{
   double ret = 0.0;
   bool success = false;
   QLocale sysDefault = QLocale();

   ret = sysDefault.toDouble(text,&success);

   // If we failed, try C conversion
   if ( ! success ) 
      ret = text.toDouble(&success);

   // If we were asked to return the success, return it here.
   if ( ok != NULL )
      *ok = success;

   // Whatever we got, we return it
   return ret;
}

// And a few convenience methods, just for that sweet, sweet syntatic sugar
double Brewtarget::toDouble(const BeerXMLElement* element, QString attribute, QString caller)
{
   double amount = 0.0;
   QString value;
   bool ok = false;

   if ( element->property(attribute.toLatin1().constData()).canConvert(QVariant::String) )
   {
      // Get the amount
      value = element->property(attribute.toLatin1().constData()).toString();
      amount = toDouble( value, &ok );
      if ( ! ok )
         logW( QString("%1 could not convert %2 to double").arg(caller).arg(value));
      // Get the display units and scale
   }
   return amount;
}

double Brewtarget::toDouble(QString text, QString caller)
{
   double ret = 0.0;
   bool success = false;

   ret = toDouble(text,&success);

   if ( ! success ) 
      logW( QString("%1 could not convert %2 to double").arg(caller).arg(text));

   return ret;
}


// Displays "amount" of units "units" in the proper format.
// If "units" is null, just return the amount.
QString Brewtarget::displayAmount( double amount, Unit* units, int precision, Unit::unitDisplay displayUnits, Unit::unitScale displayScale)
{
   int fieldWidth = 0;
   char format = 'f';
   UnitSystem* temp;

   // Check for insane values.
   if( Algorithms::isNan(amount) || Algorithms::isInf(amount) )
      return "-";

   // Special case.
   if( units == 0 )
      return QString("%L1").arg(amount, fieldWidth, format, precision);

   QString SIUnitName = units->getSIUnitName();
   double SIAmount = units->toSI( amount );
   QString ret;

   // convert to the current unit system (s).
   temp = findUnitSystem(units, displayUnits);
   // If we cannot find a unit system
   if ( temp == 0 )
      ret = QString("%L1 %2").arg(SIAmount, fieldWidth, format, precision).arg(SIUnitName);
   else
      ret = temp->displayAmount( amount, units, precision, displayScale );

   return ret;
}

QString Brewtarget::displayAmount(BeerXMLElement* element, QObject* object, QString attribute, Unit* units, int precision )
{
   double amount = 0.0;
   QString value;
   bool ok = false;
   Unit::unitScale dispScale;
   Unit::unitDisplay dispUnit;

   if ( element->property(attribute.toLatin1().constData()).canConvert(QVariant::Double) )
   {
      // Get the amount
      value = element->property(attribute.toLatin1().constData()).toString();
      amount = toDouble( value, &ok );
      if ( ! ok )
         logW( QString("Brewtarget::displayAmount(BeerXMLElement*,QObject*,QString,Unit*,int) could not convert %1 to double").arg(value));
      // Get the display units and scale
      dispUnit  = (Unit::unitDisplay)option(attribute, Unit::noUnit,  object->objectName(), UNIT).toInt();
      dispScale = (Unit::unitScale)option(  attribute, Unit::noScale, object->objectName(), SCALE).toInt();

      return displayAmount(amount, units, precision, dispUnit, dispScale);
   }
   else
      return "?";

}

QString Brewtarget::displayAmount(double amt, QString section, QString attribute, Unit* units, int precision )
{
   Unit::unitScale dispScale;
   Unit::unitDisplay dispUnit;

   // Get the display units and scale
   dispUnit  = (Unit::unitDisplay)Brewtarget::option(attribute, Unit::noUnit,  section, UNIT).toInt();
   dispScale = (Unit::unitScale)Brewtarget::option(  attribute, Unit::noScale, section, SCALE).toInt();

   return displayAmount(amt, units, precision, dispUnit, dispScale);

}

double Brewtarget::amountDisplay( double amount, Unit* units, int precision, Unit::unitDisplay displayUnits, Unit::unitScale displayScale)
{
   UnitSystem* temp;

   // Check for insane values.
   if( Algorithms::isNan(amount) || Algorithms::isInf(amount) )
      return -1.0;

   // Special case.
   if( units == 0 )
      return amount;

   QString SIUnitName = units->getSIUnitName();
   double SIAmount = units->toSI( amount );
   double ret;

   // convert to the current unit system (s).
   temp = findUnitSystem(units, displayUnits);
   // If we cannot find a unit system
   if ( temp == 0 )
      ret = SIAmount;
   else
      ret = temp->amountDisplay( amount, units, displayScale );

   return ret;
}

double Brewtarget::amountDisplay(BeerXMLElement* element, QObject* object, QString attribute, Unit* units, int precision )
{
   double amount = 0.0;
   QString value;
   bool ok = false;
   Unit::unitScale dispScale;
   Unit::unitDisplay dispUnit;

   if ( element->property(attribute.toLatin1().constData()).canConvert(QVariant::Double) )
   {
      // Get the amount
      value = element->property(attribute.toLatin1().constData()).toString();
      amount = toDouble( value, &ok );
      if ( ! ok )
         logW( QString("Brewtarget::amountDisplay(BeerXMLElement*,QObject*,QString,Unit*,int) could not convert %1 to double").arg(value));
      // Get the display units and scale
      dispUnit  = (Unit::unitDisplay)option(attribute, Unit::noUnit,  object->objectName(), UNIT).toInt();
      dispScale = (Unit::unitScale)option(  attribute, Unit::noScale, object->objectName(), SCALE).toInt();

      return amountDisplay(amount, units, precision, dispUnit, dispScale);
   }
   else
      return -1.0;
}

UnitSystem* Brewtarget::findUnitSystem(Unit* unit, Unit::unitDisplay display)
{
   if ( ! unit )
      return 0;

   int key = unit->getUnitType();

   // noUnit means get the default UnitSystem. Through little planning on my
   // part, it happens that is equivalent to just the unitType
   if ( display != Unit::noUnit )
      key |= display;

   if ( thingToUnitSystem.contains( key ) )
      return thingToUnitSystem.value(key);

   return 0;
}

void Brewtarget::getThicknessUnits( Unit** volumeUnit, Unit** weightUnit )
{
   *volumeUnit = thingToUnitSystem.value(Unit::Volume | Unit::displayDef)->thicknessUnit();
   *weightUnit = thingToUnitSystem.value(Unit::Mass   | Unit::displayDef)->thicknessUnit();
}

QString Brewtarget::displayThickness( double thick_lkg, bool showUnits )
{
   int fieldWidth = 0;
   char format = 'f';
   int precision = 2;

   Unit* volUnit    = thingToUnitSystem.value(Unit::Volume | Unit::displayDef)->thicknessUnit();
   Unit* weightUnit = thingToUnitSystem.value(Unit::Mass   | Unit::displayDef)->thicknessUnit();

   double num = volUnit->fromSI(thick_lkg);
   double den = weightUnit->fromSI(1.0);

   if( showUnits )
      return QString("%L1 %2/%3").arg(num/den, fieldWidth, format, precision).arg(volUnit->getUnitName()).arg(weightUnit->getUnitName());
   else
      return QString("%L1").arg(num/den, fieldWidth, format, precision).arg(volUnit->getUnitName()).arg(weightUnit->getUnitName());
}

double Brewtarget::qStringToSI(QString qstr, Unit* unit, Unit::unitDisplay dispUnit, bool force)
{
   UnitSystem* temp = findUnitSystem(unit, dispUnit);
   return temp->qstringToSI(qstr,temp->unit(),force);
}

QString Brewtarget::ibuFormulaName()
{
   switch ( ibuFormula )
   {
      case Brewtarget::TINSETH:
         return "Tinseth";
      case Brewtarget::RAGER:
         return "Rager";
      case Brewtarget::NOONAN:
         return "Noonan";
   }
  return tr("Unknown");
}

QString Brewtarget::colorFormulaName()
{

   switch( Brewtarget::colorFormula )
   {
      case Brewtarget::MOREY:
         return "Morey";
      case Brewtarget::DANIEL:
         return "Daniels";
      case Brewtarget::MOSHER:
         return "Mosher";
   }
   return tr("Unknown");
}

QString Brewtarget::colorUnitName(Unit::unitDisplay display)
{
   if ( display == Unit::noUnit )
      display = getColorUnit();

   if ( display == Unit::displaySrm )
      return QString("SRM");
   else
      return QString("EBC");
}

bool Brewtarget::hasUnits(QString qstr)
{
   // accepts X,XXX.YZ (or X.XXX,YZ for EU users) as well as .YZ (or ,YZ) followed by
   // some unit string
   QString decimal = QRegExp::escape( QLocale::system().decimalPoint());
   QString grouping = QRegExp::escape(QLocale::system().groupSeparator());

   QRegExp amtUnit("((?:\\d+" + grouping + ")?\\d+(?:" + decimal + "\\d+)?|" + decimal + "\\d+)\\s*(\\w+)?");
   amtUnit.indexIn(qstr);

   return amtUnit.cap(2).size() > 0;
}

QPair<double,double> Brewtarget::displayRange(BeerXMLElement* element, QObject *object, QString attribute, RangeType _type)
{
   QPair<double,double> range;
   QString minName = QString("%1%2").arg(attribute).arg("Min");
   QString maxName = QString("%1%2").arg(attribute).arg("Max");

   if ( ! element ) {
      range.first  = 0.0;
      range.second = 100.0;
   }
   else if ( _type != DENSITY )
   {
      range.first  = amountDisplay(element, object, "colorMin_srm", Units::srm,0);
      range.second = amountDisplay(element, object, "colorMax_srm", Units::srm,0);
   }
   else
   {
      range.first  = amountDisplay(element, object, minName, Units::sp_grav,0);
      range.second = amountDisplay(element, object, maxName, Units::sp_grav,0);
   }

   return range;
}

QPair<double,double> Brewtarget::displayRange(QObject *object, QString attribute, double min, double max, RangeType _type)
{
   QPair<double,double> range;
   Unit::unitDisplay displayUnit;

   displayUnit = (Unit::unitDisplay)option(attribute, Unit::noUnit, object->objectName(), UNIT).toInt();

   if ( _type == DENSITY )
   {
      range.first  = amountDisplay(min, Units::sp_grav, 0, displayUnit );
      range.second = amountDisplay(max, Units::sp_grav, 0, displayUnit );
   }
   else
   {
      range.first  = amountDisplay(min, Units::srm, 0, displayUnit );
      range.second = amountDisplay(max, Units::srm, 0, displayUnit );
   }

   return range;
}

QString Brewtarget::displayDate(QDate const& date )
{
   QLocale loc(QLocale::system().name());
   return date.toString(loc.dateFormat(QLocale::ShortFormat));
}

QString Brewtarget::displayDateUserFormated(QDate const &date) {
   QString format;
   switch (Brewtarget::getDateFormat()) {
      case Unit::displayUS:
         format = "MM-dd-yyyy";
         break;
      case Unit::displayImp:
         format = "dd-MM-yyyy";
         break;
      default:
      case Unit::displaySI:
         format = "yyyy-MM-dd";
   }
   return date.toString(format);
}

bool Brewtarget::hasOption(QString attribute, const QString section, iUnitOps ops)
{
   QString name;

   if ( section.isNull() )
      name = attribute;
   else
      name = generateName(attribute,section,ops);

   return QSettings().contains(name);
}

void Brewtarget::setOption(QString attribute, QVariant value, const QString section, iUnitOps ops)
{
   QString name;

   if ( section.isNull() )
      name = attribute;
   else
      name = generateName(attribute,section,ops);


   QSettings().setValue(name,value);
}

QVariant Brewtarget::option(QString attribute, QVariant default_value, QString section, iUnitOps ops)
{
   QString name;

   if ( section.isNull() )
      name = attribute;
   else
      name = generateName(attribute,section,ops);

   return QSettings().value(name,default_value);
}

void Brewtarget::removeOption(QString attribute)
{
   if ( hasOption(attribute) )
        QSettings().remove(attribute);
}

QString Brewtarget::generateName(QString attribute, const QString section, iUnitOps ops)
{
   QString ret = QString("%1/%2").arg(section).arg(attribute);

   if ( ops != NOOP )
      ret += ops == UNIT ? "_unit" : "_scale";

   return ret;
}

// These are used in at least two places. I hate cut'n'paste coding so I am
// putting them here.
// I use a QActionGroup to make sure only one button is ever selected at once.
// It allows me to cache the menus later and speeds the response time up.
QMenu* Brewtarget::setupColorMenu(QWidget* parent, Unit::unitDisplay unit)
{
   QMenu* menu = new QMenu(parent);
   QActionGroup* qgrp = new QActionGroup(parent);

   generateAction(menu, tr("Default"), Unit::noUnit, unit, qgrp);
   generateAction(menu, tr("EBC"), Unit::displayEbc, unit, qgrp);
   generateAction(menu, tr("SRM"), Unit::displaySrm, unit, qgrp);

   return menu;
}

QMenu* Brewtarget::setupDateMenu(QWidget* parent, Unit::unitDisplay unit)
{
   QMenu* menu = new QMenu(parent);
   QActionGroup* qgrp = new QActionGroup(parent);

   generateAction(menu, tr("Default"),    Unit::noUnit,     unit, qgrp);
   generateAction(menu, tr("YYYY-mm-dd"), Unit::displaySI,  unit, qgrp);
   generateAction(menu, tr("dd-mm-YYYY"), Unit::displayImp, unit, qgrp);
   generateAction(menu, tr("mm-dd-YYYY"), Unit::displayUS,  unit, qgrp);

   return menu;
}

QMenu* Brewtarget::setupDensityMenu(QWidget* parent, Unit::unitDisplay unit)
{
   QMenu* menu = new QMenu(parent);
   QActionGroup* qgrp = new QActionGroup(parent);

   generateAction(menu, tr("Default"), Unit::noUnit, unit, qgrp);
   generateAction(menu, tr("Plato"), Unit::displayPlato, unit, qgrp);
   generateAction(menu, tr("Specific Gravity"), Unit::displaySg, unit, qgrp);

   return menu;
}

QMenu* Brewtarget::setupMassMenu(QWidget* parent, Unit::unitDisplay unit, Unit::unitScale scale, bool generateScale)
{
   QMenu* menu = new QMenu(parent);
   QMenu* sMenu;
   QActionGroup* qgrp = new QActionGroup(parent);

   generateAction(menu, tr("Default"), Unit::noUnit, unit, qgrp);
   generateAction(menu, tr("SI"), Unit::displaySI, unit, qgrp);
   generateAction(menu, tr("US Customary"), Unit::displayUS, unit, qgrp);

   // Some places can't do scale -- like yeast tables and misc tables because
   // they can be mixed. It doesn't stop the unit selection from working, but
   // the scale menus don't make sense
   if ( generateScale == false )
      return menu;

   if ( unit == Unit::noUnit )
   {
      if ( thingToUnitSystem.value(Unit::Mass) == UnitSystems::usWeightUnitSystem() )
         unit = Unit::displayUS;
      else
         unit = Unit::displaySI;
   }

   sMenu = new QMenu(menu);
   QActionGroup* qsgrp = new QActionGroup(menu);
   switch(unit)
   {
      case Unit::displaySI:
         generateAction(sMenu, tr("Default"), Unit::noScale, scale,qsgrp);
         generateAction(sMenu, tr("Milligrams"), Unit::scaleExtraSmall, scale,qsgrp);
         generateAction(sMenu, tr("Grams"), Unit::scaleSmall, scale,qsgrp);
         generateAction(sMenu, tr("Kilograms"), Unit::scaleMedium, scale,qsgrp);
         break;
      default:
         generateAction(sMenu, tr("Default"), Unit::noScale, scale,qsgrp);
         generateAction(sMenu, tr("Ounces"), Unit::scaleExtraSmall, scale,qsgrp);
         generateAction(sMenu, tr("Pounds"), Unit::scaleSmall, scale,qsgrp);
         break;
   }
   sMenu->setTitle(tr("Scale"));
   menu->addMenu(sMenu);

   return menu;
}

QMenu* Brewtarget::setupTemperatureMenu(QWidget* parent, Unit::unitDisplay unit)
{
   QMenu* menu = new QMenu(parent);
   QActionGroup* qgrp = new QActionGroup(parent);

   generateAction(menu, tr("Default"), Unit::noUnit, unit, qgrp);
   generateAction(menu, tr("Celsius"), Unit::displaySI, unit, qgrp);
   generateAction(menu, tr("Fahrenheit"), Unit::displayUS, unit, qgrp);

   return menu;
}

// Time menus only have scale
QMenu* Brewtarget::setupTimeMenu(QWidget* parent, Unit::unitScale scale)
{
   QMenu* menu = new QMenu(parent);
   QMenu* sMenu = new QMenu(menu);
   QActionGroup* qgrp = new QActionGroup(parent);

   generateAction(sMenu, tr("Default"), Unit::noScale, scale, qgrp);
   generateAction(sMenu, tr("Seconds"), Unit::scaleExtraSmall, scale, qgrp);
   generateAction(sMenu, tr("Minutes"), Unit::scaleSmall, scale, qgrp);
   generateAction(sMenu, tr("Hours"),   Unit::scaleMedium, scale, qgrp);
   generateAction(sMenu, tr("Days"),    Unit::scaleLarge, scale, qgrp);

   sMenu->setTitle(tr("Scale"));
   menu->addMenu(sMenu);

   return menu;
}

QMenu* Brewtarget::setupVolumeMenu(QWidget* parent, Unit::unitDisplay unit, Unit::unitScale scale, bool generateScale)
{
   QMenu* menu = new QMenu(parent);
   QActionGroup* qgrp = new QActionGroup(parent);
   QMenu* sMenu;

   generateAction(menu, tr("Default"), Unit::noUnit, unit, qgrp);
   generateAction(menu, tr("SI"), Unit::displaySI, unit, qgrp);
   generateAction(menu, tr("US Customary"), Unit::displayUS, unit, qgrp);
   generateAction(menu, tr("British Imperial"), Unit::displayImp, unit, qgrp);

   if ( generateScale == false )
      return menu;

   if ( unit == Unit::noUnit )
   {
      if ( thingToUnitSystem.value(Unit::Volume) == UnitSystems::usVolumeUnitSystem() )
         unit = Unit::displayUS;
      else if ( thingToUnitSystem.value(Unit::Volume) == UnitSystems::imperialVolumeUnitSystem() )
         unit = Unit::displayImp;
      else
         unit = Unit::displaySI;
   }


   sMenu = new QMenu(menu);
   QActionGroup* qsgrp = new QActionGroup(menu);
   switch(unit)
   {
      case Unit::displaySI:
         generateAction(sMenu, tr("Default"), Unit::noScale, scale,qsgrp);
         generateAction(sMenu, tr("MilliLiters"), Unit::scaleExtraSmall, scale,qsgrp);
         generateAction(sMenu, tr("Liters"), Unit::scaleSmall, scale,qsgrp);
         break;
        // I can cheat because Imperial and US use the same names
      default:
         generateAction(sMenu, tr("Default"), Unit::noScale, scale,qsgrp);
         generateAction(sMenu, tr("Teaspoons"), Unit::scaleExtraSmall, scale,qsgrp);
         generateAction(sMenu, tr("Tablespoons"), Unit::scaleSmall, scale,qsgrp);
         generateAction(sMenu, tr("Cups"), Unit::scaleMedium, scale,qsgrp);
         generateAction(sMenu, tr("Quarts"), Unit::scaleLarge, scale,qsgrp);
         generateAction(sMenu, tr("Gallons"), Unit::scaleExtraLarge, scale,qsgrp);
         generateAction(sMenu, tr("Barrels"), Unit::scaleHuge, scale,qsgrp);
         break;
   }
   sMenu->setTitle(tr("Scale"));
   menu->addMenu(sMenu);

   return menu;
}

void Brewtarget::generateAction(QMenu* menu, QString text, QVariant data, QVariant currentVal, QActionGroup* qgrp)
{
   QAction* action = new QAction(menu);

   action->setText(text);
   action->setData(data);
   action->setCheckable(true);
   action->setChecked(currentVal == data);;
   if ( qgrp )
      qgrp->addAction(action);

  menu->addAction(action);
}

MainWindow* Brewtarget::mainWindow()
{
   return _mainWindow;
}
