// ======================================================================
/*!
 * \file NFmiSoundingIndexCalculator.cpp
 *
 * Tämä luokka laskee erilaisia luotausi ndeksejä annettujen querinfojen avulla.
 * Mm. CAPE, CIN, LCL, BulkShear StormRelatedHellicity jne.
 */
// ======================================================================

#include "NFmiSoundingIndexCalculator.h"
#include "NFmiDrawParam.h"
#include "NFmiInfoOrganizer.h"
#include "NFmiSoundingData.h"
#include "NFmiSoundingFunctions.h"
#include <newbase/NFmiFastQueryInfo.h>
#include <newbase/NFmiGrid.h>
#include <newbase/NFmiQueryData.h>
#include <newbase/NFmiQueryDataUtil.h>
#include <newbase/NFmiValueString.h>

#ifndef BOOST_DISABLE_THREADS

#ifdef _MSC_VER
#pragma warning(disable : 4244)  // boost:in thread kirjastosta tulee ikävästi 4244 varoituksia
#endif
#include <boost/thread.hpp>
#ifdef _MSC_VER
#pragma warning(default : 4244)  // laitetaan 4244 takaisin päälle, koska se on tärkeä (esim. double
                                 // -> int auto castaus varoitus)
#endif

#endif  // BOOST_DISABLE_THREADS

using namespace NFmiSoundingFunctions;

bool NFmiSoundingIndexCalculator::FillSoundingData(
    const boost::shared_ptr<NFmiFastQueryInfo> &theInfo,
    NFmiSoundingData &theSoundingData,
    const NFmiMetTime &theTime,
    const NFmiLocation &theLocation,
    const boost::shared_ptr<NFmiFastQueryInfo> &theGroundDataInfo)
{
  if (theInfo)
  {
    if (theInfo->IsGrid())
      return theSoundingData.FillSoundingData(
          theInfo, theTime, theInfo->OriginTime(), theLocation, theGroundDataInfo);
    else
      return theSoundingData.FillSoundingData(theInfo, theTime, theInfo->OriginTime(), theLocation);
  }
  return false;
}

static bool FillSoundingData(const boost::shared_ptr<NFmiFastQueryInfo> &theInfo,
                             NFmiSoundingData &theSoundingData,
                             const boost::shared_ptr<NFmiFastQueryInfo> &thePossibleGroundInfo,
                             const NFmiMetTime &theTime,
                             const NFmiPoint &theLatlon,
                             bool useFastFill)
{
  if (theInfo)
  {
    if (theInfo->IsGrid())
      return theSoundingData.FillSoundingData(theInfo,
                                              theTime,
                                              theInfo->OriginTime(),
                                              NFmiLocation(theLatlon),
                                              thePossibleGroundInfo,
                                              useFastFill);
  }
  return false;
}

bool NFmiSoundingIndexCalculator::IsSurfaceBasedSoundingIndex(
    FmiSoundingParameters theSoundingParameter)
{
  if (theSoundingParameter >= kSoundingParLCLSurBas &&
      theSoundingParameter <= kSoundingParCAPE_TT_SurBas)
    return true;
  else
    return false;
}

#if 0  // NEVER USED
static bool FillSurfaceValuesFromInfo(NFmiSmartInfo *theInfo, NFmiSoundingData &theSoundingData, const NFmiPoint &theLatLon, float &theT, float &theTd)
{
	theInfo->Param(kFmiTemperature);
	float T = theInfo->InterpolatedValue(theLatLon);
	theT = T;
	theInfo->Param(kFmiDewPoint);
	float Td = theInfo->InterpolatedValue(theLatLon);
	theTd = Td;
	if(T != kFloatMissing && Td != kFloatMissing)
	{
		theSoundingData.SetTandTdSurfaceValues(T, Td);
		return true;
	}
	else
		return false;
}
#endif

static void CheckIfStopped(NFmiStopFunctor *theStopFunctor)
{
  if (theStopFunctor && theStopFunctor->Stop())
    throw NFmiStopThreadException();
}

static void CalcAllSoundingIndexParamFields(
    boost::shared_ptr<NFmiFastQueryInfo> &theSourceInfo,
    boost::shared_ptr<NFmiFastQueryInfo> &theResultInfo,
    const boost::shared_ptr<NFmiFastQueryInfo> &thePossibleGroundInfo,
    bool useFastFill,
    NFmiStopFunctor *theStopFunctor)
{
  // bool fObsDataFound = false; // toistaiseksi ei käytössä
  // bool useAnalyzeData = false; // toistaiseksi ei käytössä

  NFmiSoundingData soundingData;
  unsigned long counter = 0;
  for (theResultInfo->ResetLocation(); theResultInfo->NextLocation();)
  {
    try
    {
      // bool surfaceBaseStatus = false;
      if (useFastFill)
        theSourceInfo->LocationIndex(theResultInfo->LocationIndex());
      ::FillSoundingData(theSourceInfo,
                         soundingData,
                         thePossibleGroundInfo,
                         theResultInfo->Time(),
                         theResultInfo->LatLon(),
                         useFastFill);
      if (theSourceInfo->Grid() && !soundingData.IsDataGood())
        continue;  // jos oltiin mallidatassa ja datassa oli tiettyjä puutteita, ei tehdä laskentoja

      for (theResultInfo->ResetParam(); theResultInfo->NextParam();)
      {
        counter++;
        if (counter % 20 == 0)
          ::CheckIfStopped(
              theStopFunctor);  // joka 20 hila/paramtrilla -pisteellä katsotaan, pitääkö lopettaa

        FmiSoundingParameters soundingParameter =
            static_cast<FmiSoundingParameters>(theResultInfo->Param().GetParamIdent());
        // bool surfaceBasedCalculation =
        // NFmiSoundingIndexCalculator::IsSurfaceBasedSoundingIndex(soundingParameter); // onko
        // surfacebased???

        // HUOM!!!! muista muuttaa luotaus-parametri pelkäksi surface arvoksi, koska loppu menee
        // itsestään sitten
        float value = NFmiSoundingIndexCalculator::Calc(soundingData, soundingParameter);
        theResultInfo->FloatValue(value);
      }
    }
    catch (NFmiStopThreadException &)
    {
      break;
    }
    catch (...)
    {
      // jouduin laittamaan try-catch blokin tänne, koska aina silloin tällöin jossain vaiheessa
      // lentää poikkeus, joka lopettaa laskennat
      // tässä ei tehdä mitään, mutta laskennat jatkuvat ainakin seuraavasta pisteestä...
    }
  }
}

static void CalculatePartOfSoundingData(
    boost::shared_ptr<NFmiFastQueryInfo> &theSourceInfo,
    boost::shared_ptr<NFmiFastQueryInfo> &theResultInfo,
    const boost::shared_ptr<NFmiFastQueryInfo> &thePossibleGroundInfo,
    unsigned long startTimeIndex,
    unsigned long endTimeIndex,
    bool useFastFill,
    NFmiStopFunctor *theStopFunctor,
    int index,
    bool fDoCerrReporting)
{
  try
  {
    if (theSourceInfo->IsGrid() == false || theResultInfo->IsGrid() == false)
      throw std::runtime_error(
          "Error in CalculatePartOfSoundingData, source or result data was non grid-data.");

    for (unsigned long i = startTimeIndex; i <= endTimeIndex; i++)
    {
      if (theResultInfo->TimeIndex(i))
      {
        if (useFastFill == false ||
            theSourceInfo->TimeIndex(
                theResultInfo->TimeIndex()))  // optimointia, molemmissa samat ajat!!!
        {
          if (fDoCerrReporting)
            std::cerr << "thread nro: " << index << " starting time step nro: " << i << std::endl;
          ::CheckIfStopped(theStopFunctor);
          ::CalcAllSoundingIndexParamFields(
              theSourceInfo, theResultInfo, thePossibleGroundInfo, useFastFill, theStopFunctor);
        }
      }
    }
  }
  catch (NFmiStopThreadException & /* stopException */)
  {
    // lopetetaan sitten kun on niin haluttu
    if (fDoCerrReporting)
      std::cerr << "thread nro: " << index << " stops because it was told to do so." << std::endl;
  }
  catch (std::exception &e)
  {
    // lopetetaan muutenkin, jos tulee poikkeus
    if (fDoCerrReporting)
      std::cerr << "thread nro: " << index << " stops because exception was thrown:\n"
                << e.what() << std::endl;
  }
  catch (...)
  {
    // lopetetaan muutenkin, jos tulee poikkeus
    if (fDoCerrReporting)
      std::cerr << "thread nro: " << index << " stops because unknown error." << std::endl;
  }
  if (fDoCerrReporting)
    std::cerr << "thread nro: " << index << " end here." << std::endl;
}

