#include "MantidQtCustomInterfaces/Indirect/ConvFit.h"

#include "MantidQtCustomInterfaces/UserInputValidator.h"

#include "MantidQtMantidWidgets/RangeSelector.h"

#include "MantidAPI/AlgorithmManager.h"
#include "MantidAPI/FunctionDomain1D.h"
#include "MantidAPI/FunctionFactory.h"
#include "MantidAPI/TextAxis.h"

#include <QDoubleValidator>
#include <QFileInfo>
#include <QMenu>

#include <qwt_plot.h>
#include <qwt_plot_curve.h>

using namespace Mantid::API;

namespace {
Mantid::Kernel::Logger g_log("ConvFit");
}

namespace MantidQt {
namespace CustomInterfaces {
namespace IDA {

ConvFit::ConvFit(QWidget *parent)
    : IndirectDataAnalysisTab(parent), m_stringManager(NULL), m_cfTree(NULL),
      m_fixedProps(), m_cfInputWS(), m_cfInputWSName(), m_confitResFileType() {
  m_uiForm.setupUi(parent);
}

void ConvFit::setup() {
  // Create Property Managers
  m_stringManager = new QtStringPropertyManager();
  m_runMin = 0;
  m_runMax = 0;

  // Initialise fitTypeStrings
  m_fitStrings = QStringList() << ""
                               << "1L"
                               << "2L"
                               << "IDS"
                               << "IDC"
                               << "EDS"
                               << "EDC"
                               << "SFT";

  // Create TreeProperty Widget
  m_cfTree = new QtTreePropertyBrowser();
  m_uiForm.properties->addWidget(m_cfTree);

  // add factories to managers
  m_cfTree->setFactoryForManager(m_blnManager, m_blnEdFac);
  m_cfTree->setFactoryForManager(m_dblManager, m_dblEdFac);

  // Create Range Selectors
  auto fitRangeSelector = m_uiForm.ppPlot->addRangeSelector("ConvFitRange");
  auto backRangeSelector = m_uiForm.ppPlot->addRangeSelector(
      "ConvFitBackRange", MantidWidgets::RangeSelector::YSINGLE);
  auto hwhmRangeSelector = m_uiForm.ppPlot->addRangeSelector("ConvFitHWHM");
  backRangeSelector->setColour(Qt::darkGreen);
  backRangeSelector->setRange(0.0, 1.0);
  hwhmRangeSelector->setColour(Qt::red);

  // Populate Property Widget

  // Option to convolve members
  m_properties["Convolve"] = m_blnManager->addProperty("Convolve");
  m_cfTree->addProperty(m_properties["Convolve"]);
  m_blnManager->setValue(m_properties["Convolve"], true);

  // Max iterations option
  m_properties["MaxIterations"] = m_dblManager->addProperty("Max Iterations");
  m_dblManager->setDecimals(m_properties["MaxIterations"], 0);
  m_dblManager->setValue(m_properties["MaxIterations"], 500);
  m_cfTree->addProperty(m_properties["MaxIterations"]);

  // Fitting range
  m_properties["FitRange"] = m_grpManager->addProperty("Fitting Range");
  m_properties["StartX"] = m_dblManager->addProperty("StartX");
  m_dblManager->setDecimals(m_properties["StartX"], NUM_DECIMALS);
  m_properties["EndX"] = m_dblManager->addProperty("EndX");
  m_dblManager->setDecimals(m_properties["EndX"], NUM_DECIMALS);
  m_properties["FitRange"]->addSubProperty(m_properties["StartX"]);
  m_properties["FitRange"]->addSubProperty(m_properties["EndX"]);
  m_cfTree->addProperty(m_properties["FitRange"]);

  // FABADA
  m_properties["FABADA"] = m_grpManager->addProperty("Bayesian");
  m_properties["UseFABADA"] = m_blnManager->addProperty("Use FABADA");
  m_properties["FABADA"]->addSubProperty(m_properties["UseFABADA"]);
  m_properties["OutputFABADAChain"] = m_blnManager->addProperty("Output Chain");
  m_properties["FABADAChainLength"] = m_dblManager->addProperty("Chain Length");
  m_dblManager->setDecimals(m_properties["FABADAChainLength"], 0);
  m_dblManager->setValue(m_properties["FABADAChainLength"], 1000000);
  m_properties["FABADAConvergenceCriteria"] =
      m_dblManager->addProperty("Convergence Criteria");
  m_dblManager->setValue(m_properties["FABADAConvergenceCriteria"], 0.1);
  m_properties["FABADAJumpAcceptanceRate"] =
      m_dblManager->addProperty("Acceptance Rate");
  m_dblManager->setValue(m_properties["FABADAJumpAcceptanceRate"], 0.25);
  m_cfTree->addProperty(m_properties["FABADA"]);

  // Background type
  m_properties["LinearBackground"] = m_grpManager->addProperty("Background");
  m_properties["BGA0"] = m_dblManager->addProperty("A0");
  m_dblManager->setDecimals(m_properties["BGA0"], NUM_DECIMALS);
  m_properties["BGA1"] = m_dblManager->addProperty("A1");
  m_dblManager->setDecimals(m_properties["BGA1"], NUM_DECIMALS);
  m_properties["LinearBackground"]->addSubProperty(m_properties["BGA0"]);
  m_properties["LinearBackground"]->addSubProperty(m_properties["BGA1"]);
  m_cfTree->addProperty(m_properties["LinearBackground"]);

  // Delta Function
  m_properties["DeltaFunction"] = m_grpManager->addProperty("Delta Function");
  m_properties["UseDeltaFunc"] = m_blnManager->addProperty("Use");
  m_properties["DeltaHeight"] = m_dblManager->addProperty("Height");
  m_dblManager->setDecimals(m_properties["DeltaHeight"], NUM_DECIMALS);
  m_properties["DeltaFunction"]->addSubProperty(m_properties["UseDeltaFunc"]);
  m_cfTree->addProperty(m_properties["DeltaFunction"]);

  // Fit functions
  m_properties["Lorentzian1"] = createFitType("Lorentzian 1");
  m_properties["Lorentzian2"] = createFitType("Lorentzian 2");
  m_properties["DiffSphere"] = createFitType("DiffSphere");
  m_properties["DiffRotDiscreteCircle"] =
      createFitType("DiffRotDiscreteCircle");
  m_properties["ElasticDiffSphere"] = createFitType("ElasticDiffSphere");
  m_properties["ElasticDiffRotDiscreteCircle"] =
      createFitType("ElasticDiffRotDiscreteCircle");
  m_properties["InelasticDiffSphere"] = createFitType("InelasticDiffSphere");
  m_properties["InelasticDiffRotDiscreteCircle"] =
      createFitType("InelasticDiffRotDiscreteCircle");
  m_properties["StretchedExpFT"] = createFitType("StretchedExpFT");

  // Update fit parameters in browser when function is selected
  connect(m_uiForm.cbFitType, SIGNAL(currentIndexChanged(QString)), this,
          SLOT(fitFunctionSelected(const QString &)));
  fitFunctionSelected(m_uiForm.cbFitType->currentText());

  m_uiForm.leTempCorrection->setValidator(new QDoubleValidator(m_parentWidget));

  // Connections
  connect(fitRangeSelector, SIGNAL(minValueChanged(double)), this,
          SLOT(minChanged(double)));
  connect(fitRangeSelector, SIGNAL(maxValueChanged(double)), this,
          SLOT(maxChanged(double)));
  connect(backRangeSelector, SIGNAL(minValueChanged(double)), this,
          SLOT(backgLevel(double)));
  connect(hwhmRangeSelector, SIGNAL(minValueChanged(double)), this,
          SLOT(hwhmChanged(double)));
  connect(hwhmRangeSelector, SIGNAL(maxValueChanged(double)), this,
          SLOT(hwhmChanged(double)));
  connect(m_dblManager, SIGNAL(valueChanged(QtProperty *, double)), this,
          SLOT(updateRS(QtProperty *, double)));
  connect(m_blnManager, SIGNAL(valueChanged(QtProperty *, bool)), this,
          SLOT(checkBoxUpdate(QtProperty *, bool)));
  connect(m_uiForm.ckTempCorrection, SIGNAL(toggled(bool)),
          m_uiForm.leTempCorrection, SLOT(setEnabled(bool)));

  // Update guess curve when certain things happen
  connect(m_dblManager, SIGNAL(propertyChanged(QtProperty *)), this,
          SLOT(plotGuess()));
  connect(m_uiForm.cbFitType, SIGNAL(currentIndexChanged(int)), this,
          SLOT(plotGuess()));
  connect(m_uiForm.ckPlotGuess, SIGNAL(stateChanged(int)), this,
          SLOT(plotGuess()));

  // Have FWHM Range linked to Fit Start/End Range
  connect(fitRangeSelector, SIGNAL(rangeChanged(double, double)),
          hwhmRangeSelector, SLOT(setRange(double, double)));
  hwhmRangeSelector->setRange(-1.0, 1.0);
  hwhmUpdateRS(0.02);

  typeSelection(m_uiForm.cbFitType->currentIndex());
  bgTypeSelection(m_uiForm.cbBackground->currentIndex());

  // Replot input automatically when file / spec no changes
  connect(m_uiForm.spPlotSpectrum, SIGNAL(valueChanged(int)), this,
          SLOT(updatePlot()));
  connect(m_uiForm.dsSampleInput, SIGNAL(dataReady(const QString &)), this,
          SLOT(newDataLoaded(const QString &)));

  connect(m_uiForm.dsSampleInput, SIGNAL(dataReady(const QString &)), this,
          SLOT(extendResolutionWorkspace()));
  connect(m_uiForm.dsResInput, SIGNAL(dataReady(const QString &)), this,
          SLOT(extendResolutionWorkspace()));

  connect(m_uiForm.spSpectraMin, SIGNAL(valueChanged(int)), this,
          SLOT(specMinChanged(int)));
  connect(m_uiForm.spSpectraMax, SIGNAL(valueChanged(int)), this,
          SLOT(specMaxChanged(int)));

  connect(m_uiForm.cbFitType, SIGNAL(currentIndexChanged(int)), this,
          SLOT(typeSelection(int)));
  connect(m_uiForm.cbBackground, SIGNAL(currentIndexChanged(int)), this,
          SLOT(bgTypeSelection(int)));
  connect(m_uiForm.pbSingleFit, SIGNAL(clicked()), this, SLOT(singleFit()));

  // Context menu
  m_cfTree->setContextMenuPolicy(Qt::CustomContextMenu);
  connect(m_cfTree, SIGNAL(customContextMenuRequested(const QPoint &)), this,
          SLOT(fitContextMenu(const QPoint &)));

  // Tie
  connect(m_uiForm.cbFitType, SIGNAL(currentIndexChanged(QString)),
          SLOT(showTieCheckbox(QString)));
  showTieCheckbox(m_uiForm.cbFitType->currentText());

  m_previousFit = m_uiForm.cbFitType->currentText();

  updatePlotOptions();
}

/**
 * Handles the initial set up and running of the ConvolutionFitSequential
 * algorithm
 */
void ConvFit::run() {
  if (m_cfInputWS == NULL) {
    g_log.error("No workspace loaded");
    return;
  }

  QString fitType = fitTypeString();
  QString bgType = backgroundString();

  if (fitType == "") {
    g_log.error("No fit type defined");
  }

  bool useTies = m_uiForm.ckTieCentres->isChecked();
  QString ties = (useTies ? "True" : "False");

  CompositeFunction_sptr func = createFunction(useTies);
  std::string function = std::string(func->asString());
  std::string stX = m_properties["StartX"]->valueText().toStdString();
  std::string enX = m_properties["EndX"]->valueText().toStdString();
  m_runMin = m_uiForm.spSpectraMin->value();
  m_runMax = m_uiForm.spSpectraMax->value();
  std::string specMin = m_uiForm.spSpectraMin->text().toStdString();
  std::string specMax = m_uiForm.spSpectraMax->text().toStdString();
  int maxIterations =
      static_cast<int>(m_dblManager->value(m_properties["MaxIterations"]));

  // Construct expected name
  m_baseName = QString::fromStdString(m_cfInputWS->getName());
  int pos = m_baseName.lastIndexOf("_");
  if (pos != -1) {
    m_baseName = m_baseName.left(pos + 1);
  }
  m_baseName += "conv_";
  if (m_blnManager->value(m_properties["UseDeltaFunc"])) {
    m_baseName += "Delta";
  }
  int fitIndex = m_uiForm.cbFitType->currentIndex();
  if (fitIndex < 3 && fitIndex != 0) {
    m_baseName += QString::number(fitIndex);
    m_baseName += "L";
  } else {
    m_baseName += convertFuncToShort(m_uiForm.cbFitType->currentText());
  }
  m_baseName +=
      convertBackToShort(m_uiForm.cbBackground->currentText().toStdString()) +
      "_s";
  m_baseName += QString::fromStdString(specMin);
  m_baseName += "_to_";
  m_baseName += QString::fromStdString(specMax);

  // Run ConvolutionFitSequential Algorithm
  IAlgorithm_sptr cfs =
      AlgorithmManager::Instance().create("ConvolutionFitSequential");
  cfs->initialize();

  cfs->setProperty("InputWorkspace", m_cfInputWS->getName());
  cfs->setProperty("Function", function);
  cfs->setProperty("BackgroundType",
                   m_uiForm.cbBackground->currentText().toStdString());
  cfs->setProperty("StartX", stX);
  cfs->setProperty("EndX", enX);
  cfs->setProperty("SpecMin", specMin);
  cfs->setProperty("SpecMax", specMax);
  cfs->setProperty("Convolve", true);
  cfs->setProperty("Minimizer",
                   minimizerString("$outputname_$wsindex").toStdString());
  cfs->setProperty("MaxIterations", maxIterations);
  m_batchAlgoRunner->addAlgorithm(cfs);
  connect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this,
          SLOT(algorithmComplete(bool)));
  m_batchAlgoRunner->executeBatchAsync();
}

/**
 * Handles completion of the ConvolutionFitSequential algorithm.
 *
 * @param error True if the algorithm was stopped due to error, false otherwise
 */
void ConvFit::algorithmComplete(bool error) {
  disconnect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this,
             SLOT(algorithmComplete(bool)));

