/*!
 * \brief SimulationManager::run
 */
void SimulationManager::run() {
    if ( useSimpleSimulationFlag ) {
        simpleSimulation();
        CoreFacade::getInstance().simulationFinished();
    }
    else {
        rangeSimulation();
        CoreFacade::getInstance().rangeSimulationFinished();
    }
}
void model::SimulationWorker::startOptimizationSlot(SimulationSetup *simSetupTemplate, bool overrideTD,
                                                    double overrideValue, bool useHeatSources)
{
    if(!mapsInitialized || busy)
            return;
    busy = true;
    accessLock.lock();
    abort = false;
    accessLock.unlock();

    SimulationSetup  simSetup(*simSetupTemplate);

    emit startedWork();

    optimizationN = obsSize*obsSize;
    simSetup.setN(obsSize);

    // DeltaX
    double deltaX = (double) 1 / (double) (simSetup.getN()-1);

    // Anlegen des Vektors für zu optimierende Temperaturleitkoeffizienten
    std::vector<AD_TYPE> optimizedCsAD;
    if(overrideTD)
        optimizedCsAD.resize(optimizationN,overrideValue);
    else
    {
        optimizedCsAD.resize(optimizationN,simSetup.getAreaBackgroundValue(SimulationSetup::AreaThermalDiffusivity));
        // Berechnen welche Punkte von welchem Temperaturleitkoeffizienten-Gebiet
        // abgedeckt werden, dabei überschreiben neure Gebiete ältere
        emit beginningOptimizationStage("Initiale Temperaturleitkoeffizienten:",simSetup.getAreaCount(SimulationSetup::AreaThermalDiffusivity));
        if(simSetup.getAreaCount(SimulationSetup::AreaThermalDiffusivity) > 0)
        {
            QList<Area*>::const_iterator it = simSetup.getAreas(SimulationSetup::AreaThermalDiffusivity).begin();
            for(; it != simSetup.getAreas(SimulationSetup::AreaThermalDiffusivity).end(); ++it)
            {
                int count = 0;
                Area* thermalDiffusivity = *it;
                double diffusivity = thermalDiffusivity->getValue();
                double xMin, xMax, yMin, yMax;
                thermalDiffusivity->getTransitiveRectangle(xMin,xMax,yMin,yMax);
                long xLBound = ceil(xMin/deltaX),
                        xUBound = floor(xMax/deltaX),
                        yLBound = ceil(yMin/deltaX),
                        yUBound = floor(yMax/deltaX);
                for(long i = xLBound; i <= xUBound; ++i)
                    for(long j = yLBound; j <= yUBound; ++j)
                        if(thermalDiffusivity->insidePoint(i*deltaX,j*deltaX))
                            optimizedCsAD[i+j*n] = diffusivity;    //thermalDiffusivity.getValue(i*deltaX,j*deltaX);
                emit finishedOptimizationStep(++count);
            }
        }
    }

    if(!useHeatSources)
        while(simSetup.getAreaCount(SimulationSetup::AreaHeatSource) > 0)
            simSetup.removeLastArea(SimulationSetup::AreaHeatSource);

    // Berechnen welche Punkte von welcher Wärmequelle abgedeckt werden,
    // zwischenspeichern als Indizes
    QList<QList<long> *> heatSourceIndices;
    if(simSetup.getAreaCount(SimulationSetup::AreaHeatSource) > 0)
    {
        emit beginningOptimizationStage("Wärmequellen", simSetup.getAreaCount(SimulationSetup::AreaHeatSource));
        int count = 0;
        QList<Area*>::const_iterator it = simSetup.getAreas(SimulationSetup::AreaHeatSource).begin();
        for(; it != simSetup.getAreas(SimulationSetup::AreaHeatSource).end(); ++it)
        {
            QList<long> * tmpListPtr = new QList<long>;
            Area* heatSource = *it;
            double xMin, xMax, yMin, yMax;
            heatSource->getTransitiveRectangle(xMin,xMax,yMin,yMax);
            long xLBound = xMin > 0 ? ceil(xMin/deltaX) : 1,
                    xUBound = xMax < 1 ? floor(xMax/deltaX) : n-2,
                    yLBound = yMin > 0 ? ceil(yMin/deltaX) : 1,
                    yUBound = yMax < 1 ? floor(yMax/deltaX) : n-2;
            for(long i = xLBound; i <= xUBound; ++i)
                for(long j = yLBound; j <= yUBound; ++j)
                    if(heatSource->insidePoint(i*deltaX,j*deltaX))
                        tmpListPtr->append(i+j*n);
            heatSourceIndices.append(tmpListPtr);
            emit finishedOptimizationStep(++count);
        }
    }

    QVector<AD_TYPE> * step1 = new QVector<AD_TYPE>(optimizationN,simSetup.getIBV(SimulationSetup::InitialValue));
    QVector<AD_TYPE> * step2 = new QVector<AD_TYPE>(optimizationN,simSetup.getIBV(SimulationSetup::InitialValue));

    bool tmpAbort = false;

#ifndef _WIN32
    accessLock.lock();
    tmpAbort = abort;
    accessLock.unlock();

    if(tmpAbort)
    {
       emit beginningOptimizationStage("Optimierung abbgebrochen",1);
    }
    else
    {
        emit beginningOptimizationStage("Optimierungschritt berechnen",simSetup.getSolverMaxIt());
        int count = 0;
        AD_MODE::global_tape = AD_MODE::tape_t::create();
        AD_TYPE norm;
        do
        {
            AD_MODE::global_tape->register_variable(optimizedCsAD.data(),optimizationN);

            QVector<AD_TYPE> tmpCs (QVector<AD_TYPE>::fromStdVector(optimizedCsAD));
            QVector<AD_TYPE> * result = simpleSimulation(simSetup,step1,step2,tmpCs,
                                                         heatSourceIndices);
            AD_TYPE J = 0;
            for(int i = 0; i < obsSize; ++i) {
                for(int j = 0; j < obsSize; ++j) {
                    J += ((*result)[i*obsSize + j] - observations[i][j])
                            *((*result)[i*obsSize + j] - observations[i][j]);
                }
            }
            J *= 1./((obsSize-1)*(obsSize-1));

            std::cout << "J" << count+1 << ": " << J << std::endl<< std::endl;

            dco::derivative(J) = 1;
            AD_MODE::global_tape->interpret_adjoint();

            QVector<AD_TYPE> grad(QVector<AD_TYPE>::fromStdVector(optimizedCsAD));
            for(int i = 0; i < optimizationN; ++i)
                grad[i] = dco::derivative(optimizedCsAD[i]);

            AD_TYPE s = 1e-11;
            for(int i = 1; i < obsSize-1; ++i)
                for(int j = 1; j < obsSize-1; ++j)
                    optimizedCsAD[i*obsSize+j]  -= s * grad[i*obsSize+j];

            norm = algorithms::norm2(grad);

            accessLock.lock();
            tmpAbort = abort;
            accessLock.unlock();
            if(tmpAbort)
                break;
            emit finishedOptimizationStep(++count);
            AD_MODE::global_tape->reset();
        }
        while(count < simSetup.getSolverMaxIt() && norm-simSetup.getSolverMaxError() > 0);
        AD_MODE::tape_t::remove(AD_MODE::global_tape);
        if(tmpAbort)
            emit beginningOptimizationStage("Optimierung abgebrochen",1);
        else
        {
            optimizedCs.resize(optimizationN);
            for(int i = 0; i < optimizationN; ++i)
                optimizedCs[i] = dco::value(optimizedCsAD[i]);
            emit beginningOptimizationStage("Optimierung abgeschlossen",1);
            emit finishedOptimizationStep(1);
        }
    }

#endif

    //Aufräumen des Speichers
    QList<QList<long> *>::iterator it = heatSourceIndices.begin();
    for(; it != heatSourceIndices.end(); ++it)
        delete (*it);
    delete step1;
    delete step2;

    optimized = true;
    busy = false;
    emit finishedOptimization(!tmpAbort);
}