static void CalculateSoundingDataOneTimeStepAtTime(
    boost::shared_ptr<NFmiFastQueryInfo> &theSourceInfo,
    boost::shared_ptr<NFmiFastQueryInfo> &theResultInfo,
    const boost::shared_ptr<NFmiFastQueryInfo> &thePossibleGroundInfo,
    NFmiTimeIndexCalculator &theTimeIndexCalculator,
    bool useFastFill,
    NFmiStopFunctor *theStopFunctor,
    int index,
    bool fDoCerrReporting)
{
  try
  {
    if (theSourceInfo->IsGrid() == false || theResultInfo->IsGrid() == false)
      throw std::runtime_error(
          "Error in CalculatePartOfSoundingData, source or result data was non grid-data.");

    unsigned long workedTimeIndex = 0;
    for (; theTimeIndexCalculator.GetCurrentTimeIndex(workedTimeIndex);)
    {
      if (theResultInfo->TimeIndex(workedTimeIndex))
      {
        if (useFastFill == false ||
            theSourceInfo->TimeIndex(
                theResultInfo->TimeIndex()))  // optimointia, molemmissa samat ajat!!!
        {
          if (fDoCerrReporting)
            std::cerr << "thread nro: " << index << " starting time step nro: " << workedTimeIndex
                      << std::endl;
          ::CheckIfStopped(theStopFunctor);
          ::CalcAllSoundingIndexParamFields(
              theSourceInfo, theResultInfo, thePossibleGroundInfo, useFastFill, theStopFunctor);
        }
      }
    }
  }
  catch (NFmiStopThreadException & /* stopException */)
  {
    // lopetetaan sitten kun on niin haluttu
    if (fDoCerrReporting)
      std::cerr << "thread nro: " << index << " stops because it was told to do so." << std::endl;
  }
  catch (std::exception &e)
  {
    // lopetetaan muutenkin, jos tulee poikkeus
    if (fDoCerrReporting)
      std::cerr << "thread nro: " << index << " stops because exception was thrown:\n"
                << e.what() << std::endl;
  }
  catch (...)
  {
    // lopetetaan muutenkin, jos tulee poikkeus
    if (fDoCerrReporting)
      std::cerr << "thread nro: " << index << " stops because unknown error." << std::endl;
  }
  if (fDoCerrReporting)
    std::cerr << "thread nro: " << index << " end here." << std::endl;
}

// Jos useFastFill on true, on datoilla sama hila ja aika descriptor rakenne
// theMaxThreadCount -parametrilla voidaan rajoittaa käytettävien threadien määrää. Jos sen arvo on
// <=0,
// ei rajoitusta ole ja käytetään koneen kaikkia coreja (paitsi jos fUseOnlyOneThread = true,
// jolloin
// käytetään vain yhtä threadia).
void NFmiSoundingIndexCalculator::CalculateWholeSoundingData(NFmiQueryData &theSourceData,
                                                             NFmiQueryData &theResultData,
                                                             NFmiQueryData *thePossibleGroundData,
                                                             bool useFastFill,
                                                             bool fDoCerrReporting,
                                                             NFmiStopFunctor *theStopFunctor,
                                                             bool fUseOnlyOneThread,
                                                             int theMaxThreadCount)
{
  NFmiSoundingFunctions::CalcDP(1, 56);  // tämä funktio pitää varmistaa että se on alustettu, koska
                                         // siellä on pari staattista muuttujaa, jotka
  // alustetaan ensimmäisellä kerralla ja multi-threaddaavassa jutussa se voisi olla ongelma.

  unsigned long timeSize = theResultData.Info()->SizeTimes();
  unsigned int usedThreadCount = boost::thread::hardware_concurrency();
  if (theMaxThreadCount > 0 && usedThreadCount > static_cast<unsigned int>(theMaxThreadCount))
    usedThreadCount = static_cast<unsigned int>(theMaxThreadCount);  // jos on haluttu säätää maksim
  // threadien määrää, säädetään
  // maksimia jos usedThreadCount
  // olisi muuten ylittänyt sen.

  usedThreadCount = NFmiQueryDataUtil::CalcOptimalThreadCount(usedThreadCount, timeSize);

  if (fUseOnlyOneThread || usedThreadCount < 2)
  {  // jos aikoja oli alle kaksi, lasketaan data yhdessä funktiossa

    if (fDoCerrReporting)
      std::cerr << "making data in single thread" << std::endl;
    boost::shared_ptr<NFmiFastQueryInfo> sourceInfo(new NFmiFastQueryInfo(&theSourceData));
    boost::shared_ptr<NFmiFastQueryInfo> resultInfo(new NFmiFastQueryInfo(&theResultData));
    boost::shared_ptr<NFmiFastQueryInfo> possibleGroundInfo(
        thePossibleGroundData ? new NFmiFastQueryInfo(thePossibleGroundData) : nullptr);
    ::CalculatePartOfSoundingData(sourceInfo,
                                  resultInfo,
                                  possibleGroundInfo,
                                  0,
                                  timeSize - 1,
                                  useFastFill,
                                  theStopFunctor,
                                  1,
                                  fDoCerrReporting);
  }
  else
  {
    if (fDoCerrReporting)
      std::cerr << "making data in multiple threads" << std::endl;

#ifndef WGS84
    theSourceData.LatLonCache();  // Ennen multi-thread laskuja pitää varmistaa että kunkin datan
                                  // (source + result) latlon-cache on alustettu, muutern tulee
                                  // ongelmia.
    theResultData.LatLonCache();
    if (thePossibleGroundData)
      thePossibleGroundData->LatLonCache();
#endif

    // pakko luoda dynaamisesti eri threadeille tarvittavat kopiot source ja target datoista
    std::vector<boost::shared_ptr<NFmiFastQueryInfo> > resultInfos(usedThreadCount);
    std::vector<boost::shared_ptr<NFmiFastQueryInfo> > sourceInfos(usedThreadCount);
    std::vector<boost::shared_ptr<NFmiFastQueryInfo> > possibleGroundInfos(usedThreadCount);
    for (unsigned int i = 0; i < usedThreadCount; i++)
    {
      resultInfos[i] = boost::shared_ptr<NFmiFastQueryInfo>(new NFmiFastQueryInfo(&theResultData));
      sourceInfos[i] = boost::shared_ptr<NFmiFastQueryInfo>(new NFmiFastQueryInfo(&theSourceData));
      if (thePossibleGroundData)
        possibleGroundInfos[i] =
            boost::shared_ptr<NFmiFastQueryInfo>(new NFmiFastQueryInfo(thePossibleGroundData));
    }

    NFmiTimeIndexCalculator timeIndexCalculator(0, timeSize - 1);
    boost::thread_group calcParts;
    for (unsigned int i = 0; i < usedThreadCount; i++)
      calcParts.add_thread(new boost::thread(::CalculateSoundingDataOneTimeStepAtTime,
                                             sourceInfos[i],
                                             resultInfos[i],
                                             possibleGroundInfos[i],
                                             boost::ref(timeIndexCalculator),
                                             useFastFill,
                                             theStopFunctor,
                                             i + 1,
                                             fDoCerrReporting));
    calcParts.join_all();  // odotetaan että threadit lopettavat

    if (fDoCerrReporting)
      std::cerr << "all threads ended" << std::endl;
  }
}