  if (error)
    return;

  std::string resultName = m_baseName.toStdString() + "_Result";
  MatrixWorkspace_sptr resultWs =
      AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(resultName);

  const bool save = m_uiForm.ckSave->isChecked();

  // Handle Save file
  if (save) {
    QString saveDir = QString::fromStdString(
        Mantid::Kernel::ConfigService::Instance().getString(
            "defaultsave.directory"));
    // Check validity of save path
    QString QresultWsName = QString::fromStdString(resultWs->getName());
    QString fullPath = saveDir.append(QresultWsName).append(".nxs");
    addSaveWorkspaceToQueue(QresultWsName, fullPath);
  }

  std::string plot = m_uiForm.cbPlotType->currentText().toStdString();

  // Handle plot result
  if (!(plot.compare("None") == 0)) {
    if (plot.compare("All") == 0) {
      int specEnd = (int)resultWs->getNumberHistograms();
      for (int i = 0; i < specEnd; i++) {
        IndirectTab::plotSpectrum(QString::fromStdString(resultWs->getName()),
                                  i, i);
      }
    } else {
      // -1 to account for None in dropDown
      int specNumber = m_uiForm.cbPlotType->currentIndex() - 1;
      IndirectTab::plotSpectrum(QString::fromStdString(resultWs->getName()),
                                specNumber, specNumber);
    }
  }

  // Handle Temperature logs
  if (m_uiForm.ckTempCorrection->isChecked()) {
    QString temperature = m_uiForm.leTempCorrection->text();
    double temp = 0.0;
    if (temperature.toStdString().compare("") != 0) {
      temp = temperature.toDouble();
    }

    if (temp != 0.0) {
      // Obtain WorkspaceGroup from ADS
      std::string groupName = m_baseName.toStdString() + "_Workspaces";
      WorkspaceGroup_sptr groupWs =
          AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(groupName);

      auto addSample = AlgorithmManager::Instance().create("AddSampleLog");
      addSample->setProperty("Workspace", resultWs);
      addSample->setProperty("LogName", "temperature_value");
      addSample->setProperty("LogText", temperature.toStdString());
      addSample->setProperty("LogType", "Number");
      addSample->execute();
      addSample->setProperty("Workspace", resultWs);
      addSample->setProperty("LogName", "temperature_correction");
      addSample->setProperty("LogText", "true");
      addSample->setProperty("LogType", "String");
      addSample->execute();
      addSample->setProperty("Workspace", groupWs);
      addSample->setProperty("LogName", "temperature_value");
      addSample->setProperty("LogText", temperature.toStdString());
      addSample->setProperty("LogType", "Number");
      addSample->execute();
      addSample->setProperty("Workspace", groupWs);
      addSample->setProperty("LogName", "temperature_correction");
      addSample->setProperty("LogText", "true");
      addSample->setProperty("LogType", "String");
      addSample->execute();
    }
  }
  m_batchAlgoRunner->executeBatchAsync();
  updatePlot();
}

/**
 * Validates the user's inputs in the ConvFit tab.
 * @return If the validation was successful
 */
bool ConvFit::validate() {
  UserInputValidator uiv;

  uiv.checkDataSelectorIsValid("Sample", m_uiForm.dsSampleInput);
  uiv.checkDataSelectorIsValid("Resolution", m_uiForm.dsResInput);

  auto range = std::make_pair(m_dblManager->value(m_properties["StartX"]),
                              m_dblManager->value(m_properties["EndX"]));
  uiv.checkValidRange("Fitting Range", range);

  // Enforce the rule that at least one fit is needed; either a delta function,
  // one or two lorentzian functions,
  // or both.  (The resolution function must be convolved with a model.)
  if (m_uiForm.cbFitType->currentIndex() == 0 &&
      !m_blnManager->value(m_properties["UseDeltaFunc"]))
    uiv.addErrorMessage("No fit function has been selected.");

  QString error = uiv.generateErrorMessage();
  showMessageBox(error);

  return error.isEmpty();
}

/**
 * Reads in settings files
 * @param settings The name of the QSettings object to retrieve data from
 */
void ConvFit::loadSettings(const QSettings &settings) {
  m_uiForm.dsSampleInput->readSettings(settings.group());
  m_uiForm.dsResInput->readSettings(settings.group());
}

/**
 * Called when new data has been loaded by the data selector.
 *
 * Configures ranges for spin boxes before raw plot is done.
 *
 * @param wsName Name of new workspace loaded
 */
void ConvFit::newDataLoaded(const QString wsName) {
  m_cfInputWSName = wsName;
  m_cfInputWS = AnalysisDataService::Instance().retrieveWS<MatrixWorkspace>(
      m_cfInputWSName.toStdString());

  int maxSpecIndex = static_cast<int>(m_cfInputWS->getNumberHistograms()) - 1;

  m_uiForm.spPlotSpectrum->setMaximum(maxSpecIndex);
  m_uiForm.spPlotSpectrum->setMinimum(0);
  m_uiForm.spPlotSpectrum->setValue(0);

  m_uiForm.spSpectraMin->setMaximum(maxSpecIndex);
  m_uiForm.spSpectraMin->setMinimum(0);

  m_uiForm.spSpectraMax->setMaximum(maxSpecIndex);
  m_uiForm.spSpectraMax->setMinimum(0);
  m_uiForm.spSpectraMax->setValue(maxSpecIndex);

  updatePlot();
}

/**
 * Create a resolution workspace with the same number of histograms as in the
 * sample.
 *
 * Needed to allow DiffSphere and DiffRotDiscreteCircle fit functions to work as
 * they need
 * to have the WorkspaceIndex attribute set.
 */
void ConvFit::extendResolutionWorkspace() {
  if (m_cfInputWS && m_uiForm.dsResInput->isValid()) {
    const QString resWsName = m_uiForm.dsResInput->getCurrentDataName();
    API::BatchAlgorithmRunner::AlgorithmRuntimeProps appendProps;
    appendProps["InputWorkspace1"] = "__ConvFit_Resolution";

    size_t numHist = m_cfInputWS->getNumberHistograms();
    for (size_t i = 0; i < numHist; i++) {
      IAlgorithm_sptr appendAlg =
          AlgorithmManager::Instance().create("AppendSpectra");
      appendAlg->initialize();
      appendAlg->setProperty("InputWorkspace2", resWsName.toStdString());
      appendAlg->setProperty("OutputWorkspace", "__ConvFit_Resolution");

      if (i == 0) {
        appendAlg->setProperty("InputWorkspace1", resWsName.toStdString());
        m_batchAlgoRunner->addAlgorithm(appendAlg);
      } else {
        m_batchAlgoRunner->addAlgorithm(appendAlg, appendProps);
      }
    }

    m_batchAlgoRunner->executeBatchAsync();
  }
}

namespace {
////////////////////////////
// Anon Helper functions. //
////////////////////////////

/**
 * Takes an index and a name, and constructs a single level parameter name
 * for use with function ties, etc.
 *
 * @param index :: the index of the function in the first level.
 * @param name  :: the name of the parameter inside the function.
 *
 * @returns the constructed function parameter name.
 */
std::string createParName(size_t index, const std::string &name = "") {
  std::stringstream prefix;
  prefix << "f" << index << "." << name;
  return prefix.str();
}

/**
 * Takes an index, a sub index and a name, and constructs a double level
 * (nested) parameter name for use with function ties, etc.
 *
 * @param index    :: the index of the function in the first level.
 * @param subIndex :: the index of the function in the second level.
 * @param name     :: the name of the parameter inside the function.
 *
 * @returns the constructed function parameter name.
 */
std::string createParName(size_t index, size_t subIndex,
                          const std::string &name = "") {
  std::stringstream prefix;
  prefix << "f" << index << ".f" << subIndex << "." << name;
  return prefix.str();
}
}

/**
 * Creates a function to carry out the fitting in the "ConvFit" tab.  The
 * function consists of various sub functions, with the following structure:
 *
 * Composite
 *  |
 *  +- LinearBackground
 *  +- Convolution
 *      |
 *      +- Resolution
 *      +- Model (AT LEAST one delta function or one/two lorentzians.)
 *          |
 *          +- DeltaFunction(yes/no)
 *				+- ProductFunction
 *					|
 *					+- Lorentzian 1(yes/no)
 *					+- Temperature Correction(yes/no)
 *				+- ProductFunction
 *					|
 *					+- Lorentzian 2(yes/no)
 *					+- Temperature Correction(yes/no)
 *				+- ProductFunction
 *					|
 *					+- InelasticDiffSphere(yes/no)
 *					+- Temperature Correction(yes/no)
 *				+- ProductFunction
 *					|
 *					+-
 *InelasticDiffRotDiscreteCircle(yes/no)
 *					+- Temperature Correction(yes/no)
 *
 * @param tieCentres :: whether to tie centres of the two lorentzians.
 *
 * @returns the composite fitting function.
 */
CompositeFunction_sptr ConvFit::createFunction(bool tieCentres) {
  auto conv = boost::dynamic_pointer_cast<CompositeFunction>(
      FunctionFactory::Instance().createFunction("Convolution"));
  CompositeFunction_sptr comp(new CompositeFunction);

  IFunction_sptr func;
  size_t index = 0;

  // -------------------------------------
  // --- Composite / Linear Background ---
  // -------------------------------------
  func = FunctionFactory::Instance().createFunction("LinearBackground");
  comp->addFunction(func);

  // 0 = Fixed Flat, 1 = Fit Flat, 2 = Fit all
  const int bgType = m_uiForm.cbBackground->currentIndex();

  if (bgType == 0 || !m_properties["BGA0"]->subProperties().isEmpty()) {
    comp->tie("f0.A0", m_properties["BGA0"]->valueText().toStdString());
  } else {
    func->setParameter("A0", m_properties["BGA0"]->valueText().toDouble());
  }

  if (bgType != 2) {
    comp->tie("f0.A1", "0.0");
  } else {
    if (!m_properties["BGA1"]->subProperties().isEmpty()) {
      comp->tie("f0.A1", m_properties["BGA1"]->valueText().toStdString());
    } else {
      func->setParameter("A1", m_properties["BGA1"]->valueText().toDouble());
    }
  }

  // --------------------------------------------
  // --- Composite / Convolution / Resolution ---
  // --------------------------------------------
  func = FunctionFactory::Instance().createFunction("Resolution");
  conv->addFunction(func);

  // add resolution file
  IFunction::Attribute attr("__ConvFit_Resolution");
  func->setAttribute("Workspace", attr);

  // --------------------------------------------------------
  // --- Composite / Convolution / Model / Delta Function ---
  // --------------------------------------------------------
  CompositeFunction_sptr model(new CompositeFunction);

  bool useDeltaFunc = m_blnManager->value(m_properties["UseDeltaFunc"]);

  if (useDeltaFunc) {
    func = FunctionFactory::Instance().createFunction("DeltaFunction");
    index = model->addFunction(func);
    std::string parName = createParName(index);
    populateFunction(func, model, m_properties["DeltaFunction"], parName,
                     false);
  }

  // ------------------------------------------------------------
  // --- Composite / Convolution / Model / Temperature Factor ---
  // ------------------------------------------------------------

  // create temperature correction function to multiply with the lorentzians
  IFunction_sptr tempFunc;
  QString temperature = m_uiForm.leTempCorrection->text();
  bool useTempCorrection =
      (!temperature.isEmpty() && m_uiForm.ckTempCorrection->isChecked());

  // -----------------------------------------------------
  // --- Composite / Convolution / Model / Lorentzians ---
  // -----------------------------------------------------
  std::string prefix1;
  std::string prefix2;

  int fitTypeIndex = m_uiForm.cbFitType->currentIndex();
  if (fitTypeIndex > 0) {
    size_t subIndex = 0;
    auto product = boost::dynamic_pointer_cast<CompositeFunction>(
        FunctionFactory::Instance().createFunction("ProductFunction"));

    if (useTempCorrection) {
      createTemperatureCorrection(product);
    }

    // Add 1st Lorentzian

    // if temperature not included then product is lorentzian * 1
    // create product function for temp * lorentzian

    std::string functionName = m_uiForm.cbFitType->currentText().toStdString();

    if (fitTypeIndex == 1 || fitTypeIndex == 2) {
      functionName = "Lorentzian";
    }
    func = FunctionFactory::Instance().createFunction(functionName);
    subIndex = product->addFunction(func);
    index = model->addFunction(product);
    prefix1 = createParName(index, subIndex);

    populateFunction(func, model, m_properties["FitFunction1"], prefix1, false);

    // Add 2nd Lorentzian
    if (fitTypeIndex == 2) {
      // if temperature not included then product is lorentzian * 1
      // create product function for temp * lorentzian
      auto product = boost::dynamic_pointer_cast<CompositeFunction>(
          FunctionFactory::Instance().createFunction("ProductFunction"));

      if (useTempCorrection) {
        createTemperatureCorrection(product);
      }

      func = FunctionFactory::Instance().createFunction(functionName);
      subIndex = product->addFunction(func);
      index = model->addFunction(product);
      prefix2 = createParName(index, subIndex);

      populateFunction(func, model, m_properties["FitFunction2"], prefix2,
                       false);
    }
  }

  conv->addFunction(model);
  comp->addFunction(conv);

  // Tie PeakCentres together
  if (tieCentres) {
    std::string tieL = prefix1 + "PeakCentre";
    std::string tieR = prefix2 + "PeakCentre";
    model->tie(tieL, tieR);
  }

  comp->applyTies();
  return comp;
}

/**
 * Creates the correction for the temperature
 */
void ConvFit::createTemperatureCorrection(CompositeFunction_sptr product) {
  // create temperature correction function to multiply with the lorentzians
  IFunction_sptr tempFunc;
  QString temperature = m_uiForm.leTempCorrection->text();

  // create user function for the exponential correction
  // (x*temp) / 1-exp(-(x*temp))
  tempFunc = FunctionFactory::Instance().createFunction("UserFunction");
  // 11.606 is the conversion factor from meV to K
  std::string formula = "((x*11.606)/Temp) / (1 - exp(-((x*11.606)/Temp)))";
  IFunction::Attribute att(formula);
  tempFunc->setAttribute("Formula", att);
  tempFunc->setParameter("Temp", temperature.toDouble());

  product->addFunction(tempFunc);
  product->tie("f0.Temp", temperature.toStdString());
  product->applyTies();
}

/**
 * Obtains the instrument resolution from the provided workspace
 * @param workspaceName The name of the workspaces which holds the instrument
 * resolution
 * @return The resolution of the instrument. returns 0 if no resolution data
 * could be found
 */
double ConvFit::getInstrumentResolution(std::string workspaceName) {
  using namespace Mantid::API;

  double resolution = 0.0;
  try {
    Mantid::Geometry::Instrument_const_sptr inst =
        AnalysisDataService::Instance()
            .retrieveWS<MatrixWorkspace>(workspaceName)
            ->getInstrument();
    std::vector<std::string> analysers = inst->getStringParameter("analyser");
    if (analysers.empty()) {
      g_log.warning("Could not load instrument resolution from parameter file");
      return 0.0;
    }

    std::string analyser = analysers[0];
    std::string idfDirectory =
        Mantid::Kernel::ConfigService::Instance().getString(
            "instrumentDefinition.directory");

    // If the analyser component is not already in the data file then load it
    // from the parameter file
    if (inst->getComponentByName(analyser)
            ->getNumberParameter("resolution")
            .size() == 0) {
      std::string reflection = inst->getStringParameter("reflection")[0];

      IAlgorithm_sptr loadParamFile =
          AlgorithmManager::Instance().create("LoadParameterFile");
      loadParamFile->initialize();
      loadParamFile->setProperty("Workspace", workspaceName);
      loadParamFile->setProperty(
          "Filename", idfDirectory + inst->getName() + "_" + analyser + "_" +
                          reflection + "_Parameters.xml");
      loadParamFile->execute();

      if (!loadParamFile->isExecuted()) {
        g_log.warning("Could not load parameter file, ensure instrument "
                      "directory is in data search paths.");
        return 0.0;
      }

      inst = AnalysisDataService::Instance()
                 .retrieveWS<MatrixWorkspace>(workspaceName)
                 ->getInstrument();
    }

    resolution =
        inst->getComponentByName(analyser)->getNumberParameter("resolution")[0];
  } catch (Mantid::Kernel::Exception::NotFoundError &e) {
    UNUSED_ARG(e);

    g_log.warning("Could not load instrument resolution from parameter file");
    resolution = 0.0;
  }

  return resolution;
}

/**
 * Intialises the property values for any of the fit type
 * @param propName The name of the property group
 * @return The popuated property group representing a fit type
 */
QtProperty *ConvFit::createFitType(const QString &propName) {
  QtProperty *fitTypeGroup = m_grpManager->addProperty(propName);
  QString cbName = propName;
  if (propName.compare("Lorentzian 1") == 0) {
    cbName = "One Lorentzian";
  }
  if (propName.compare("Lorentzian 2") == 0) {
    cbName = "Two Lorentzians";
  }
  auto params = getFunctionParameters(cbName);

  for (auto it = params.begin(); it != params.end(); ++it) {
    QString paramName = propName + "." + *it;
    m_properties[paramName] = m_dblManager->addProperty(*it);
    m_dblManager->setDecimals(m_properties[paramName], NUM_DECIMALS);
    if (QString(*it).compare("FWHM") == 0) {
      m_dblManager->setValue(m_properties[paramName], 0.02);
    }
    fitTypeGroup->addSubProperty(m_properties[paramName]);
  }
  return fitTypeGroup;
}

/**
 * Populates the properties of a function with given values
 * @param func The function currently being added to the composite
 * @param comp A composite function of the previously called functions
 * @param group The QtProperty representing the fit type
 * @param pref The index of the functions eg. (f0.f1)
 * @param tie Bool to state if parameters are to be tied together
 */
void ConvFit::populateFunction(IFunction_sptr func, IFunction_sptr comp,
                               QtProperty *group, const std::string &pref,
                               bool tie) {
  // Get subproperties of group and apply them as parameters on the function
  // object
  QList<QtProperty *> props = group->subProperties();

  for (int i = 0; i < props.size(); i++) {
    if (tie || !props[i]->subProperties().isEmpty()) {
      std::string name = pref + props[i]->propertyName().toStdString();
      std::string value = props[i]->valueText().toStdString();
      comp->tie(name, value);
    } else {
      std::string propName = props[i]->propertyName().toStdString();
      double propValue = props[i]->valueText().toDouble();
      if (propValue) {
        if (func->hasAttribute(propName))
          func->setAttributeValue(propName, propValue);
        else
          func->setParameter(propName, propValue);
      }
    }
  }
}

/**
 * Generate a string to describe the fit type selected by the user.
 * Used when naming the resultant workspaces.
 *
 * Assertions used to guard against any future changes that dont take
 * workspace naming into account.
 *
 * @returns the generated QString.
 */
QString ConvFit::fitTypeString() const {
  QString fitType("");

  if (m_blnManager->value(m_properties["UseDeltaFunc"]))
    fitType += "Delta";

  fitType += m_fitStrings.at(m_uiForm.cbFitType->currentIndex());

  return fitType;
}

/**
 * Generate a string to describe the background selected by the user.
 * Used when naming the resultant workspaces.
 *
 * Assertions used to guard against any future changes that dont take
 * workspace naming into account.
 *
 * @returns the generated QString.
 */
QString ConvFit::backgroundString() const {
  switch (m_uiForm.cbBackground->currentIndex()) {
  case 0:
    return "FixF_s";
  case 1:
    return "FitF_s";
  case 2:
    return "FitL_s";
  default:
    return "";
  }
}

/**
 * Generates a string that defines the fitting minimizer based on the user
 * options.
 *
 * @return Minimizer as a string
 */
QString ConvFit::minimizerString(QString outputName) const {
  QString minimizer = "Levenberg-Marquardt";

  if (m_blnManager->value(m_properties["UseFABADA"])) {
    minimizer = "FABADA";

    int chainLength = static_cast<int>(
        m_dblManager->value(m_properties["FABADAChainLength"]));
    minimizer += ",ChainLength=" + QString::number(chainLength);

    double convergenceCriteria =
        m_dblManager->value(m_properties["FABADAConvergenceCriteria"]);
    minimizer += ",ConvergenceCriteria=" + QString::number(convergenceCriteria);

    double jumpAcceptanceRate =
        m_dblManager->value(m_properties["FABADAJumpAcceptanceRate"]);
    minimizer += ",JumpAcceptanceRate=" + QString::number(jumpAcceptanceRate);

    minimizer += ",PDF=" + outputName + "_PDF";

    if (m_blnManager->value(m_properties["OutputFABADAChain"]))
      minimizer += ",Chains=" + outputName + "_Chain";
  }

  return minimizer;
}

/**
 * Changes property tree and plot appearance based on Fit Type
 * @param index A reference to the Fit Type (0-9)
 */
void ConvFit::typeSelection(int index) {

  auto hwhmRangeSelector = m_uiForm.ppPlot->getRangeSelector("ConvFitHWHM");

  if (index == 0) {
    hwhmRangeSelector->setVisible(false);
  } else if (index < 3) {
    hwhmRangeSelector->setVisible(true);
  } else {
    hwhmRangeSelector->setVisible(false);
    m_uiForm.ckPlotGuess->setChecked(false);
    m_blnManager->setValue(m_properties["UseDeltaFunc"], false);
  }

  // Disable Plot Guess and Use Delta Function for DiffSphere and
  // DiffRotDiscreteCircle
  m_uiForm.ckPlotGuess->setEnabled(index < 3);
  m_properties["UseDeltaFunc"]->setEnabled(index < 3);

  updatePlotOptions();
}

/**
 * Add/Remove sub property 'BGA1' from background based on Background type
 * @param index A reference to the Background type
 */
void ConvFit::bgTypeSelection(int index) {
  if (index == 2) {
    m_properties["LinearBackground"]->addSubProperty(m_properties["BGA1"]);
  } else {
    m_properties["LinearBackground"]->removeSubProperty(m_properties["BGA1"]);
  }
}

/**
 * Updates the plot in the gui window
 */
void ConvFit::updatePlot() {
  using Mantid::Kernel::Exception::NotFoundError;

  if (!m_cfInputWS) {
    g_log.error("No workspace loaded, cannot create preview plot.");
    return;
  }

  bool plotGuess = m_uiForm.ckPlotGuess->isChecked();
  m_uiForm.ckPlotGuess->setChecked(false);

  int specNo = m_uiForm.spPlotSpectrum->text().toInt();

  m_uiForm.ppPlot->clear();
  m_uiForm.ppPlot->addSpectrum("Sample", m_cfInputWS, specNo);

  try {
    const QPair<double, double> curveRange =
        m_uiForm.ppPlot->getCurveRange("Sample");
    const std::pair<double, double> range(curveRange.first, curveRange.second);
    m_uiForm.ppPlot->getRangeSelector("ConvFitRange")
        ->setRange(range.first, range.second);
    m_uiForm.ckPlotGuess->setChecked(plotGuess);
  } catch (std::invalid_argument &exc) {
    showMessageBox(exc.what());
  }

  // Default FWHM to resolution of instrument
  double resolution = getInstrumentResolution(m_cfInputWSName.toStdString());
  if (resolution > 0) {
    m_dblManager->setValue(m_properties["Lorentzian 1.FWHM"], resolution);
    m_dblManager->setValue(m_properties["Lorentzian 2.FWHM"], resolution);
  }

  // If there is a result plot then plot it
  std::string groupName = m_baseName.toStdString() + "_Workspaces";
  if (AnalysisDataService::Instance().doesExist(groupName)) {
    WorkspaceGroup_sptr outputGroup =
        AnalysisDataService::Instance().retrieveWS<WorkspaceGroup>(groupName);
    if (specNo - m_runMin >= static_cast<int>(outputGroup->size()))
      return;
    if ((specNo - m_runMin) >= 0) {
      MatrixWorkspace_sptr ws = boost::dynamic_pointer_cast<MatrixWorkspace>(
		  outputGroup->getItem(specNo- m_runMin));
      if (ws) {
        m_uiForm.ppPlot->addSpectrum("Fit", ws, 1, Qt::red);
        m_uiForm.ppPlot->addSpectrum("Diff", ws, 2, Qt::blue);
        if (m_uiForm.ckPlotGuess->isChecked()) {
          m_uiForm.ppPlot->removeSpectrum("Guess");
          m_uiForm.ckPlotGuess->setChecked(false);
        }
      }
    }
  }
}

/**
 * Updates the guess for the plot
 */
void ConvFit::plotGuess() {
  m_uiForm.ppPlot->removeSpectrum("Guess");

  // Do nothing if there is not a sample and resolution
  if (!(m_uiForm.dsSampleInput->isValid() && m_uiForm.dsResInput->isValid() &&
        m_uiForm.ckPlotGuess->isChecked()))
    return;

  if (m_uiForm.cbFitType->currentIndex() > 2) {
    return;
  }

  bool tieCentres = (m_uiForm.cbFitType->currentIndex() > 1);
  CompositeFunction_sptr function = createFunction(tieCentres);

  if (m_cfInputWS == NULL) {
    updatePlot();
  }

  const size_t binIndexLow =
      m_cfInputWS->binIndexOf(m_dblManager->value(m_properties["StartX"]));
  const size_t binIndexHigh =
      m_cfInputWS->binIndexOf(m_dblManager->value(m_properties["EndX"]));
  const size_t nData = binIndexHigh - binIndexLow;

  std::vector<double> inputXData(nData);
  const Mantid::MantidVec &XValues = m_cfInputWS->readX(0);
  const bool isHistogram = m_cfInputWS->isHistogramData();

  for (size_t i = 0; i < nData; i++) {
    if (isHistogram) {
      inputXData[i] =
          0.5 * (XValues[binIndexLow + i] + XValues[binIndexLow + i + 1]);
    } else {
      inputXData[i] = XValues[binIndexLow + i];
    }
  }

  FunctionDomain1DVector domain(inputXData);
  FunctionValues outputData(domain);
  function->function(domain, outputData);

  QVector<double> dataX, dataY;

  for (size_t i = 0; i < nData; i++) {
    dataX.append(inputXData[i]);
    dataY.append(outputData.getCalculated(i));
  }

  IAlgorithm_sptr createWsAlg =
      AlgorithmManager::Instance().create("CreateWorkspace");
  createWsAlg->initialize();
  createWsAlg->setChild(true);
  createWsAlg->setLogging(false);
  createWsAlg->setProperty("OutputWorkspace", "__GuessAnon");
  createWsAlg->setProperty("NSpec", 1);
  createWsAlg->setProperty("DataX", dataX.toStdVector());
  createWsAlg->setProperty("DataY", dataY.toStdVector());
  createWsAlg->execute();
  MatrixWorkspace_sptr guessWs = createWsAlg->getProperty("OutputWorkspace");

  m_uiForm.ppPlot->addSpectrum("Guess", guessWs, 0, Qt::green);
}

/**
 * Fits a single spectrum to the plot
 */
void ConvFit::singleFit() {
  if (!validate())
    return;

  updatePlot();

  m_uiForm.ckPlotGuess->setChecked(false);

  CompositeFunction_sptr function =
      createFunction(m_uiForm.ckTieCentres->isChecked());

  // get output name
  QString fitType = fitTypeString();
  QString bgType = backgroundString();

  if (fitType == "") {
    g_log.error("No fit type defined.");
  }

  m_singleFitOutputName =
      runPythonCode(
          QString(
              "from IndirectCommon import getWSprefix\nprint getWSprefix('") +
          m_cfInputWSName + QString("')\n"))
          .trimmed();
  m_singleFitOutputName +=
      QString("conv_") + fitType + bgType + m_uiForm.spPlotSpectrum->text();
  int maxIterations =
      static_cast<int>(m_dblManager->value(m_properties["MaxIterations"]));

  m_singleFitAlg = AlgorithmManager::Instance().create("Fit");
  m_singleFitAlg->initialize();
  m_singleFitAlg->setPropertyValue("Function", function->asString());
  m_singleFitAlg->setPropertyValue("InputWorkspace",
                                   m_cfInputWSName.toStdString());
  m_singleFitAlg->setProperty<int>("WorkspaceIndex",
                                   m_uiForm.spPlotSpectrum->text().toInt());
  m_singleFitAlg->setProperty<double>(
      "StartX", m_dblManager->value(m_properties["StartX"]));
  m_singleFitAlg->setProperty<double>(
      "EndX", m_dblManager->value(m_properties["EndX"]));
  m_singleFitAlg->setProperty("Output", m_singleFitOutputName.toStdString());
  m_singleFitAlg->setProperty("CreateOutput", true);
  m_singleFitAlg->setProperty("OutputCompositeMembers", true);
  m_singleFitAlg->setProperty("ConvolveMembers", true);
  m_singleFitAlg->setProperty("MaxIterations", maxIterations);
  m_singleFitAlg->setProperty(
      "Minimizer", minimizerString(m_singleFitOutputName).toStdString());

  m_batchAlgoRunner->addAlgorithm(m_singleFitAlg);
  connect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this,
          SLOT(singleFitComplete(bool)));
  m_batchAlgoRunner->executeBatchAsync();
}