float NFmiSoundingIndexCalculator::Calc(NFmiSoundingData &theSoundingData,
                                        FmiSoundingParameters theParam)
{
  double value = kFloatMissing;
  double xxxxValue = kFloatMissing;  // tämä on ns. hukka parametri, koska jotkut parametrit
                                     // syntyvät sivutuotteena ja tähän sijoitetaan aina se ei
                                     // haluttu parametri
  switch (theParam)
  {
    // **** 1. yksinkertaiset indeksit, tarvitaan vain soundingdata ***
    case kSoundingParSHOW:
      value = theSoundingData.CalcSHOWIndex();
      break;
    case kSoundingParLIFT:
      value = theSoundingData.CalcLIFTIndex();
      break;
    case kSoundingParKINX:
      value = theSoundingData.CalcKINXIndex();
      break;
    case kSoundingParCTOT:
      value = theSoundingData.CalcCTOTIndex();
      break;
    case kSoundingParVTOT:
      value = theSoundingData.CalcVTOTIndex();
      break;
    case kSoundingParTOTL:
      value = theSoundingData.CalcTOTLIndex();
      break;

    // **** 2. indeksit joissa tarvitaan myös pintakerros lasku tyyppi soundingdatan lisäksi ja
    // mahd. korkeus parametri ***
    // **** surface ****
    case kSoundingParLCLSur:
    case kSoundingParLCLSurBas:
      value = theSoundingData.CalcLCLIndex(kLCLCalcSurface);
      break;
    case kSoundingParCAPESur:
    case kSoundingParCAPESurBas:
      value = theSoundingData.CalcCAPE500Index(kLCLCalcSurface);
      break;
    case kSoundingParCAPE0_3kmSur:
    case kSoundingParCAPE0_3kmSurBas:
      value = theSoundingData.CalcCAPE500Index(kLCLCalcSurface, 3000);
      break;
    case kSoundingParCAPE_TT_Sur:
    case kSoundingParCAPE_TT_SurBas:
      value = theSoundingData.CalcCAPE_TT_Index(kLCLCalcSurface, -10, -40);
      break;
    case kSoundingParCINSur:
    case kSoundingParCINSurBas:
      value = theSoundingData.CalcCINIndex(kLCLCalcSurface);
      break;
    case kSoundingParLCLHeightSur:
    case kSoundingParLCLHeightSurBas:
      value = theSoundingData.CalcLCLHeightIndex(kLCLCalcSurface);
      break;

    // **** 500 m mixing ****
    case kSoundingParLCL500m:
      value = theSoundingData.CalcLCLIndex(kLCLCalc500m2);
      break;
    case kSoundingParCAPE500m:
      value = theSoundingData.CalcCAPE500Index(kLCLCalc500m2);
      break;
    case kSoundingParCAPE0_3km500m:
      value = theSoundingData.CalcCAPE500Index(kLCLCalc500m2, 3000);
      break;
    case kSoundingParCAPE_TT_500m:
      value = theSoundingData.CalcCAPE_TT_Index(kLCLCalc500m2, -10, -40);
      break;
    case kSoundingParCIN500m:
      value = theSoundingData.CalcCINIndex(kLCLCalc500m2);
      break;
    case kSoundingParLCLHeight500m:
      value = theSoundingData.CalcLCLHeightIndex(kLCLCalc500m2);
      break;

    // **** most unstable case ****
    case kSoundingParLCLMostUn:
      value = theSoundingData.CalcLCLIndex(kLCLCalcMostUnstable);
      break;
    case kSoundingParCAPEMostUn:
      value = theSoundingData.CalcCAPE500Index(kLCLCalcMostUnstable);
      break;
    case kSoundingParCAPE0_3kmMostUn:
      value = theSoundingData.CalcCAPE500Index(kLCLCalcMostUnstable, 3000);
      break;
    case kSoundingParCAPE_TT_MostUn:
      value = theSoundingData.CalcCAPE_TT_Index(kLCLCalcMostUnstable, -10, -40);
      break;
    case kSoundingParCINMostUn:
      value = theSoundingData.CalcCINIndex(kLCLCalcMostUnstable);
      break;
    case kSoundingParLCLHeightMostUn:
      value = theSoundingData.CalcLCLHeightIndex(kLCLCalcMostUnstable);
      break;

    // **** 3. indeksit jotka lasketaan jonkun muun indeksin yhteydessä, tarvitaan myös
    // mahdollisesti pintakerros lasku tyyppi ja soundingdata ***
    case kSoundingParLFCSur:
    case kSoundingParLFCSurBas:
      value = theSoundingData.CalcLFCIndex(kLCLCalcSurface, xxxxValue);
      break;
    case kSoundingParELSur:
    case kSoundingParELSurBas:
      xxxxValue = theSoundingData.CalcLFCIndex(kLCLCalcSurface, value);
      break;
    case kSoundingParLFCHeightSur:
    case kSoundingParLFCHeightSurBas:
      value = theSoundingData.CalcLFCHeightIndex(kLCLCalcSurface, xxxxValue);
      break;
    case kSoundingParELHeightSur:
    case kSoundingParELHeightSurBas:
      xxxxValue = theSoundingData.CalcLFCHeightIndex(kLCLCalcSurface, value);
      break;

    case kSoundingParLFC500m:
      value = theSoundingData.CalcLFCIndex(kLCLCalc500m2, xxxxValue);
      break;
    case kSoundingParEL500m:
      xxxxValue = theSoundingData.CalcLFCIndex(kLCLCalc500m2, value);
      break;
    case kSoundingParLFCHeight500m:
      value = theSoundingData.CalcLFCHeightIndex(kLCLCalc500m2, xxxxValue);
      break;
    case kSoundingParELHeight500m:
      xxxxValue = theSoundingData.CalcLFCHeightIndex(kLCLCalc500m2, value);
      break;

    case kSoundingParLFCMostUn:
      value = theSoundingData.CalcLFCIndex(kLCLCalcMostUnstable, xxxxValue);
      break;
    case kSoundingParELMostUn:
      xxxxValue = theSoundingData.CalcLFCIndex(kLCLCalcMostUnstable, value);
      break;
    case kSoundingParLFCHeightMostUn:
      value = theSoundingData.CalcLFCHeightIndex(kLCLCalcMostUnstable, xxxxValue);
      break;
    case kSoundingParELHeightMostUn:
      xxxxValue = theSoundingData.CalcLFCHeightIndex(kLCLCalcMostUnstable, value);
      break;

    // **** 4. indeksit joiden laskuissa tarvitaan korkeus parametreja ja soundingdata ***
    case kSoundingParBS0_6km:
      value = theSoundingData.CalcBulkShearIndex(0, 6);
      break;
    case kSoundingParBS0_1km:
      value = theSoundingData.CalcBulkShearIndex(0, 1);
      break;
    case kSoundingParSRH0_3km:
      value = theSoundingData.CalcSRHIndex(0, 3);
      break;
    case kSoundingParSRH0_1km:
      value = theSoundingData.CalcSRHIndex(0, 1);
      break;
    case kSoundingParWS1500m:
      value = theSoundingData.CalcWSatHeightIndex(1500);
      break;
    case kSoundingParThetaE0_3km:
      value = theSoundingData.CalcThetaEDiffIndex(0, 3);
      break;
    case kSoundingParGDI:
      value = theSoundingData.CalcGDI();
      break;
    case kSoundingParNone:
      value = kFloatMissing;
      break;
  }

  return static_cast<float>(value);
}

float NFmiSoundingIndexCalculator::Calc(const boost::shared_ptr<NFmiFastQueryInfo> &theInfo,
                                        const NFmiPoint &theLatlon,
                                        const NFmiMetTime &theTime,
                                        FmiSoundingParameters theParam)
{
  NFmiSoundingData soundingData;
  NFmiLocation wantedLocation(theLatlon);
  if (FillSoundingData(theInfo, soundingData, theTime, wantedLocation, nullptr))
    return Calc(soundingData, theParam);
  return kFloatMissing;
}