/**
 * Handle completion of the fit algorithm for single fit.
 *
 * @param error If the fit algorithm failed
 */
void ConvFit::singleFitComplete(bool error) {
  disconnect(m_batchAlgoRunner, SIGNAL(batchComplete(bool)), this,
             SLOT(singleFitComplete(bool)));

  if (error) {
    showMessageBox("Fit algorithm failed.");
    return;
  }

  // Plot the line on the mini plot
  m_uiForm.ppPlot->removeSpectrum("Guess");
  m_uiForm.ppPlot->addSpectrum("Fit", m_singleFitOutputName + "_Workspace", 1,
                               Qt::red);

  IFunction_sptr outputFunc = m_singleFitAlg->getProperty("Function");

  QString functionName = m_uiForm.cbFitType->currentText();

  // Get params.
  QMap<QString, double> parameters;
  std::vector<std::string> parNames = outputFunc->getParameterNames();
  std::vector<double> parVals;

  QStringList params = getFunctionParameters(functionName);

  for (size_t i = 0; i < parNames.size(); ++i)
    parVals.push_back(outputFunc->getParameter(parNames[i]));

  for (size_t i = 0; i < parNames.size(); ++i)
    parameters[QString(parNames[i].c_str())] = parVals[i];

  // Populate Tree widget with values
  // Background should always be f0
  m_dblManager->setValue(m_properties["BGA0"], parameters["f0.A0"]);
  m_dblManager->setValue(m_properties["BGA1"], parameters["f0.A1"]);

  int fitTypeIndex = m_uiForm.cbFitType->currentIndex();

  int funcIndex = 0;
  int subIndex = 0;

  // check if we're using a temperature correction
  if (m_uiForm.ckTempCorrection->isChecked() &&
      !m_uiForm.leTempCorrection->text().isEmpty()) {
    subIndex++;
  }

  bool usingDeltaFunc = m_blnManager->value(m_properties["UseDeltaFunc"]);

  // If using a delta function with any fit type or using two Lorentzians
  bool usingCompositeFunc =
      ((usingDeltaFunc && fitTypeIndex > 0) || fitTypeIndex == 2);

  QString prefBase = "f1.f1.";

  if (usingDeltaFunc) {
    QString key = prefBase;
    if (usingCompositeFunc) {
      key += "f0.";
    }

    key += "Height";

    m_dblManager->setValue(m_properties["DeltaHeight"], parameters[key]);
    funcIndex++;
  }

  QString pref = prefBase;

  if (usingCompositeFunc) {
    pref += "f" + QString::number(funcIndex) + ".f" +
            QString::number(subIndex) + ".";
  } else {
    pref += "f" + QString::number(subIndex) + ".";
  }

  if (fitTypeIndex == 1 || fitTypeIndex == 2) {
    functionName = "Lorentzian 1";
  }

  if (fitTypeIndex == 2) {
    for (auto it = params.begin(); it != params.end() - 3; ++it) {
      QString functionParam = functionName + "." + *it;
      QString paramValue = pref + *it;
      m_dblManager->setValue(m_properties[functionParam],
                             parameters[paramValue]);
    }
    funcIndex++;
    pref = prefBase;
    pref += "f" + QString::number(funcIndex) + ".f" +
            QString::number(subIndex) + ".";

    functionName = "Lorentzian 2";

    for (auto it = params.begin() + 3; it != params.end(); ++it) {
      QString functionParam = functionName + "." + *it;
      QString paramValue = pref + *it;
      m_dblManager->setValue(m_properties[functionParam],
                             parameters[paramValue]);
    }

  } else {
    for (auto it = params.begin(); it != params.end(); ++it) {
      QString functionParam = functionName + "." + *it;
      QString paramValue = pref + *it;
      m_dblManager->setValue(m_properties[functionParam],
                             parameters[paramValue]);
    }
  }

  m_pythonExportWsName = "";
}