static const NFmiParamDescriptor &GetSoundingIndexParams()
{
  static bool firstTime = true;
  static NFmiParamDescriptor soundingParams;
  if (firstTime)
  {
    firstTime = false;
    NFmiParamBag parBag;

    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParSHOW,
                                       "SHOW",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLIFT,
                                       "LIFT",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParKINX,
                                       "KINX",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCTOT,
                                       "CTOT",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParVTOT,
                                       "VTOT",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParTOTL,
                                       "TOTL",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));

    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLCLSur,
                                       "LCL (sur)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLFCSur,
                                       "LFC (sur)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParELSur,
                                       "EL (sur)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLCLHeightSur,
                                       "LCL height (sur)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLFCHeightSur,
                                       "LFC height (sur)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParELHeightSur,
                                       "EL height (sur)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCAPESur,
                                       "CAPE (sur)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCAPE0_3kmSur,
                                       "CAPE 0-3km (sur)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCAPE_TT_Sur,
                                       "CAPE -10-40 (sur)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCINSur,
                                       "CIN (sur)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));

    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLCL500m,
                                       "LCL (500m)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLFC500m,
                                       "LFC (500m)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParEL500m,
                                       "EL (500m)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLCLHeight500m,
                                       "LCL height (500m)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLFCHeight500m,
                                       "LFC height (500m)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParELHeight500m,
                                       "EL height (500m)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCAPE500m,
                                       "CAPE (500m)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCAPE0_3km500m,
                                       "CAPE 0-3km (500m)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCAPE_TT_500m,
                                       "CAPE -10-40 (500m)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCIN500m,
                                       "CIN (500m)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));

    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLCLMostUn,
                                       "LCL (mu)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLFCMostUn,
                                       "LFC (mu)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParELMostUn,
                                       "EL (mu)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLCLHeightMostUn,
                                       "LCL height (mu)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLFCHeightMostUn,
                                       "LFC height (mu)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParELHeightMostUn,
                                       "EL height (mu)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));

    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCAPEMostUn,
                                       "CAPE (mu)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));

    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCAPE0_3kmMostUn,
                                       "CAPE 0-3km (mu)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCAPE_TT_MostUn,
                                       "CAPE -10-40 (mu)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCINMostUn,
                                       "CIN (mu)",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));

    /* xxxx jää kommenttiin
                    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLCLSurBas, "LCL (obs-bas)",
       kFloatMissing, kFloatMissing, kFloatMissing, kFloatMissing, "%.1f", kLinearly)));
                    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLFCSurBas, "LFC (obs-bas)",
       kFloatMissing, kFloatMissing, kFloatMissing, kFloatMissing, "%.1f", kLinearly)));
                    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParELSurBas, "EL (obs-bas)",
       kFloatMissing, kFloatMissing, kFloatMissing, kFloatMissing, "%.1f", kLinearly)));
                    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLCLHeightSurBas, "LCL height
       (obs-bas)", kFloatMissing, kFloatMissing, kFloatMissing, kFloatMissing, "%.1f", kLinearly)));
                    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParLFCHeightSurBas, "LFC height
       (obs-bas)", kFloatMissing, kFloatMissing, kFloatMissing, kFloatMissing, "%.1f", kLinearly)));
                    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParELHeightSurBas, "EL height
       (obs-bas)", kFloatMissing, kFloatMissing, kFloatMissing, kFloatMissing, "%.1f", kLinearly)));
                    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCAPESurBas, "CAPE (obs-bas)",
       kFloatMissing, kFloatMissing, kFloatMissing, kFloatMissing, "%.1f", kLinearly)));
                    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCAPE0_3kmSurBas, "CAPE 0-3km
       (obs-bas)", kFloatMissing, kFloatMissing, kFloatMissing, kFloatMissing, "%.1f", kLinearly)));
                    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCAPE_TT_SurBas, "CAPE -10-40
       (obs-bas)", kFloatMissing, kFloatMissing, kFloatMissing, kFloatMissing, "%.1f", kLinearly)));
                    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParCINSurBas, "CIN (obs-bas)",
       kFloatMissing, kFloatMissing, kFloatMissing, kFloatMissing, "%.1f", kLinearly)));
    */

    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParBS0_6km,
                                       "BS 0-6km",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParBS0_1km,
                                       "BS 0-1km",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParSRH0_3km,
                                       "SRH 0-3km",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParSRH0_1km,
                                       "SRH 0-1km",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParWS1500m,
                                       "WS 1500m",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParThetaE0_3km,
                                       "ThetaE 0-3km",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));
    parBag.Add(NFmiDataIdent(NFmiParam(kSoundingParGDI,
                                       "GDI",
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       kFloatMissing,
                                       "%.1f",
                                       kLinearly)));

    soundingParams = NFmiParamDescriptor(parBag);
  }

  return soundingParams;
}

static NFmiQueryInfo MakeSoundingIndexInfo(NFmiQueryData &theSourceData,
                                           const std::string &theProducerName)
{
  NFmiFastQueryInfo fInfo(&theSourceData);

  NFmiParamDescriptor params = ::GetSoundingIndexParams();
  NFmiProducer usedProducer = *fInfo.Producer();
  if (theProducerName.empty() == false)
    usedProducer.SetName(theProducerName);
  else
  {
    NFmiString prodName = usedProducer.GetName();
    prodName += " (sounding index)";
    usedProducer.SetName(prodName);
  }
  params.SetProducer(usedProducer);  // tuottaja pitää asettaa oikeaksi

  NFmiQueryInfo info(
      params, fInfo.TimeDescriptor(), fInfo.HPlaceDescriptor());  // default vplaceDesc riittää kun
                                                                  // dataa lasketaan vain yhteen
                                                                  // tasoon
  return info;
}

boost::shared_ptr<NFmiQueryData> NFmiSoundingIndexCalculator::CreateNewSoundingIndexData(
    const std::string &theSourceFileFilter,
    const std::string &theProducerName,
    bool fDoCerrReporting,
    NFmiStopFunctor *theStopFunctor,
    bool fUseOnlyOneThread,
    int theMaxThreadCount)
{
  return NFmiSoundingIndexCalculator::CreateNewSoundingIndexData(theSourceFileFilter,
                                                                 theProducerName,
                                                                 "",
                                                                 fDoCerrReporting,
                                                                 theStopFunctor,
                                                                 fUseOnlyOneThread,
                                                                 theMaxThreadCount);
}

const std::string NFmiSoundingIndexCalculator::itsReadCompatibleGroundData_functionName =
    "ReadCompatibleGroundData";

// Jos thePossibleGroundDataFileFilter ei ole tyhjä, vaaditaan että löytyy data, jossa on sama
// tuottaja kuin theSourceData:ssa ja niissä sama origin aika. Jos pinta dataa ei vaadita,
// palautetaan tyhjää. Jos löytyy, palautetaan ladattu data. Jos pintadata vaaditaan, mutta sitä ei
// löydy, heitetään poikkeus.
static boost::shared_ptr<NFmiQueryData> ReadCompatibleGroundData(
    const std::string &thePossibleGroundDataFileFilter,
    boost::shared_ptr<NFmiQueryData> &theSourceData,
    bool fDoCerrReporting)
{
  if (thePossibleGroundDataFileFilter.empty())
    return boost::shared_ptr<NFmiQueryData>();
  else
  {
    boost::shared_ptr<NFmiQueryData> groundData(
        NFmiQueryDataUtil::ReadNewestData(thePossibleGroundDataFileFilter));
    if (!groundData)
      throw std::runtime_error(
          NFmiSoundingIndexCalculator::itsReadCompatibleGroundData_functionName +
          ": Cannot read required ground data " + thePossibleGroundDataFileFilter);
    else
    {
      if (theSourceData->Info()->Producer()->GetIdent() !=
          groundData->Info()->Producer()->GetIdent())
        throw std::runtime_error(
            NFmiSoundingIndexCalculator::itsReadCompatibleGroundData_functionName +
            ": Sounding index source data had different producer id than in ground data " +
            thePossibleGroundDataFileFilter);
      else
      {
        if (theSourceData->OriginTime() != groundData->OriginTime())
          throw std::runtime_error(
              NFmiSoundingIndexCalculator::itsReadCompatibleGroundData_functionName +
              ": Sounding index source data had different origin time than in ground data " +
              thePossibleGroundDataFileFilter);
        else
        {
          if (!groundData->Info()->Param(kFmiPressureAtStationLevel))
            throw std::runtime_error(
                NFmiSoundingIndexCalculator::itsReadCompatibleGroundData_functionName +
                ": Required ground data didn't have the needed PressureAtStationLevel parameter " +
                thePossibleGroundDataFileFilter);
        }
      }
    }
    return groundData;
  }
}

boost::shared_ptr<NFmiQueryData> NFmiSoundingIndexCalculator::CreateNewSoundingIndexData(
    const std::string &theSourceFileFilter,
    const std::string &theProducerName,
    const std::string &thePossibleGroundDataFileFilter,
    bool fDoCerrReporting,
    NFmiStopFunctor *theStopFunctor,
    bool fUseOnlyOneThread,
    int theMaxThreadCount)
{
  // 1. lue uusin pohjadata käyttöön
  boost::shared_ptr<NFmiQueryData> sourceData(
      NFmiQueryDataUtil::ReadNewestData(theSourceFileFilter));
  if (sourceData == 0)
    throw std::runtime_error("Error in CreateNewSoundingIndexData, cannot read source data.");
  else
  {
    if (fDoCerrReporting)
      std::cerr << "read qd-file: " << theSourceFileFilter << std::endl;
  }

  boost::shared_ptr<NFmiQueryData> possibleGroundData =
      ::ReadCompatibleGroundData(thePossibleGroundDataFileFilter, sourceData, fDoCerrReporting);

  return NFmiSoundingIndexCalculator::CreateNewSoundingIndexData(sourceData,
                                                                 possibleGroundData,
                                                                 theProducerName,
                                                                 fDoCerrReporting,
                                                                 theStopFunctor,
                                                                 fUseOnlyOneThread,
                                                                 theMaxThreadCount);
}

boost::shared_ptr<NFmiQueryData> NFmiSoundingIndexCalculator::CreateNewSoundingIndexData(
    boost::shared_ptr<NFmiQueryData> sourceData,
    boost::shared_ptr<NFmiQueryData> possibleGroundData,
    const std::string &theProducerName,
    bool fDoCerrReporting,
    NFmiStopFunctor *theStopFunctor,
    bool fUseOnlyOneThread,
    int theMaxThreadCount)
{
  // 1. luo sen avulla uusi qinfo pohjaksi
  NFmiQueryInfo soundingIndexInfo = ::MakeSoundingIndexInfo(*sourceData, theProducerName);
  // 2. luo uusi qdata
  boost::shared_ptr<NFmiQueryData> data(NFmiQueryDataUtil::CreateEmptyData(soundingIndexInfo));
  if (data == 0)
    throw std::runtime_error("Error in CreateNewSoundingIndexData, could not create result data.");
  // 4. täytä qdata
  NFmiSoundingIndexCalculator::CalculateWholeSoundingData(*sourceData.get(),
                                                          *data.get(),
                                                          possibleGroundData.get(),
                                                          true,
                                                          fDoCerrReporting,
                                                          theStopFunctor,
                                                          fUseOnlyOneThread,
                                                          theMaxThreadCount);

  if (theStopFunctor && theStopFunctor->Stop())
    return boost::shared_ptr<NFmiQueryData>();  // Jos Esim. SmartMetia ollaan sulkemassa ja laskut
                                                // on lopetettu kesken, ei haluta tallettaa kesken
                                                // jääneitä laskuja tiedostoon, eli palautetaan
                                                // tässä tyhjä smart-pointer
  else
    return data;
}


/**
 * This code is part of Qiskit.
 *
 * (C) Copyright IBM 2018, 2019.
 *
 * This code is licensed under the Apache License, Version 2.0. You may
 * obtain a copy of this license in the LICENSE.txt file in the root directory
 * of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Any modifications or derivative works of this code must retain this
 * copyright notice, and modified files need to carry a notice indicating
 * that they have been altered from the originals.
 */

#ifndef _aer_statevector_controller_hpp_
#define _aer_statevector_controller_hpp_

#include "base/controller.hpp"
#include "statevector_state.hpp"
#include "framework/json.hpp"
#include <chrono>
#include <ctime>
#include <sstream>
#include <boost/lexical_cast.hpp>

#include <limits>
//#include "dtoa_milo.h"

extern "C" {
  #include "devittsim/Simulator/sim.h"
}

#include <Python.h>
//#include <stdlib.h>
#include <cmath>

namespace AER {
namespace Simulator {

enum class Gates {
  id, h, s, sdg, t, tdg, // single qubit
  // multi-qubit controlled (including single-qubit non-controlled)
  mcx, mcy, mcz, mcu1, mcu2, mcu3, mcswap
};
//=========================================================================
// StatevectorController class
//=========================================================================

/**************************************************************************
 * Config settings:
 *
 * From Statevector::State class
 *
 * - "initial_statevector" (json complex vector): Use a custom initial
 *      statevector for the simulation [Default: null].
 * - "zero_threshold" (double): Threshold for truncating small values to
 *      zero in result data [Default: 1e-10]
 * - "statevector_parallel_threshold" (int): Threshold that number of qubits
 *      must be greater than to enable OpenMP parallelization at State
 *      level [Default: 13]
 * - "statevector_sample_measure_opt" (int): Threshold that number of qubits
 *      must be greater than to enable indexing optimization during
 *      measure sampling [Default: 10]
 * - "statevector_hpc_gate_opt" (bool): Enable large qubit gate optimizations.
 *      [Default: False]
 *
 * From BaseController Class
 *
 * - "max_parallel_threads" (int): Set the maximum OpenMP threads that may
 *      be used across all levels of parallelization. Set to 0 for maximum
 *      available. [Default : 0]
 * - "max_parallel_experiments" (int): Set number of circuits that may be
 *      executed in parallel. Set to 0 to use the number of max parallel
 *      threads [Default: 1]
 * - "counts" (bool): Return counts object in circuit data [Default: True]
 * - "snapshots" (bool): Return snapshots object in circuit data [Default: True]
 * - "memory" (bool): Return memory array in circuit data [Default: False]
 * - "register" (bool): Return register array in circuit data [Default: False]
 *
 **************************************************************************/

const stringmap_t<Gates> ma_gateset_({
  // Single qubit gates
  {"id", Gates::id},     // Pauli-Identity gate
  {"x", Gates::mcx},     // Pauli-X gate
  {"y", Gates::mcy},     // Pauli-Y gate
  {"z", Gates::mcz},     // Pauli-Z gate
  {"s", Gates::s},       // Phase gate (aka sqrt(Z) gate)
  {"sdg", Gates::sdg},   // Conjugate-transpose of Phase gate
  {"h", Gates::h},       // Hadamard gate (X + Z / sqrt(2))
  {"t", Gates::t},       // T-gate (sqrt(S))
  {"tdg", Gates::tdg},   // Conjguate-transpose of T gate
  // Waltz Gates
  {"u1", Gates::mcu1},   // zero-X90 pulse waltz gate
  {"u2", Gates::mcu2},   // single-X90 pulse waltz gate
  {"u3", Gates::mcu3},   // two X90 pulse waltz gate
  // Two-qubit gates
  {"cx", Gates::mcx},        // Controlled-X gate (CNOT)
  {"cy", Gates::mcy},        // Controlled-Y gate
  {"cz", Gates::mcz},        // Controlled-Z gate
  /*{"cu1", Gates::mcu1},      // Controlled-u1 gate
  {"cu2", Gates::mcu2},      // Controlled-u2 gate
  {"cu3", Gates::mcu3},      // Controlled-u3 gate
  */
  {"swap", Gates::mcswap},   // SWAP gate
  // 3-qubit gates
  //{"ccx", Gates::mcx},       // Controlled-CX gate (Toffoli)
  //{"cswap", Gates::mcswap},  // Controlled SWAP gate (Fredkin)
  // Multi-qubit controlled gates
  /*{"mcx", Gates::mcx},      // Multi-controlled-X gate
  {"mcy", Gates::mcy},      // Multi-controlled-Y gate
  {"mcz", Gates::mcz},      // Multi-controlled-Z gate
  {"mcu1", Gates::mcu1},    // Multi-controlled-u1
  {"mcu2", Gates::mcu2},    // Multi-controlled-u2
  {"mcu3", Gates::mcu3},    // Multi-controlled-u3
  {"mcswap", Gates::mcswap} // Multi-controlled SWAP gate
  */
});
class StatevectorController : public Base::Controller {
public:
  //-----------------------------------------------------------------------
  // Base class config override
  //-----------------------------------------------------------------------
  StatevectorController();

  // Load Controller, State and Data config from a JSON
  // config settings will be passed to the State and Data classes
  // Allowed config options:
  // - "initial_statevector: complex_vector"
  // Plus Base Controller config options
  virtual void set_config(const json_t &config) override;

  // Clear the current config
  void virtual clear_config() override;

protected:

  virtual size_t required_memory_mb(const Circuit& circuit,
                                    const Noise::NoiseModel& noise) const override;

private:

  //-----------------------------------------------------------------------
  // Base class abstract method override
  //-----------------------------------------------------------------------

  // This simulator will only return a single shot, regardless of the
  // input shot number

  virtual void ds_apply_ops(ds_Register ds_reg, const Circuit &circ, const std::unordered_set<int> & x_errors, const std::unordered_set<int> & y_errors, const std::unordered_set<int> & z_errors) const;
  virtual void check_and_inject(ds_Register ds_reg, long position, int qubit, const std::unordered_set<int> & x_errors, const std::unordered_set<int> & y_errors, const std::unordered_set<int> & z_errors) const;

  virtual ExperimentData run_circuit(const Circuit &circ,
                                 const Noise::NoiseModel& noise,
                                 const json_t &config,
                                 uint_t shots,
                                 uint_t rng_seed) const override;

  virtual ExperimentData direct_ds_sim(const Circuit &circ,
                                 const Noise::NoiseModel& noise,
                                 const json_t &config,
                                 uint_t shots,
                                 uint_t rng_seed) const;

  virtual ExperimentData inject_n_errors(const Circuit &circ,
                                 const Noise::NoiseModel& noise,
                                 const json_t &config,
                                 uint_t shots,
                                 uint_t rng_seed) const;