/**
 * Handles the user entering a new minimum spectrum index.
 *
 * Prevents the user entering an overlapping spectra range.
 *
 * @param value Minimum spectrum index
 */
void ConvFit::specMinChanged(int value) {
  m_uiForm.spSpectraMax->setMinimum(value);
}

/**
 * Handles the user entering a new maximum spectrum index.
 *
 * Prevents the user entering an overlapping spectra range.
 *
 * @param value Maximum spectrum index
 */
void ConvFit::specMaxChanged(int value) {
  m_uiForm.spSpectraMin->setMaximum(value);
}

void ConvFit::minChanged(double val) {
  m_dblManager->setValue(m_properties["StartX"], val);
}

void ConvFit::maxChanged(double val) {
  m_dblManager->setValue(m_properties["EndX"], val);
}

void ConvFit::hwhmChanged(double val) {
  const double peakCentre =
      m_dblManager->value(m_properties["Lorentzian 1.PeakCentre"]);
  // Always want FWHM to display as positive.
  const double hwhm = std::fabs(val - peakCentre);
  // Update the property
  auto hwhmRangeSelector = m_uiForm.ppPlot->getRangeSelector("ConvFitHWHM");
  hwhmRangeSelector->blockSignals(true);
  m_dblManager->setValue(m_properties["Lorentzian 1.FWHM"], hwhm * 2);
  hwhmRangeSelector->blockSignals(false);
}