  virtual ExperimentData get_hotspots(const Circuit &circ,
                                 const Noise::NoiseModel& noise,
                                 const json_t &config,
                                 uint_t shots,
                                 uint_t rng_seed) const;
  //-----------------------------------------------------------------------
  // Custom initial state
  //-----------------------------------------------------------------------
  cvector_t initial_state_;
};

//=========================================================================
// Implementations
//=========================================================================

StatevectorController::StatevectorController() : Base::Controller() {
  // Disable qubit truncation by default
  Base::Controller::truncate_qubits_ = false;
}

//-------------------------------------------------------------------------
// Config
//-------------------------------------------------------------------------

void StatevectorController::set_config(const json_t &config) {
  // Set base controller config
  Base::Controller::set_config(config);

  //Add custom initial state
  if (JSON::get_value(initial_state_, "initial_statevector", config)) {
    // Check initial state is normalized
    if (!Utils::is_unit_vector(initial_state_, validation_threshold_))
      throw std::runtime_error("StatevectorController: initial_statevector is not a unit vector");
  }
}

void StatevectorController::clear_config() {
  Base::Controller::clear_config();
  initial_state_ = cvector_t();
}

size_t StatevectorController::required_memory_mb(const Circuit& circ,
                                                 const Noise::NoiseModel& noise) const {
  //Statevector::State<> state;
  //return state.required_memory_mb(circ.num_qubits, circ.ops);
  return 32;
}

//-------------------------------------------------------------------------
// Run circuit
//-------------------------------------------------------------------------

void StatevectorController::check_and_inject(ds_Register ds_reg, long position, int qubit, const std::unordered_set<int> & x_errors, const std::unordered_set<int> & y_errors, const std::unordered_set<int> & z_errors) const{
  if(x_errors.count(position) != 0){
    ds_X_no_error(ds_reg, qubit, 1);
    ds_reg.n_errors[0] += 1;
  }
  if(y_errors.count(position) != 0){
    ds_XZ_no_error(ds_reg, qubit, 1);
    ds_reg.n_errors[2] += 1;
  }
  if(z_errors.count(position) != 0){
    ds_Z_no_error(ds_reg, qubit, 1);
    ds_reg.n_errors[1] += 1;
  }
}
void StatevectorController::ds_apply_ops(ds_Register ds_reg, const Circuit &circ,const std::unordered_set<int> & x_errors, const std::unordered_set<int> & y_errors, const std::unordered_set<int> & z_errors) const {
  int gate_time = 1;
  long gate_position = 0;
  std::unordered_set<int> qubit_init_done;
  //std::cout<<"Add code for errors directly after initialization!" << std::endl;

  for(int i = 0; i < circ.num_qubits; i++){
    check_and_inject(ds_reg, i, i, x_errors, y_errors, z_errors);
  }
  gate_position += circ.num_qubits;
  bool do_init_errors = true;
  for (const auto & op: circ.ops) {
    reg_t oqs = op.qubits;
    const size_t Nqs = op.qubits.size();
    switch (op.type) {
      case Operations::OpType::gate:
        auto it = ma_gateset_.find(op.name);
        if (it == ma_gateset_.end())
          throw std::invalid_argument("QubitVectorState::invalid gate instruction \'" +
                                      op.name + "\'.");
        switch (it -> second) {
	  case Gates::mcx:
	    // Includes X, CX, CCX, etc
	    //BaseState::qreg_.apply_mcx(op.qubits);
            if (Nqs == 1){
              ds_X(ds_reg,oqs[0],gate_time);
            }
            else if(Nqs == 2){
              ds_cnot(ds_reg,oqs[0],oqs[1], gate_time);
            }
            else {
                throw std::invalid_argument("QubitVectorState::invalid gate instruction \'" +
                                            op.name + "\'. X with more than 2 argument qubits");
            }
	    break;
          case Gates::mcy:
	    // Includes Y, CY, CCY, etc
	    //BaseState::qreg_.apply_mcy(op.qubits);
            if (Nqs == 1){
              ds_XZ(ds_reg,oqs[0],gate_time);
            }
            else {
              throw std::invalid_argument("QubitVectorState::invalid gate instruction \'" +
                                          op.name + "\'. Y with more than 2 argument qubits");
            }
	    break;
	  case Gates::mcz:
	    // Includes Z, CZ, CCZ, etc
	    //BaseState::qreg_.apply_mcphase(op.qubits, -1);
            if (Nqs == 1){
              ds_Z(ds_reg,oqs[0],gate_time);
            }
            else {
                throw std::invalid_argument("QubitVectorState::invalid gate instruction \'" +
                                            op.name + "\'. Z with more than 2 argument qubits");
            }
	    break;

	  case Gates::id:
	    break;
	  case Gates::h:
	    //apply_gate_mcu3(op.qubits, M_PI / 2., 0., M_PI);
            if (Nqs == 1){
              ds_Hadamard(ds_reg,oqs[0],gate_time);
            }
            else {
                throw std::invalid_argument("QubitVectorState::invalid gate instruction \'" +
                                            op.name + "\'. H with more than 2 argument qubits");
            }
	    break;
          /*
          case Gates::s:
            apply_gate_phase(op.qubits[0], complex_t(0., 1.));
            break;
          case Gates::sdg:
            apply_gate_phase(op.qubits[0], complex_t(0., -1.));
            break;
          case Gates::t: {
            const double isqrt2{1. / std::sqrt(2)};
            apply_gate_phase(op.qubits[0], complex_t(isqrt2, isqrt2));
          } break;
          case Gates::tdg: {
            const double isqrt2{1. / std::sqrt(2)};
            apply_gate_phase(op.qubits[0], complex_t(isqrt2, -isqrt2));
          } break;
          case Gates::mcswap:
            // Includes SWAP, CSWAP, etc
            BaseState::qreg_.apply_mcswap(op.qubits);
            break;
          case Gates::mcu3:
            // Includes u3, cu3, etc
            apply_gate_mcu3(op.qubits,
			      std::real(op.params[0]),
			      std::real(op.params[1]),
			      std::real(op.params[2]));
            break;
          case Gates::mcu2:
	    // Includes u2, cu2, etc
	    apply_gate_mcu3(op.qubits,
		    apply_gate(op);
		    break;
          */
          default:
            throw std::invalid_argument("QubitVector::State::invalid instruction \'" +
                                        op.name + "\'.");
        }
     }

    for(int i = 0; i < Nqs; i++){
      check_and_inject(ds_reg, gate_position, oqs[i], x_errors, y_errors, z_errors);
      gate_position += 1;
    }

  }
  /*for(i=0;i<qs;i++){
    ds_Hadamard(ds_reg, i, 1);
  }
  for(i=0;i<qs;i++){
    ds_Z(ds_reg, i, 1);
  }
  for(i=0;i<qs;i++){
    ds_Hadamard(ds_reg, i, 1);
  }*/
}

ExperimentData StatevectorController::direct_ds_sim(const Circuit &circ,
                                 const Noise::NoiseModel& noise,
                                 const json_t &config,
                                 uint_t shots,
                                 uint_t rng_seed) const {
 {

    Statevector::State<> state;
    // Set config
    state.set_config(config);
    double error_rate=-1.0;
    JSON::get_value(error_rate, "ds_error_rate",config);
    // Rng engine
    //RngEngine rng;
    //rng.set_seed(rng_seed);

    // Output data container
    ExperimentData data;
    data.set_config(config);

    int i;
    int qs = circ.num_qubits;

    int gate_time=1;
    long ds_seed_offset = 0;
    JSON::get_value(ds_seed_offset, "ds_seed_offset",config);

    long ds_seed_py = rng_seed+ds_seed_offset;
    ds_initialize_simulator(ds_seed_offset);

    ds_Register ds_reg;
    ds_reg = ds_create_register(qs, error_rate, 0);
    ds_set_state(ds_reg, 0, 1, 0);
    // apply ops
    //state.apply_ops(circ.ops, data, rng);
    std::unordered_set <int> x_errors, y_errors, z_errors;
    ds_apply_ops(ds_reg, circ, x_errors, y_errors, z_errors);
    //std::vector<std::complex<double>> state_from_sim;
    //cvector_t state_from_sim;

    std::time_t done_sim = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    //std::cout << "done sim at: "<<std::ctime(&done_sim) <<"nc: " <<ds_reg.nc<<std::endl;

    //std::vector<complex_t> state_from_sim;
    //data.sv_vec.reserve(ds_reg.nc);

    //PyObject* resList = PyList_New(ds_reg.nc);
    data.sv_list = PyList_New(ds_reg.nc);

    //std::cout<<"injected "<<ds_reg.n_errors[0]<<" X-errors "<<ds_reg.n_errors[1]<<" Z-errors and "<<ds_reg.n_errors[2]<<" Y-Errors" << std::endl;

    data.add_additional_data("x_errors", ds_reg.n_errors[0]);
    data.add_additional_data("z_errors", ds_reg.n_errors[1]);
    data.add_additional_data("y_errors", ds_reg.n_errors[2]);
    for(i=0;i<ds_reg.nc;i++){
      //std::cout<<" i: "<<i<<" x: "<<ds_reg.state[i].x<<" <: "<<ds_reg.state[i].y<<std::endl;
      //data.sv_vec.emplace_back(std::complex<double>{ds_reg.state[i].x, ds_reg.state[i].y});

      //PyList_SetItem(data.sv_list, i, PyComplex_FromDoubles(0.1,-0.3));
      PyList_SetItem(data.sv_list, i, PyComplex_FromDoubles(ds_reg.state[i].x, ds_reg.state[i].y));
    }

    /*
    for(i=0;i<ds_reg.nc;i++){
      //std::ostringstream strs;
      //strs <<std::setprecision(std::numeric_limits<double>::max_digits10)<< ds_reg.state[i].x<<"+"<<ds_reg.state[i].y<<"j";
      //state_from_sim.emplace_back(std::complex<double>{ds_reg.state[i].x, ds_reg.state[i].y});
      char buffer1[256] = { '\0' };
      char buffer2[256] = { '\0' };
      dtoa_milo(ds_reg.state[i].x, buffer1);
      dtoa_milo(ds_reg.state[i].y, buffer2);

      std::string strbuffer1(buffer1);
      std::string strbuffer2(buffer2);
      //strbuffer+= strbuffer1 + "+"+strbuffer2+"j";
      state_from_sim.emplace_back(strbuffer1+"+"+strbuffer2+"j");
      //state_from_sim.emplace_back(boost::lexical_cast<std::string>(ds_reg.state[i].x)+"+"+boost::lexical_cast<std::string>(ds_reg.state[i].y)+"j");
    }
    */
    //ds_print(ds_reg);
    // translate ds_reg to std::vector<std::complex<RealType>> ret;
    //

    std::time_t bfddone_sim = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    //std::cout << "bafore destroy at: "<<std::ctime(&bfddone_sim) <<std::endl;
    //:wqds_reg = &ds_reg;
   //std::cout<<"injected "<<ds_reg.n_errors<<" errors" << std::endl;
    ds_destroy_register(ds_reg);
    //state.add_creg_to_data(data);
    //json_t jdata = state_from_sim;
    // Add final state to the data
    //json_t datRes = state_from_sim;
    //data.add_additional_data("statevector",datRes);
    //data.sv_vec=state_from_sim;
    //data.sv_list = resList;
    //ExperimentData data2;
    //data2.set_config(config);
    //data.sim_reg = ds_reg;
    std::time_t end_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    //std::cout << "done sim at: "<<std::ctime(&end_time) <<std::endl;
    return data;
  }
}

ExperimentData StatevectorController::inject_n_errors(const Circuit &circ,
                                 const Noise::NoiseModel& noise,
                                 const json_t &config,
                                 uint_t shots,
                                 uint_t rng_seed) const {
    Statevector::State<> state;
    // Set config
    state.set_config(config);
    double error_rate=-1.0;
    JSON::get_value(error_rate, "ds_error_rate",config);
    // Rng engine
    RngEngine rng;
    rng.set_seed(rng_seed);

    // Output data container
    ExperimentData data;
    data.set_config(config);

    int i;
    int qs = circ.num_qubits;

    int gate_time=1;
    long ds_seed_offset = 1;
    JSON::get_value(ds_seed_offset, "ds_seed_offset",config);

    long ds_seed_py = rng_seed+ds_seed_offset;
    ds_initialize_simulator(ds_seed_offset);

    ds_Register ds_reg;
    //error-free, errors are injected using x_/y_/z_errors in ds_apply_ops
    ds_reg = ds_create_register(qs, 0, 0);
    ds_set_state(ds_reg, 0, 1, 0);
    // apply ops
    //state.apply_ops(circ.ops, data, rng);
    std::unordered_set <int> x_errors, y_errors, z_errors;

    //std::cout << "ds::before first apply_ops" <<std::endl;
    ds_apply_ops(ds_reg, circ, x_errors, y_errors, z_errors);
    //std::vector<std::complex<double>> state_from_sim;
    //cvector_t state_from_sim;

    //std::cout << "ds::after first apply_ops" <<std::endl;
    int *qubits_to_measure = new int[qs];
    for(int j=0;j<qs;j++){
      qubits_to_measure[j] = qs - j - 1;
    }

    //std::cout << "ds::before measure" <<std::endl;
    double correct_result = ds_set_measure(ds_reg, qs, qubits_to_measure, (1 << qs) - 1);
    int locations = 0;
    // locations gives how many positions there are to inject an error
    for(int j = 0; j<qs; j++){
      locations += ds_reg.steps[j];
    }
    // allow for init errors
    locations += qs;
    //std::cout << " number of locations in circuit: " << locations << std::endl;
    //get number of errors
    long num_x_errors, num_y_errors, num_z_errors;
    num_x_errors = 0;
    num_y_errors = 0;
    num_z_errors = 0;

    JSON::get_value(num_x_errors, "ds_num_x_errors", config);
    JSON::get_value(num_y_errors, "ds_num_y_errors", config);
    JSON::get_value(num_z_errors, "ds_num_z_errors", config);

    // create arrays that store the location of the errors
    std::vector<int> x_error_locations, y_error_locations, z_error_locations;

    x_error_locations.resize(num_x_errors);
    y_error_locations.resize(num_y_errors);
    z_error_locations.resize(num_z_errors);

    // repeat simulations num_iterations times
    int num_iterations;
    num_iterations = 1;
    JSON::get_value(num_iterations, "ds_num_iterations", config);
    std::vector<double> result_ar;
    result_ar.reserve(num_iterations);

    //std::cout << "ds::before iterations" <<std::endl;
    for(int iteration=0;iteration<num_iterations;iteration++){

      x_errors.clear();
      y_errors.clear();
      z_errors.clear();

      for(int j=0; j<num_x_errors; j++)
        x_error_locations.at(j) = rng.rand_int((int_t) 0, (int_t) (locations-1)); //interval excluding #locations

      for(int j=0; j<num_y_errors; j++)
        y_error_locations.at(j) = rng.rand_int((int_t)0, (int_t) locations-1); //interval excluding #locations

      for(int j=0; j<num_z_errors; j++)
        z_error_locations.at(j) = rng.rand_int((int_t)0, (int_t) locations-1); //interval excluding #locations

      // only inject if there is an odd number of errors at a location
      for(int j=0; j<num_x_errors; j++)
        if(std::count(x_error_locations.begin(), x_error_locations.end(), x_error_locations[j]) % 2 == 1)
          x_errors.insert(x_error_locations[j]);

      for(int j=0; j<num_y_errors; j++)
        if(std::count(y_error_locations.begin(), y_error_locations.end(), y_error_locations[j]) % 2 == 1)
          y_errors.insert(y_error_locations[j]);

      for(int j=0; j<num_z_errors; j++)
        if(std::count(z_error_locations.begin(), z_error_locations.end(), z_error_locations[j]) % 2 == 1)
          z_errors.insert(z_error_locations[j]);
      // reset ds_reg and start new simulation with errors injected at locations given by unordered sets x_/y_/z_errors
      ds_clearreg(ds_reg);
      ds_set_state(ds_reg, 0, 1, 0);

      //std::cout << "ds::before iteration apply ops" <<std::endl;
      ds_apply_ops(ds_reg, circ, x_errors, y_errors, z_errors);

      //std::cout << "ds::after iteration apply ops" <<std::endl;
      double result_with_error = ds_set_measure(ds_reg, qs, qubits_to_measure, (1<<qs) - 1);

      //std::cout << "ds::after iteration apply op measurement: "<< result_with_error <<std::endl;
      result_ar.push_back(result_with_error);
    }
    //TODO: return result_ar
    //

    //std::cout << "ds::done adding info to return object" <<std::endl;
    data.add_additional_data("result-ar", result_ar);
    data.add_additional_data("correct-result", correct_result);

    //data.add_additional_data("x_errors", ds_reg.n_errors[0]);
    //data.add_additional_data("z_errors", ds_reg.n_errors[1]);
    //data.add_additional_data("y_errors", ds_reg.n_errors[2]);

    data.sv_list = PyList_New(ds_reg.nc);

    //std::cout<<"injected "<<ds_reg.n_errors[0]<<" X-errors "<<ds_reg.n_errors[1]<<" Z-errors and "<<ds_reg.n_errors[2]<<" Y-Errors" << std::endl;

    data.add_additional_data("x_errors", ds_reg.n_errors[0]);
    data.add_additional_data("z_errors", ds_reg.n_errors[1]);
    data.add_additional_data("y_errors", ds_reg.n_errors[2]);
    for(i=0;i<ds_reg.nc;i++){
      //std::cout<<" i: "<<i<<" x: "<<ds_reg.state[i].x<<" <: "<<ds_reg.state[i].y<<std::endl;
      //data.sv_vec.emplace_back(std::complex<double>{ds_reg.state[i].x, ds_reg.state[i].y});

      //PyList_SetItem(data.sv_list, i, PyComplex_FromDoubles(0.1,-0.3));
      PyList_SetItem(data.sv_list, i, PyComplex_FromDoubles(ds_reg.state[i].x, ds_reg.state[i].y));
    }

    data.add_additional_data("x_error_locs", x_errors);
    data.add_additional_data("z_error_locs", z_errors);
    data.add_additional_data("y_error_locs", y_errors);

    ds_destroy_register(ds_reg);

    delete[] qubits_to_measure;

    //std::cout << "ds::returning data" <<std::endl;
    //state.add_creg_to_data(data);
    //json_t jdata = state_from_sim;
    // Add final state to the data
    //json_t datRes = state_from_sim;
    //data.add_additional_data("statevector",datRes);
    //data.sv_vec=state_from_sim;
    //data.sv_list = resList;
    //ExperimentData data2;
    //data2.set_config(config);
    //data.sim_reg = ds_reg;
    //std::time_t end_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    //std::cout << "done sim at: "<<std::ctime(&end_time) <<std::endl;
    return data;
}

ExperimentData StatevectorController::get_hotspots(const Circuit &circ,
                                 const Noise::NoiseModel& noise,
                                 const json_t &config,
                                 uint_t shots,
                                 uint_t rng_seed) const {
/* TODO
 * similar to inject_n_errors: iterate over every location, do simulation with error at that location, divide and get probiablity for noisy result by correct result? or by 1/2^Qubits
 *
 * */

    Statevector::State<> state;
    // Set config
    state.set_config(config);
    double error_rate=-1.0;
    JSON::get_value(error_rate, "ds_error_rate",config);
    // Rng engine
    RngEngine rng;
    rng.set_seed(rng_seed);

    // Output data container
    ExperimentData data;
    data.set_config(config);

    int i;
    int qs = circ.num_qubits;

    int gate_time=1;
    long ds_seed_offset = 1;
    JSON::get_value(ds_seed_offset, "ds_seed_offset",config);

    long ds_seed_py = rng_seed+ds_seed_offset;
    ds_initialize_simulator(ds_seed_offset);

    ds_Register ds_reg;
    //error-free, errors are injected using x_/y_/z_errors in ds_apply_ops
    ds_reg = ds_create_register(qs, 0, 0);
    ds_set_state(ds_reg, 0, 1, 0);
    // apply ops
    //state.apply_ops(circ.ops, data, rng);
    std::unordered_set <int> x_errors, y_errors, z_errors;
    ds_apply_ops(ds_reg, circ, x_errors, y_errors, z_errors);
    //std::vector<std::complex<double>> state_from_sim;
    //cvector_t state_from_sim;
    int *qubits_to_measure = new int[qs];
    for(int j=0;j<qs;j++){
      qubits_to_measure[j] = qs - j - 1;
    }
    double correct_result = ds_set_measure(ds_reg, qs, qubits_to_measure, (1<<qs) - 1);
    int locations = 0;
    // locations gives how many positions there are to inject an error
    for(int j = 0; j<qs; j++){
      locations += ds_reg.steps[j];
    }
    // allow for init errors
    locations += qs;

    std::vector<double> result_ar_x;
    std::vector<double> result_ar_y;
    std::vector<double> result_ar_z;

    result_ar_x.reserve(locations);
    result_ar_y.reserve(locations);
    result_ar_z.reserve(locations);

    for(int location=0; location<locations;location++){
      x_errors.insert(location);
      ds_clearreg(ds_reg);
      ds_set_state(ds_reg, 0, 1, 0);
      ds_apply_ops(ds_reg, circ, x_errors, y_errors, z_errors);
      double result_with_error = ds_set_measure(ds_reg, qs, qubits_to_measure, (1<<qs) - 1);
      result_ar_x[location] = result_with_error;
      x_errors.clear();
    }

    for(int location=0; location<locations;location++){
      y_errors.insert(location);
      ds_clearreg(ds_reg);
      ds_set_state(ds_reg, 0, 1, 0);
      ds_apply_ops(ds_reg, circ, x_errors, y_errors, z_errors);
      double result_with_error = ds_set_measure(ds_reg, qs, qubits_to_measure, (1<<qs) - 1);
      result_ar_y[location] = result_with_error;
      y_errors.clear();
    }

    for(int location=0; location<locations;location++){
      z_errors.insert(location);
      ds_clearreg(ds_reg);
      ds_set_state(ds_reg, 0, 1, 0);
      ds_apply_ops(ds_reg, circ, x_errors, y_errors, z_errors);
      double result_with_error = ds_set_measure(ds_reg, qs, qubits_to_measure, (1<<qs) - 1);
      result_ar_z[location] = result_with_error;
      z_errors.clear();
    }

    data.add_additional_data("result-ar-x",result_ar_x);
    data.add_additional_data("result-ar-y",result_ar_y);
    data.add_additional_data("result-ar-z",result_ar_z);

    data.add_additional_data("correct-result", correct_result);
}
ExperimentData StatevectorController::run_circuit(const Circuit &circ,
                                              const Noise::NoiseModel& noise,
                                              const json_t &config,
                                              uint_t shots,
                                              uint_t rng_seed) const {
  // Initialize  state
  Statevector::State<> state;

  //std::time_t sstart_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
  //std::cout << "start sim at: "<<std::ctime(&sstart_time) <<std::endl;
  bool is_devitt_sim = false;
  bool is_inject_n_errors = false;
  bool is_hotspots = false;

  JSON::get_value(is_devitt_sim, "ds_direct_sim",config);
  JSON::get_value(is_inject_n_errors, "ds_inject_n_errors",config);
  JSON::get_value(is_hotspots, "ds_hotspots",config);
  if(is_devitt_sim){
    return direct_ds_sim(circ, noise, config, shots, rng_seed);
  }
  else if(is_inject_n_errors){
    std::cout << "start running inject n errors" <<std::endl;
    return inject_n_errors(circ, noise, config, shots, rng_seed);
  }
  else if(is_hotspots){
    return get_hotspots(circ, noise, config, shots, rng_seed);
  }
  else {
    std::cout << "Old Code! Did not check functionality."<<std::endl;
    // Set config
    state.set_config(config);
    state.set_parallalization(parallel_state_update_);

    // Rng engine
    RngEngine rng;
    rng.set_seed(rng_seed);

    // Output data container
    ExperimentData data;
    data.set_config(config);

    // Run single shot collecting measure data or snapshots
    if (initial_state_.empty())
      state.initialize_qreg(circ.num_qubits);
    else
      state.initialize_qreg(circ.num_qubits, initial_state_);
    state.initialize_creg(circ.num_memory, circ.num_registers);
    //state.apply_ops(circ.ops, data, rng);
    state.add_creg_to_data(data);

    // Add final state to the data
    data.add_additional_data("statevector", state.qreg().vector());

    return data;
  }
}

//-------------------------------------------------------------------------
} // end namespace Simulator
//-------------------------------------------------------------------------
} // end namespace AER
//-------------------------------------------------------------------------
#endif