void ConvFit::backgLevel(double val) {
  m_dblManager->setValue(m_properties["BGA0"], val);
}

void ConvFit::updateRS(QtProperty *prop, double val) {
  auto fitRangeSelector = m_uiForm.ppPlot->getRangeSelector("ConvFitRange");
  auto backRangeSelector =
      m_uiForm.ppPlot->getRangeSelector("ConvFitBackRange");

  if (prop == m_properties["StartX"]) {
    fitRangeSelector->setMinimum(val);
  } else if (prop == m_properties["EndX"]) {
    fitRangeSelector->setMaximum(val);
  } else if (prop == m_properties["BGA0"]) {
    backRangeSelector->setMinimum(val);
  } else if (prop == m_properties["Lorentzian 1.FWHM"]) {
    hwhmUpdateRS(val);
  } else if (prop == m_properties["Lorentzian 1.PeakCentre"]) {
    hwhmUpdateRS(m_dblManager->value(m_properties["Lorentzian 1.FWHM"]));
  }
}

void ConvFit::hwhmUpdateRS(double val) {
  const double peakCentre =
      m_dblManager->value(m_properties["Lorentzian 1.PeakCentre"]);
  auto hwhmRangeSelector = m_uiForm.ppPlot->getRangeSelector("ConvFitHWHM");
  hwhmRangeSelector->setMinimum(peakCentre - val / 2);
  hwhmRangeSelector->setMaximum(peakCentre + val / 2);
}