/// \file   python_module_walras.cpp
///
/// \brief
///
/// \authors    Maarten P. Scholl
/// \date       2020-10-11
/// \copyright  Copyright 2017-2020 The Institute for New Economic Thinking,
///             Oxford Martin School, University of Oxford
///
///             Licensed under the Apache License, Version 2.0 (the "License");
///             you may not use this file except in compliance with the License.
///             You may obtain a copy of the License at
///
///                 http://www.apache.org/licenses/LICENSE-2.0
///
///             Unless required by applicable law or agreed to in writing,
///             software distributed under the License is distributed on an "AS
///             IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
///             express or implied. See the License for the specific language
///             governing permissions and limitations under the License.
///
///             You may obtain instructions to fulfill the attribution
///             requirements in CITATION.cff
///
#include <esl/economics/markets/walras/python_module_walras.hpp>
#include <esl/economics/markets/walras/price_setter.hpp>
#include <esl/economics/markets/walras/tatonnement.hpp>

#ifdef WITH_PYTHON
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include <boost/python.hpp>

using namespace boost::python;
using namespace esl::economics::markets;
using namespace esl::economics::markets::tatonnement;

BOOST_PYTHON_MODULE(_walras)
{
    enum_<excess_demand_model::solver>("solver")
        .value("root", excess_demand_model::root)
        .value("minimization", excess_demand_model::minimization)
        .export_values()
        ;

    class_<excess_demand_model>("excess_demand_model", init<esl::law::property_map<quote>>())
    .def_readwrite("circuit_breaker", &excess_demand_model::circuit_breaker)
        .def_readwrite("methods", &excess_demand_model::methods)
        .def_readwrite("quotes", &excess_demand_model::quotes)
        .def("compute_clearing_quotes", &excess_demand_model::compute_clearing_quotes)
        ;

}

#endif