void ConvFit::checkBoxUpdate(QtProperty *prop, bool checked) {
  UNUSED_ARG(checked);

  if (prop == m_properties["UseDeltaFunc"]) {
    updatePlotOptions();
    if (checked == true) {
      m_properties["DeltaFunction"]->addSubProperty(
          m_properties["DeltaHeight"]);
      m_dblManager->setValue(m_properties["DeltaHeight"], 1.0000);
    } else {
      m_properties["DeltaFunction"]->removeSubProperty(
          m_properties["DeltaHeight"]);
    }
  } else if (prop == m_properties["UseFABADA"]) {
    if (checked) {
      // FABADA needs a much higher iteration limit
      m_dblManager->setValue(m_properties["MaxIterations"], 20000);

      m_properties["FABADA"]->addSubProperty(m_properties["OutputFABADAChain"]);
      m_properties["FABADA"]->addSubProperty(m_properties["FABADAChainLength"]);
      m_properties["FABADA"]->addSubProperty(
          m_properties["FABADAConvergenceCriteria"]);
      m_properties["FABADA"]->addSubProperty(
          m_properties["FABADAJumpAcceptanceRate"]);
    } else {
      m_dblManager->setValue(m_properties["MaxIterations"], 500);

      m_properties["FABADA"]->removeSubProperty(
          m_properties["OutputFABADAChain"]);
      m_properties["FABADA"]->removeSubProperty(
          m_properties["FABADAChainLength"]);
      m_properties["FABADA"]->removeSubProperty(
          m_properties["FABADAConvergenceCriteria"]);
      m_properties["FABADA"]->removeSubProperty(
          m_properties["FABADAJumpAcceptanceRate"]);
    }
  }
}

void ConvFit::fitContextMenu(const QPoint &) {
  QtBrowserItem *item(NULL);

  item = m_cfTree->currentItem();

  if (!item)
    return;

  // is it a fit property ?
  QtProperty *prop = item->property();
  if (prop == m_properties["StartX"] || prop == m_properties["EndX"])
    return;

  // is it already fixed?
  bool fixed = prop->propertyManager() != m_dblManager;
  if (fixed && prop->propertyManager() != m_stringManager)
    return;

  // Create the menu
  QMenu *menu = new QMenu("ConvFit", m_cfTree);
  QAction *action;

  if (!fixed) {
    action = new QAction("Fix", m_parentWidget);
    connect(action, SIGNAL(triggered()), this, SLOT(fixItem()));
  } else {
    action = new QAction("Remove Fix", m_parentWidget);
    connect(action, SIGNAL(triggered()), this, SLOT(unFixItem()));
  }

  menu->addAction(action);

  // Show the menu
  menu->popup(QCursor::pos());
}

void ConvFit::fixItem() {
  QtBrowserItem *item = m_cfTree->currentItem();

  // Determine what the property is.
  QtProperty *prop = item->property();
  QtProperty *fixedProp = m_stringManager->addProperty(prop->propertyName());
  QtProperty *fprlbl = m_stringManager->addProperty("Fixed");
  fixedProp->addSubProperty(fprlbl);
  m_stringManager->setValue(fixedProp, prop->valueText());

  item->parent()->property()->addSubProperty(fixedProp);

  m_fixedProps[fixedProp] = prop;

  item->parent()->property()->removeSubProperty(prop);
}

void ConvFit::unFixItem() {
  QtBrowserItem *item = m_cfTree->currentItem();

  QtProperty *prop = item->property();
  if (prop->subProperties().empty()) {
    item = item->parent();
    prop = item->property();
  }

  item->parent()->property()->addSubProperty(m_fixedProps[prop]);
  item->parent()->property()->removeSubProperty(prop);
  m_fixedProps.remove(prop);
  QtProperty *proplbl = prop->subProperties()[0];
  delete proplbl;
  delete prop;
}

void ConvFit::showTieCheckbox(QString fitType) {
  m_uiForm.ckTieCentres->setVisible(fitType == "Two Lorentzians");
}

/**
 * Gets a list of parameters for a given fit function.
 * @return List fo parameters
 */
QStringList ConvFit::getFunctionParameters(QString functionName) {
  QStringList parameters;
  if (functionName.compare("Two Lorentzians") == 0) {
    functionName = "Lorentzian";
    IFunction_sptr func =
        FunctionFactory::Instance().createFunction(functionName.toStdString());

    for (size_t i = 0; i < func->nParams(); i++) {
      parameters << QString::fromStdString(func->parameterName(i));
    }
  }

  if (functionName.compare("One Lorentzian") == 0) {
    functionName = "Lorentzian";
  }

  if (functionName.compare("Zero Lorentzians") == 0) {
    parameters.append("Zero");
  } else {
    IFunction_sptr func =
        FunctionFactory::Instance().createFunction(functionName.toStdString());

    for (size_t i = 0; i < func->nParams(); i++) {
      parameters << QString::fromStdString(func->parameterName(i));
    }
  }

  return parameters;
}

/**
 * Handles a new fit function being selected.
 * @param functionName Name of new fit function
 */
void ConvFit::fitFunctionSelected(const QString &functionName) {
  double oneLValues[3] = {0.0, 0.0, 0.0};
  bool previouslyOneL = false;
  if (m_previousFit.compare("One Lorentzian") == 0 &&
      m_uiForm.cbFitType->currentText().compare("Two Lorentzians") == 0) {
    previouslyOneL = true;
    oneLValues[0] = m_dblManager->value(m_properties["Lorentzian 1.Amplitude"]);
    oneLValues[1] =
        m_dblManager->value(m_properties["Lorentzian 1.PeakCentre"]);
    oneLValues[2] = m_dblManager->value(m_properties["Lorentzian 1.FWHM"]);
  }

  // Remove previous parameters from tree
  m_cfTree->removeProperty(m_properties["FitFunction1"]);
  m_cfTree->removeProperty(m_properties["FitFunction2"]);

  m_uiForm.ckPlotGuess->setChecked(false);
  m_uiForm.ckTieCentres->setChecked(false);

  // Add new parameter elements
  int fitFunctionIndex = m_uiForm.cbFitType->currentIndex();
  QStringList parameters = getFunctionParameters(functionName);
  updatePlotOptions();

  // Two Lorentzians Fit
  if (fitFunctionIndex == 2) {
    m_properties["FitFunction1"] = m_grpManager->addProperty("Lorentzian 1");
    m_cfTree->addProperty(m_properties["FitFunction1"]);
    m_properties["FitFunction2"] = m_grpManager->addProperty("Lorentzian 2");
    m_cfTree->addProperty(m_properties["FitFunction2"]);
  } else {
    m_properties["FitFunction1"] = m_grpManager->addProperty(functionName);
    m_cfTree->addProperty(m_properties["FitFunction1"]);
  }

  QString propName;
  // No fit function parameters required for Zero
  if (parameters[0].compare("Zero") != 0) {
    // Two Lorentzians Fit
    if (fitFunctionIndex == 2) {
      int count = 0;
      propName = "Lorentzian 1";
      for (auto it = parameters.begin(); it != parameters.end(); ++it) {
        if (count == 3) {
          propName = "Lorentzian 2";
        }
        QString name = propName + "." + *it;
        m_properties[name] = m_dblManager->addProperty(*it);

        if (QString(*it).compare("FWHM") == 0) {
          if (previouslyOneL && count < 3) {
            m_dblManager->setValue(m_properties[name], oneLValues[2]);
          } else {
            m_dblManager->setValue(m_properties[name], 0.0175);
          }
        } else if (QString(*it).compare("Amplitude") == 0) {
          if (previouslyOneL && count < 3) {
            m_dblManager->setValue(m_properties[name], oneLValues[0]);
          } else {
            m_dblManager->setValue(m_properties[name], 1.0);
          }
        } else if (QString(*it).compare("PeakCentre") == 0) {
          if (previouslyOneL && count < 3) {
            m_dblManager->setValue(m_properties[name], oneLValues[1]);
          } else {
            m_dblManager->setValue(m_properties[name], 0.0);
          }
        } else {
          m_dblManager->setValue(m_properties[name], 0.0);
        }

        m_dblManager->setDecimals(m_properties[name], NUM_DECIMALS);
        if (count < 3) {
          m_properties["FitFunction1"]->addSubProperty(m_properties[name]);
        } else {
          m_properties["FitFunction2"]->addSubProperty(m_properties[name]);
        }
        count++;
      }
    } else {
      if (fitFunctionIndex == 1) {
        propName = "Lorentzian 1";
      } else {
        propName = functionName;
      }
      for (auto it = parameters.begin(); it != parameters.end(); ++it) {
        QString name = propName + "." + *it;
        m_properties[name] = m_dblManager->addProperty(*it);

        if (QString(*it).compare("FWHM") == 0) {
          m_dblManager->setValue(m_properties[name], 0.0175);
        } else if (QString(*it).compare("Amplitude") == 0 ||
                   QString(*it).compare("Intensity") == 0) {
          m_dblManager->setValue(m_properties[name], 1.0);
        } else {
          m_dblManager->setValue(m_properties[name], 0.0);
        }

        m_dblManager->setDecimals(m_properties[name], NUM_DECIMALS);
        m_properties["FitFunction1"]->addSubProperty(m_properties[name]);
      }
    }
  }
  m_previousFit = m_uiForm.cbFitType->currentText();
}

/**
 * Populates the plot combobox
 */
void ConvFit::updatePlotOptions() {
  m_uiForm.cbPlotType->clear();

  const bool deltaFunction = m_blnManager->value(m_properties["UseDeltaFunc"]);
  const int fitFunctionType = m_uiForm.cbFitType->currentIndex();
  QStringList plotOptions;
  plotOptions << "None";

  if (deltaFunction && fitFunctionType < 3) {
    plotOptions << "Height";
  }

  QStringList params = QStringList();

  if (fitFunctionType != 2) {
    params = getFunctionParameters(m_uiForm.cbFitType->currentText());
  } else {
    params = getFunctionParameters(QString("One Lorentzian"));
  }
  if (fitFunctionType < 3 && fitFunctionType != 0) {
    params.removeAll("PeakCentre");
  }
  if (fitFunctionType != 0) {
    plotOptions.append(params);
  }

  if (fitFunctionType != 0 || deltaFunction) {
    plotOptions << "All";
  }
  m_uiForm.cbPlotType->addItems(plotOptions);
}

/**
 * Converts the user input for function into short hand for use in the workspace
 * naming
 * @param original - The original user input to the function
 * @return The short hand of the users input
 */
QString ConvFit::convertFuncToShort(const QString &original) {
  QString result = "";
  if (m_uiForm.cbFitType->currentIndex() != 0) {
    if (original.at(0) == 'E') {
      result += "E";
    } else if (original.at(0) == 'I') {
      result += "I";
    } else {
      return "SFT";
    }
    auto pos = original.find("Circle");
    if (pos != -1) {
      result += "DC";
    } else {
      result += "DS";
    }
  }
  return result;
}

/**
 * Converts the user input for background into short hand for use in the
 * workspace naming
 * @param original - The original user input to the function
 * @return The short hand of the users input
 */
QString ConvFit::convertBackToShort(const std::string &original) {
  QString result = QString::fromStdString(original.substr(0, 3));
  auto pos = original.find(" ");
  if (pos != std::string::npos) {
    result += original.at(pos + 1);
  }
  return result;
}

} // namespace IDA
} // namespace CustomInterfaces
} // namespace MantidQt