/**
 * @file
 * @brief Implementation of generic charge propagation module
 * @remarks Based on code from Paul Schuetze
 * @copyright Copyright (c) 2017-2020 CERN and the Allpix Squared authors.
 * This software is distributed under the terms of the MIT License, copied verbatim in the file "LICENSE.md".
 * In applying this license, CERN does not waive the privileges and immunities granted to it by virtue of its status as an
 * Intergovernmental Organization or submit itself to any jurisdiction.
 */

#include "GenericPropagationModule.hpp"

#include <cmath>
#include <limits>
#include <map>
#include <memory>
#include <random>
#include <sstream>
#include <string>
#include <utility>

#include <Eigen/Core>

#include <Math/Point3D.h>
#include <Math/Vector3D.h>
#include <TCanvas.h>
#include <TFile.h>
#include <TH2F.h>
#include <TH3F.h>
#include <TPaveText.h>
#include <TPolyLine3D.h>
#include <TPolyMarker3D.h>
#include <TStyle.h>

#include "core/config/Configuration.hpp"
#include "core/messenger/Messenger.hpp"
#include "core/utils/distributions.h"
#include "core/utils/log.h"
#include "core/utils/unit.h"
#include "tools/ROOT.h"
#include "tools/runge_kutta.h"

#include "objects/DepositedCharge.hpp"
#include "objects/PropagatedCharge.hpp"

using namespace allpix;

/**
 * Besides binding the message and setting defaults for the configuration, the module copies some configuration variables to
 * local copies to speed up computation.
 */
GenericPropagationModule::GenericPropagationModule(Configuration& config,
                                                   Messenger* messenger,
                                                   std::shared_ptr<Detector> detector)
    : Module(config, detector), messenger_(messenger), detector_(std::move(detector)) {
    // Save detector model
    model_ = detector_->getModel();

    // Require deposits message for single detector
    messenger_->bindSingle<DepositedChargeMessage>(this, MsgFlags::REQUIRED);

    // Set default value for config variables
    config_.setDefault<double>("spatial_precision", Units::get(0.25, "nm"));
    config_.setDefault<double>("timestep_start", Units::get(0.01, "ns"));
    config_.setDefault<double>("timestep_min", Units::get(0.001, "ns"));
    config_.setDefault<double>("timestep_max", Units::get(0.5, "ns"));
    config_.setDefault<double>("integration_time", Units::get(25, "ns"));
    config_.setDefault<unsigned int>("charge_per_step", 10);
    config_.setDefault<double>("temperature", 293.15);

    // Models:
    config_.setDefault<std::string>("mobility_model", "jacoboni");
    config_.setDefault<std::string>("recombination_model", "none");

    config_.setDefault<bool>("output_linegraphs", false);
    config_.setDefault<bool>("output_animations", false);
    config_.setDefault<bool>("output_plots",
                             config_.get<bool>("output_linegraphs") || config_.get<bool>("output_animations"));
    config_.setDefault<bool>("output_animations_color_markers", false);
    config_.setDefault<double>("output_plots_step", config_.get<double>("timestep_max"));
    config_.setDefault<bool>("output_plots_use_pixel_units", false);
    config_.setDefault<bool>("output_plots_align_pixels", false);
    config_.setDefault<double>("output_plots_theta", 0.0f);
    config_.setDefault<double>("output_plots_phi", 0.0f);
    config_.setDefault<bool>("output_plots_lines_at_implants", false);

    // Set defaults for charge carrier propagation:
    config_.setDefault<bool>("propagate_electrons", true);
    config_.setDefault<bool>("propagate_holes", false);
    if(!config_.get<bool>("propagate_electrons") && !config_.get<bool>("propagate_holes")) {
        throw InvalidValueError(
            config_,
            "propagate_electrons",
            "No charge carriers selected for propagation, enable 'propagate_electrons' or 'propagate_holes'.");
    }

    config_.setDefault<bool>("ignore_magnetic_field", false);

    // Copy some variables from configuration to avoid lookups:
    temperature_ = config_.get<double>("temperature");
    timestep_min_ = config_.get<double>("timestep_min");
    timestep_max_ = config_.get<double>("timestep_max");
    timestep_start_ = config_.get<double>("timestep_start");
    integration_time_ = config_.get<double>("integration_time");
    target_spatial_precision_ = config_.get<double>("spatial_precision");
    output_plots_ = config_.get<bool>("output_plots");
    output_linegraphs_ = config_.get<bool>("output_linegraphs");
    output_animations_ = config_.get<bool>("output_animations");
    output_plots_step_ = config_.get<double>("output_plots_step");
    output_plots_lines_at_implants_ = config_.get<bool>("output_plots_lines_at_implants");
    propagate_electrons_ = config_.get<bool>("propagate_electrons");
    propagate_holes_ = config_.get<bool>("propagate_holes");
    charge_per_step_ = config_.get<unsigned int>("charge_per_step");

    // Enable multithreading of this module if multithreading is enabled and no per-event output plots are requested:
    // FIXME: Review if this is really the case or we can still use multithreading
    if(!(output_animations_ || output_linegraphs_)) {
        allow_multithreading();
    } else {
        LOG(WARNING) << "Per-event line graphs or animations requested, disabling parallel event processing";
    }

    boltzmann_kT_ = Units::get(8.6173e-5, "eV/K") * temperature_;

    // Parameter for charge transport in magnetic field (approximated from graphs:
    // http://www.ioffe.ru/SVA/NSM/Semicond/Si/electric.html) FIXME
    electron_Hall_ = 1.15;
    hole_Hall_ = 0.9;
}

void GenericPropagationModule::create_output_plots(uint64_t event_num, OutputPlotPoints& output_plot_points) {
    LOG(TRACE) << "Writing output plots";

    // Convert to pixel units if necessary
    if(config_.get<bool>("output_plots_use_pixel_units")) {
        for(auto& deposit_points : output_plot_points) {
            for(auto& point : deposit_points.second) {
                point.SetX(point.x() / model_->getPixelSize().x());
                point.SetY(point.y() / model_->getPixelSize().y());
            }
        }
    }

    // Calculate the axis limits
    double minX = FLT_MAX, maxX = FLT_MIN;
    double minY = FLT_MAX, maxY = FLT_MIN;
    unsigned long tot_point_cnt = 0;
    double start_time = std::numeric_limits<double>::max();
    unsigned int total_charge = 0;
    unsigned int max_charge = 0;
    for(auto& [deposit, points] : output_plot_points) {
        for(auto& point : points) {
            minX = std::min(minX, point.x());
            maxX = std::max(maxX, point.x());

            minY = std::min(minY, point.y());
            maxY = std::max(maxY, point.y());
        }
        start_time = std::min(start_time, deposit.getGlobalTime());
        total_charge += deposit.getCharge();
        max_charge = std::max(max_charge, deposit.getCharge());

        tot_point_cnt += points.size();
    }

    // Compute frame axis sizes if equal scaling is requested
    if(config_.get<bool>("output_plots_use_equal_scaling", true)) {
        double centerX = (minX + maxX) / 2.0;
        double centerY = (minY + maxY) / 2.0;
        if(config_.get<bool>("output_plots_use_pixel_units")) {
            minX = centerX - model_->getSensorSize().z() / model_->getPixelSize().x() / 2.0;
            maxX = centerX + model_->getSensorSize().z() / model_->getPixelSize().x() / 2.0;

            minY = centerY - model_->getSensorSize().z() / model_->getPixelSize().y() / 2.0;
            maxY = centerY + model_->getSensorSize().z() / model_->getPixelSize().y() / 2.0;
        } else {
            minX = centerX - model_->getSensorSize().z() / 2.0;
            maxX = centerX + model_->getSensorSize().z() / 2.0;

            minY = centerY - model_->getSensorSize().z() / 2.0;
            maxY = centerY + model_->getSensorSize().z() / 2.0;
        }
    }

    // Align on pixels if requested
    if(config_.get<bool>("output_plots_align_pixels")) {
        if(config_.get<bool>("output_plots_use_pixel_units")) {
            minX = std::floor(minX - 0.5) + 0.5;
            minY = std::floor(minY + 0.5) - 0.5;
            maxX = std::ceil(maxX - 0.5) + 0.5;
            maxY = std::ceil(maxY + 0.5) - 0.5;
        } else {
            double div = minX / model_->getPixelSize().x();
            minX = (std::floor(div - 0.5) + 0.5) * model_->getPixelSize().x();
            div = minY / model_->getPixelSize().y();
            minY = (std::floor(div - 0.5) + 0.5) * model_->getPixelSize().y();
            div = maxX / model_->getPixelSize().x();
            maxX = (std::ceil(div + 0.5) - 0.5) * model_->getPixelSize().x();
            div = maxY / model_->getPixelSize().y();
            maxY = (std::ceil(div + 0.5) - 0.5) * model_->getPixelSize().y();
        }
    }

    // Use a histogram to create the underlying frame
    auto* histogram_frame = new TH3F(("frame_" + getUniqueName() + "_" + std::to_string(event_num)).c_str(),
                                     "",
                                     10,
                                     minX,
                                     maxX,
                                     10,
                                     minY,
                                     maxY,
                                     10,
                                     model_->getSensorCenter().z() - model_->getSensorSize().z() / 2.0,
                                     model_->getSensorCenter().z() + model_->getSensorSize().z() / 2.0);
    histogram_frame->SetDirectory(getROOTDirectory());

    // Create the canvas for the line plot and set orientation
    auto canvas = std::make_unique<TCanvas>(("line_plot_" + std::to_string(event_num)).c_str(),
                                            ("Propagation of charge for event " + std::to_string(event_num)).c_str(),
                                            1280,
                                            1024);
    canvas->cd();
    canvas->SetTheta(config_.get<float>("output_plots_theta") * 180.0f / ROOT::Math::Pi());
    canvas->SetPhi(config_.get<float>("output_plots_phi") * 180.0f / ROOT::Math::Pi());

    // Draw the frame on the canvas
    histogram_frame->GetXaxis()->SetTitle(
        (std::string("x ") + (config_.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)")).c_str());
    histogram_frame->GetYaxis()->SetTitle(
        (std::string("y ") + (config_.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)")).c_str());
    histogram_frame->GetZaxis()->SetTitle("z (mm)");
    histogram_frame->Draw();

    // Loop over all point sets created during propagation
    // The vector of unique_pointers is required in order not to delete the objects before the canvas is drawn.
    std::vector<std::unique_ptr<TPolyLine3D>> lines;
    short current_color = 1;
    for(auto& [deposit, points] : output_plot_points) {
        auto line = std::make_unique<TPolyLine3D>();
        for(auto& point : points) {
            line->SetNextPoint(point.x(), point.y(), point.z());
        }
        // Plot all lines with at least three points with different color
        if(line->GetN() >= 3) {
            EColor plot_color = (deposit.getType() == CarrierType::ELECTRON ? EColor::kAzure : EColor::kOrange);
            current_color = static_cast<short int>(plot_color - 9 + (static_cast<int>(current_color) + 1) % 19);
            line->SetLineColor(current_color);
            line->Draw("same");
        }
        lines.push_back(std::move(line));
    }

    // Draw and write canvas to module output file, then clear the stored lines
    canvas->Draw();
    getROOTDirectory()->WriteTObject(canvas.get());
    lines.clear();

    // Create canvas for GIF animition of process
    canvas = std::make_unique<TCanvas>(("animation_" + std::to_string(event_num)).c_str(),
                                       ("Propagation of charge for event " + std::to_string(event_num)).c_str(),
                                       1280,
                                       1024);
    canvas->cd();

    // Change axis labels if close to zero or PI as they behave different here
    if(std::fabs(config_.get<double>("output_plots_theta") / (ROOT::Math::Pi() / 2.0) -
                 std::round(config_.get<double>("output_plots_theta") / (ROOT::Math::Pi() / 2.0))) < 1e-6 ||
       std::fabs(config_.get<double>("output_plots_phi") / (ROOT::Math::Pi() / 2.0) -
                 std::round(config_.get<double>("output_plots_phi") / (ROOT::Math::Pi() / 2.0))) < 1e-6) {
        histogram_frame->GetXaxis()->SetLabelOffset(-0.1f);
        histogram_frame->GetYaxis()->SetLabelOffset(-0.075f);
    } else {
        histogram_frame->GetXaxis()->SetTitleOffset(2.0f);
        histogram_frame->GetYaxis()->SetTitleOffset(2.0f);
    }

    // Draw frame on canvas
    histogram_frame->Draw();

    if(output_animations_) {
        // Create the contour histogram
        std::vector<std::string> file_name_contour;
        std::vector<TH2F*> histogram_contour;
        file_name_contour.push_back(createOutputFile("contourX" + std::to_string(event_num) + ".gif"));
        histogram_contour.push_back(new TH2F(("contourX_" + getUniqueName() + "_" + std::to_string(event_num)).c_str(),
                                             "",
                                             100,
                                             minY,
                                             maxY,
                                             100,
                                             model_->getSensorCenter().z() - model_->getSensorSize().z() / 2.0,
                                             model_->getSensorCenter().z() + model_->getSensorSize().z() / 2.0));
        histogram_contour.back()->SetDirectory(getROOTDirectory());
        file_name_contour.push_back(createOutputFile("contourY" + std::to_string(event_num) + ".gif"));
        histogram_contour.push_back(new TH2F(("contourY_" + getUniqueName() + "_" + std::to_string(event_num)).c_str(),
                                             "",
                                             100,
                                             minX,
                                             maxX,
                                             100,
                                             model_->getSensorCenter().z() - model_->getSensorSize().z() / 2.0,
                                             model_->getSensorCenter().z() + model_->getSensorSize().z() / 2.0));
        histogram_contour.back()->SetDirectory(getROOTDirectory());
        file_name_contour.push_back(createOutputFile("contourZ" + std::to_string(event_num) + ".gif"));
        histogram_contour.push_back(new TH2F(("contourZ_" + getUniqueName() + "_" + std::to_string(event_num)).c_str(),
                                             "",
                                             100,
                                             minX,
                                             maxX,
                                             100,
                                             minY,
                                             maxY));
        histogram_contour.back()->SetDirectory(getROOTDirectory());

        // Create file and disable statistics for histogram
        std::string file_name_anim = createOutputFile("animation" + std::to_string(event_num) + ".gif");
        for(size_t i = 0; i < 3; ++i) {
            histogram_contour[i]->SetStats(false);
        }

        // Remove temporary created files
        auto ret = remove(file_name_anim.c_str());
        for(size_t i = 0; i < 3; ++i) {
            ret |= remove(file_name_contour[i].c_str());
        }
        if(ret != 0) {
            throw ModuleError("Could not delete temporary animation files.");
        }

        // Create color table
        TColor* colors[80]; // NOLINT
        for(int i = 20; i < 100; ++i) {
            auto color_idx = TColor::GetFreeColorIndex();
            colors[i - 20] = new TColor(color_idx,
                                        static_cast<float>(i) / 100.0f - 0.2f,
                                        static_cast<float>(i) / 100.0f - 0.2f,
                                        static_cast<float>(i) / 100.0f - 0.2f);
        }

        // Create animation of moving charges
        auto animation_time = static_cast<unsigned int>(
            std::round((Units::convert(config_.get<long double>("output_plots_step"), "ms") / 10.0) *
                       config_.get<long double>("output_animations_time_scaling", 1e9)));
        unsigned long plot_idx = 0;
        unsigned int point_cnt = 0;
        LOG_PROGRESS(INFO, getUniqueName() + "_OUTPUT_PLOTS") << "Written 0 of " << tot_point_cnt << " points for animation";
        while(point_cnt < tot_point_cnt) {
            std::vector<std::unique_ptr<TPolyMarker3D>> markers;
            unsigned long min_idx_diff = std::numeric_limits<unsigned long>::max();

            // Reset the canvas
            canvas->Clear();
            canvas->SetTheta(config_.get<float>("output_plots_theta") * 180.0f / ROOT::Math::Pi());
            canvas->SetPhi(config_.get<float>("output_plots_phi") * 180.0f / ROOT::Math::Pi());
            canvas->Draw();

            // Reset the histogram frame
            histogram_frame->SetTitle("Charge propagation in sensor");
            histogram_frame->GetXaxis()->SetTitle(
                (std::string("x ") + (config_.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)")).c_str());
            histogram_frame->GetYaxis()->SetTitle(
                (std::string("y ") + (config_.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)")).c_str());
            histogram_frame->GetZaxis()->SetTitle("z (mm)");
            histogram_frame->Draw();

            auto text = std::make_unique<TPaveText>(-0.75, -0.75, -0.60, -0.65);
            auto time_ns = Units::convert(plot_idx * config_.get<long double>("output_plots_step"), "ns");
            std::stringstream sstr;
            sstr << std::fixed << std::setprecision(2) << time_ns << "ns";
            auto time_str = std::string(8 - sstr.str().size(), ' ');
            time_str += sstr.str();
            text->AddText(time_str.c_str());
            text->Draw();

            // Plot all the required points
            for(auto& [deposit, points] : output_plot_points) {
                auto diff = static_cast<unsigned long>(
                    std::round((deposit.getGlobalTime() - start_time) / config_.get<long double>("output_plots_step")));
                if(plot_idx < diff) {
                    min_idx_diff = std::min(min_idx_diff, diff - plot_idx);
                    continue;
                }
                auto idx = plot_idx - diff;
                if(idx >= points.size()) {
                    continue;
                }
                min_idx_diff = 0;

                auto marker = std::make_unique<TPolyMarker3D>();
                marker->SetMarkerStyle(kFullCircle);
                marker->SetMarkerSize(
                    static_cast<float>(deposit.getCharge() * config_.get<double>("output_animations_marker_size", 1)) /
                    static_cast<float>(max_charge));
                auto initial_z_perc = static_cast<int>(
                    ((points[0].z() + model_->getSensorSize().z() / 2.0) / model_->getSensorSize().z()) * 80);
                initial_z_perc = std::max(std::min(79, initial_z_perc), 0);
                if(config_.get<bool>("output_animations_color_markers")) {
                    marker->SetMarkerColor(static_cast<Color_t>(colors[initial_z_perc]->GetNumber()));
                }
                marker->SetNextPoint(points[idx].x(), points[idx].y(), points[idx].z());
                marker->Draw();
                markers.push_back(std::move(marker));

                histogram_contour[0]->Fill(points[idx].y(), points[idx].z(), deposit.getCharge());
                histogram_contour[1]->Fill(points[idx].x(), points[idx].z(), deposit.getCharge());
                histogram_contour[2]->Fill(points[idx].x(), points[idx].y(), deposit.getCharge());
                ++point_cnt;
            }

            // Create a step in the animation
            if(min_idx_diff != 0) {
                canvas->Print((file_name_anim + "+100").c_str());
                plot_idx += min_idx_diff;
            } else {
                // print animation
                if(point_cnt < tot_point_cnt - 1) {
                    canvas->Print((file_name_anim + "+" + std::to_string(animation_time)).c_str());
                } else {
                    canvas->Print((file_name_anim + "++100").c_str());
                }

                // Draw and print contour histograms
                for(size_t i = 0; i < 3; ++i) {
                    canvas->Clear();
                    canvas->SetTitle((std::string("Contour of charge propagation projected on the ") +
                                      static_cast<char>('X' + i) + "-axis")
                                         .c_str());
                    switch(i) {
                    case 0 /* x */:
                        histogram_contour[i]->GetXaxis()->SetTitle(
                            (std::string("y ") + (config_.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)"))
                                .c_str());
                        histogram_contour[i]->GetYaxis()->SetTitle("z (mm)");
                        break;
                    case 1 /* y */:
                        histogram_contour[i]->GetXaxis()->SetTitle(
                            (std::string("x ") + (config_.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)"))
                                .c_str());
                        histogram_contour[i]->GetYaxis()->SetTitle("z (mm)");
                        break;
                    case 2 /* z */:
                        histogram_contour[i]->GetXaxis()->SetTitle(
                            (std::string("x ") + (config_.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)"))
                                .c_str());
                        histogram_contour[i]->GetYaxis()->SetTitle(
                            (std::string("y ") + (config_.get<bool>("output_plots_use_pixel_units") ? "(pixels)" : "(mm)"))
                                .c_str());
                        break;
                    default:;
                    }
                    histogram_contour[i]->SetMinimum(1);
                    histogram_contour[i]->SetMaximum(total_charge /
                                                     config_.get<double>("output_animations_contour_max_scaling", 10));
                    histogram_contour[i]->Draw("CONTZ 0");
                    if(point_cnt < tot_point_cnt - 1) {
                        canvas->Print((file_name_contour[i] + "+" + std::to_string(animation_time)).c_str());
                    } else {
                        canvas->Print((file_name_contour[i] + "++100").c_str());
                    }
                    histogram_contour[i]->Reset();
                }
                ++plot_idx;
            }
            markers.clear();

            LOG_PROGRESS(INFO, getUniqueName() + "_OUTPUT_PLOTS")
                << "Written " << point_cnt << " of " << tot_point_cnt << " points for animation";
        }
    }
}

void GenericPropagationModule::initialize() {

    auto detector = getDetector();

    // Check for electric field and output warning for slow propagation if not defined
    if(!detector->hasElectricField()) {
        LOG(WARNING) << "This detector does not have an electric field.";
    }

    // For linear fields we can in addition check if the correct carriers are propagated
    if(detector->getElectricFieldType() == FieldType::LINEAR) {
        auto model = detector_->getModel();
        auto probe_point = ROOT::Math::XYZPoint(model->getSensorCenter().x(),
                                                model->getSensorCenter().y(),
                                                model->getSensorCenter().z() + model->getSensorSize().z() / 2.01);

        // Get the field close to the implants and check its sign:
        auto efield = detector->getElectricField(probe_point);
        auto direction = std::signbit(efield.z());
        // Compare with propagated carrier type:
        if(direction && !propagate_electrons_) {
            LOG(WARNING) << "Electric field indicates electron collection at implants, but electrons are not propagated!";
        }
        if(!direction && !propagate_holes_) {
            LOG(WARNING) << "Electric field indicates hole collection at implants, but holes are not propagated!";
        }
    }

    // Check for magnetic field
    has_magnetic_field_ = detector->hasMagneticField();
    if(has_magnetic_field_) {
        if(config_.get<bool>("ignore_magnetic_field")) {
            has_magnetic_field_ = false;
            LOG(WARNING) << "A magnetic field is switched on, but is set to be ignored for this module.";
        } else {
            LOG(DEBUG) << "This detector sees a magnetic field.";
            magnetic_field_ = detector_->getMagneticField();
        }
    }

    if(output_plots_) {
        step_length_histo_ =
            CreateHistogram<TH1D>("step_length_histo",
                                  "Step length;length [#mum];integration steps",
                                  100,
                                  0,
                                  static_cast<double>(Units::convert(0.25 * model_->getSensorSize().z(), "um")));

        drift_time_histo_ = CreateHistogram<TH1D>("drift_time_histo",
                                                  "Drift time;Drift time [ns];charge carriers",
                                                  static_cast<int>(Units::convert(integration_time_, "ns") * 5),
                                                  0,
                                                  static_cast<double>(Units::convert(integration_time_, "ns")));

        uncertainty_histo_ =
            CreateHistogram<TH1D>("uncertainty_histo",
                                  "Position uncertainty;uncertainty [nm];integration steps",
                                  100,
                                  0,
                                  static_cast<double>(4 * Units::convert(config_.get<double>("spatial_precision"), "nm")));

        group_size_histo_ = CreateHistogram<TH1D>("group_size_histo",
                                                  "Charge carrier group size;group size;number of groups transported",
                                                  static_cast<int>(charge_per_step_ - 1),
                                                  1,
                                                  static_cast<double>(charge_per_step_));

        recombine_histo_ =
            CreateHistogram<TH1D>("recombination_histo",
                                  "Fraction of recombined charge carriers;recombination [N / N_{total}] ;number of events",
                                  100,
                                  0,
                                  1);
    }

    // Prepare mobility model
    try {
        mobility_ = Mobility(config_.get<std::string>("mobility_model"), temperature_, detector->hasDopingProfile());
    } catch(ModelError& e) {
        throw InvalidValueError(config_, "mobility_model", e.what());
    }

    // Prepare recombination model
    try {
        recombination_ = Recombination(config_.get<std::string>("recombination_model"), detector->hasDopingProfile());
    } catch(ModelError& e) {
        throw InvalidValueError(config_, "recombination_model", e.what());
    }
}

void GenericPropagationModule::run(Event* event) {
    auto deposits_message = messenger_->fetchMessage<DepositedChargeMessage>(this, event);

    // Create vector of propagated charges to output
    std::vector<PropagatedCharge> propagated_charges;

    // List of points to plot to plot for output plots
    OutputPlotPoints output_plot_points;

    // Loop over all deposits for propagation
    LOG(TRACE) << "Propagating charges in sensor";
    unsigned int propagated_charges_count = 0;
    unsigned int recombined_charges_count = 0;
    unsigned int step_count = 0;
    long double total_time = 0;
    for(const auto& deposit : deposits_message->getData()) {

        if((deposit.getType() == CarrierType::ELECTRON && !propagate_electrons_) ||
           (deposit.getType() == CarrierType::HOLE && !propagate_holes_)) {
            LOG(DEBUG) << "Skipping charge carriers (" << deposit.getType() << ") on "
                       << Units::display(deposit.getLocalPosition(), {"mm", "um"});
            continue;
        }

        // Only process if within requested integration time:
        if(deposit.getLocalTime() > integration_time_) {
            LOG(DEBUG) << "Skipping charge carriers deposited beyond integration time: "
                       << Units::display(deposit.getGlobalTime(), "ns") << " global / "
                       << Units::display(deposit.getLocalTime(), {"ns", "ps"}) << " local";
            continue;
        }

        // Loop over all charges in the deposit
        unsigned int charges_remaining = deposit.getCharge();

        LOG(DEBUG) << "Set of charge carriers (" << deposit.getType() << ") on "
                   << Units::display(deposit.getLocalPosition(), {"mm", "um"});

        auto charge_per_step = charge_per_step_;
        while(charges_remaining > 0) {
            // Define number of charges to be propagated and remove charges of this step from the total
            if(charge_per_step > charges_remaining) {
                charge_per_step = charges_remaining;
            }
            charges_remaining -= charge_per_step;

            // Get position and propagate through sensor
            auto initial_position = deposit.getLocalPosition();

            // Add point of deposition to the output plots if requested
            if(output_linegraphs_) {
                auto global_position = detector_->getGlobalPosition(initial_position);
                std::lock_guard<std::mutex> lock{stats_mutex_};
                output_plot_points.emplace_back(PropagatedCharge(initial_position,
                                                                 global_position,
                                                                 deposit.getType(),
                                                                 charge_per_step,
                                                                 deposit.getLocalTime(),
                                                                 deposit.getGlobalTime()),
                                                std::vector<ROOT::Math::XYZPoint>());
            }

            // Propagate a single charge deposit
            auto [final_position, time, alive] = propagate(
                initial_position, deposit.getType(), deposit.getLocalTime(), event->getRandomEngine(), output_plot_points);

            if(!alive) {
                LOG(DEBUG) << " Recombined " << charge_per_step << " at " << Units::display(final_position, {"mm", "um"})
                           << " in " << Units::display(time, "ns") << " time, removing";
                recombined_charges_count += charge_per_step;
                continue;
            }

            LOG(DEBUG) << " Propagated " << charge_per_step << " to " << Units::display(final_position, {"mm", "um"})
                       << " in " << Units::display(time, "ns") << " time";

            // Create a new propagated charge and add it to the list
            auto global_position = detector_->getGlobalPosition(final_position);
            PropagatedCharge propagated_charge(final_position,
                                               global_position,
                                               deposit.getType(),
                                               charge_per_step,
                                               deposit.getLocalTime() + time,
                                               deposit.getGlobalTime() + time,
                                               &deposit);

            propagated_charges.push_back(std::move(propagated_charge));

            // Update statistical information
            ++step_count;
            propagated_charges_count += charge_per_step;
            total_time += charge_per_step * time;
            if(output_plots_) {
                drift_time_histo_->Fill(static_cast<double>(Units::convert(time, "ns")), charge_per_step);
                group_size_histo_->Fill(charge_per_step);
            }
        }
    }

    // Output plots if required
    if(output_linegraphs_) {
        create_output_plots(event->number, output_plot_points);
    }

    // Write summary and update statistics
    long double average_time = total_time / std::max(1u, propagated_charges_count);
    LOG(INFO) << "Propagated " << propagated_charges_count << " charges in " << step_count << " steps in average time of "
              << Units::display(average_time, "ns") << std::endl
              << "Recombined " << recombined_charges_count << " charges during transport";
    total_propagated_charges_ += propagated_charges_count;
    total_steps_ += step_count;
    total_time_picoseconds_ += static_cast<long unsigned int>(total_time * 1e3);

    if(output_plots_) {
        recombine_histo_->Fill(static_cast<double>(recombined_charges_count) /
                               (propagated_charges_count + recombined_charges_count));
    }

    // Create a new message with propagated charges
    auto propagated_charge_message = std::make_shared<PropagatedChargeMessage>(std::move(propagated_charges), detector_);

    // Dispatch the message with propagated charges
    messenger_->dispatchMessage(this, propagated_charge_message, event);
}

/**
 * Propagation is simulated using a parameterization for the electron mobility. This is used to calculate the electron
 * velocity at every point with help of the electric field map of the detector. An Runge-Kutta integration is applied in
 * multiple steps, adding a random diffusion to the propagating charge every step.
 */
std::tuple<ROOT::Math::XYZPoint, double, bool>
GenericPropagationModule::propagate(const ROOT::Math::XYZPoint& pos,
                                    const CarrierType& type,
                                    const double initial_time,
                                    RandomNumberGenerator& random_generator,
                                    OutputPlotPoints& output_plot_points) const {
    // Create a runge kutta solver using the electric field as step function
    Eigen::Vector3d position(pos.x(), pos.y(), pos.z());

    // Define a function to compute the diffusion
    auto carrier_diffusion = [&](double efield_mag, double doping_concentration, double timestep) -> Eigen::Vector3d {
        double diffusion_constant = boltzmann_kT_ * mobility_(type, efield_mag, doping_concentration);
        double diffusion_std_dev = std::sqrt(2. * diffusion_constant * timestep);

        // Compute the independent diffusion in three
        allpix::normal_distribution<double> gauss_distribution(0, diffusion_std_dev);
        Eigen::Vector3d diffusion;
        for(int i = 0; i < 3; ++i) {
            diffusion[i] = gauss_distribution(random_generator);
        }
        return diffusion;
    };

    // Survival probability of this charge carrier package, evaluated at every step
    std::uniform_real_distribution<double> survival(0, 1);

    // Define lambda functions to compute the charge carrier velocity with or without magnetic field
    std::function<Eigen::Vector3d(double, const Eigen::Vector3d&)> carrier_velocity_noB =
        [&](double, const Eigen::Vector3d& cur_pos) -> Eigen::Vector3d {
        auto raw_field = detector_->getElectricField(static_cast<ROOT::Math::XYZPoint>(cur_pos));
        Eigen::Vector3d efield(raw_field.x(), raw_field.y(), raw_field.z());
        auto doping = detector_->getDopingConcentration(static_cast<ROOT::Math::XYZPoint>(cur_pos));

        return static_cast<int>(type) * mobility_(type, efield.norm(), doping) * efield;
    };

    std::function<Eigen::Vector3d(double, const Eigen::Vector3d&)> carrier_velocity_withB =
        [&](double, const Eigen::Vector3d& cur_pos) -> Eigen::Vector3d {
        auto raw_field = detector_->getElectricField(static_cast<ROOT::Math::XYZPoint>(cur_pos));
        Eigen::Vector3d efield(raw_field.x(), raw_field.y(), raw_field.z());

        Eigen::Vector3d velocity;
        Eigen::Vector3d bfield(magnetic_field_.x(), magnetic_field_.y(), magnetic_field_.z());

        auto doping = detector_->getDopingConcentration(static_cast<ROOT::Math::XYZPoint>(cur_pos));

        auto mob = mobility_(type, efield.norm(), doping);
        auto exb = efield.cross(bfield);

        Eigen::Vector3d term1;
        double hallFactor = (type == CarrierType::ELECTRON ? electron_Hall_ : hole_Hall_);
        term1 = static_cast<int>(type) * mob * hallFactor * exb;

        Eigen::Vector3d term2 = mob * mob * hallFactor * hallFactor * efield.dot(bfield) * bfield;

        auto rnorm = 1 + mob * mob * hallFactor * hallFactor * bfield.dot(bfield);
        return static_cast<int>(type) * mob * (efield + term1 + term2) / rnorm;
    };

    // Create the runge kutta solver with an RKF5 tableau, using different velocity calculators depending on the magnetic
    // field
    auto runge_kutta = make_runge_kutta(
        tableau::RK5, (has_magnetic_field_ ? carrier_velocity_withB : carrier_velocity_noB), timestep_start_, position);

    // Continue propagation until the deposit is outside the sensor
    Eigen::Vector3d last_position = position;
    double last_time = 0;
    size_t next_idx = 0;
    bool is_alive = true;
    while(detector_->getModel()->isWithinSensor(static_cast<ROOT::Math::XYZPoint>(position)) &&
          (initial_time + runge_kutta.getTime()) < integration_time_ && is_alive) {
        // Update output plots if necessary (depending on the plot step)
        if(output_linegraphs_) {
            auto time_idx = static_cast<size_t>(runge_kutta.getTime() / output_plots_step_);
            while(next_idx <= time_idx) {
                output_plot_points.back().second.push_back(static_cast<ROOT::Math::XYZPoint>(position));
                next_idx = output_plot_points.back().second.size();
            }
        }

        // Save previous position and time
        last_position = position;
        last_time = runge_kutta.getTime();

        // Execute a Runge Kutta step
        auto step = runge_kutta.step();

        // Get the current result and timestep
        auto timestep = runge_kutta.getTimeStep();
        position = runge_kutta.getValue();

        // Get electric field at current position and fall back to empty field if it does not exist
        auto efield = detector_->getElectricField(static_cast<ROOT::Math::XYZPoint>(position));
        auto doping = detector_->getDopingConcentration(static_cast<ROOT::Math::XYZPoint>(position));

        // Apply diffusion step
        auto diffusion = carrier_diffusion(std::sqrt(efield.Mag2()), doping, timestep);
        position += diffusion;
        runge_kutta.setValue(position);

        // Check if charge carrier is still alive:
        is_alive = !recombination_(type,
                                   detector_->getDopingConcentration(static_cast<ROOT::Math::XYZPoint>(position)),
                                   survival(random_generator),
                                   timestep);

        LOG(TRACE) << "Step from " << Units::display(static_cast<ROOT::Math::XYZPoint>(last_position), {"um", "mm"})
                   << " to " << Units::display(static_cast<ROOT::Math::XYZPoint>(position), {"um", "mm"}) << " at "
                   << Units::display(initial_time + runge_kutta.getTime(), {"ps", "ns", "us"})
                   << (is_alive ? "" : ", recombined");
        // Adapt step size to match target precision
        double uncertainty = step.error.norm();

        // Update step length histogram
        if(output_plots_) {
            step_length_histo_->Fill(static_cast<double>(Units::convert(step.value.norm(), "um")));
            uncertainty_histo_->Fill(static_cast<double>(Units::convert(step.error.norm(), "nm")));
        }

        // Lower timestep when reaching the sensor edge
        if(std::fabs(model_->getSensorSize().z() / 2.0 - position.z()) < 2 * step.value.z()) {
            timestep *= 0.75;
        } else {
            if(uncertainty > target_spatial_precision_) {
                timestep *= 0.75;
            } else if(2 * uncertainty < target_spatial_precision_) {
                timestep *= 1.5;
            }
        }
        // Limit the timestep to certain minimum and maximum step sizes
        if(timestep > timestep_max_) {
            timestep = timestep_max_;
        } else if(timestep < timestep_min_) {
            timestep = timestep_min_;
        }
        runge_kutta.setTimeStep(timestep);
    }

    // Find proper final position in the sensor
    auto time = runge_kutta.getTime();
    if(!detector_->getModel()->isWithinSensor(static_cast<ROOT::Math::XYZPoint>(position))) {
        auto check_position = position;
        check_position.z() = last_position.z();
        if(position.z() > 0 && detector_->getModel()->isWithinSensor(static_cast<ROOT::Math::XYZPoint>(check_position))) {
            // Carrier left sensor on the side of the pixel grid, interpolate end point on surface
            auto z_cur_border = std::fabs(position.z() - model_->getSensorSize().z() / 2.0);
            auto z_last_border = std::fabs(model_->getSensorSize().z() / 2.0 - last_position.z());
            auto z_total = z_cur_border + z_last_border;
            position = (z_last_border / z_total) * position + (z_cur_border / z_total) * last_position;
            time = (z_last_border / z_total) * time + (z_cur_border / z_total) * last_time;
        } else {
            // Carrier left sensor on any order border, use last position inside instead
            position = last_position;
            time = last_time;
        }
    }

    // If requested, remove charge drift lines from plots if they did not reach the implant side within the integration time:
    if(output_linegraphs_ && output_plots_lines_at_implants_) {
        // If drift time is larger than integration time or the charge carriers have been collected at the backside, remove
        if(time >= integration_time_ || last_position.z() < -model_->getSensorSize().z() * 0.45) {
            output_plot_points.pop_back();
        }
    }

    if(!is_alive) {
        LOG(DEBUG) << "Charge carrier recombined after " << Units::display(last_time, {"ns"});
    }

    // Return the final position of the propagated charge
    return std::make_tuple(static_cast<ROOT::Math::XYZPoint>(position), initial_time + time, is_alive);
}

void GenericPropagationModule::finalize() {
    if(output_plots_) {
        step_length_histo_->Write();
        drift_time_histo_->Write();
        uncertainty_histo_->Write();
        group_size_histo_->Write();
        recombine_histo_->Write();
    }

    long double average_time = static_cast<long double>(total_time_picoseconds_) / 1e3 /
                               std::max(1u, static_cast<unsigned int>(total_propagated_charges_));
    LOG(INFO) << "Propagated total of " << total_propagated_charges_ << " charges in " << total_steps_
              << " steps in average time of " << Units::display(average_time, "ns");
}


// Copyright (C) 2006-2008 Garth N. Wells
//
// This file is part of DOLFIN.
//
// DOLFIN is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// DOLFIN is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with DOLFIN. If not, see <http://www.gnu.org/licenses/>.
//
// Modified by Anders Logg 2006-2010.
// Modified by Kent-Andre Mardal 2008.
// Modified by Martin Sandve Alnes 2008.
//
// First added:  2006-04-04
// Last changed: 2011-01-14

#include <algorithm>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/vector_expression.hpp>
#include <boost/unordered_set.hpp>

#include <dolfin/log/dolfin_log.h>
#include <dolfin/common/Array.h>
#include "uBLASVector.h"
#include "uBLASFactory.h"
#include "LinearAlgebraFactory.h"

#ifdef HAS_PETSC
#include "PETScVector.h"
#endif

using namespace dolfin;

//-----------------------------------------------------------------------------
uBLASVector::uBLASVector(): x(new ublas_vector(0))
{
  // Do nothing
}
//-----------------------------------------------------------------------------
uBLASVector::uBLASVector(uint N): x(new ublas_vector(N))
{
  // Set all entries to zero
  x->clear();
}
//-----------------------------------------------------------------------------
uBLASVector::uBLASVector(const uBLASVector& x): x(new ublas_vector(*(x.x)))
{
  // Do nothing
}
//-----------------------------------------------------------------------------
uBLASVector::uBLASVector(boost::shared_ptr<ublas_vector> x) : x(x)
{
  // Do nothing
}
//-----------------------------------------------------------------------------
uBLASVector::~uBLASVector()
{
  // Do nothing
}
//-----------------------------------------------------------------------------
uBLASVector* uBLASVector::copy() const
{
  return new uBLASVector(*this);
}
//-----------------------------------------------------------------------------
void uBLASVector::resize(uint N)
{
  if (x->size() == N)
    return;
  x->resize(N, false);

  // Set vector to zero to prevent random numbers entering the vector.
  // Fixes this bug: https://bugs.launchpad.net/dolfin/+bug/594954
  x->clear();
}
//-----------------------------------------------------------------------------
void uBLASVector::resize(std::pair<uint, uint> range)
{
  if (range.first != 0)
  {
    dolfin_error("uBLASVector.cpp",
                 "resize uBLAS vector",
                 "Distributed vectors not supported by uBLAS backend");
  }

  resize(range.second - range.first);
}
//-----------------------------------------------------------------------------
void uBLASVector::resize(std::pair<uint, uint> range,
                    const std::vector<uint>& ghost_indices)
{
  if (range.first != 0)
  {
    dolfin_error("uBLASVector.cpp",
                 "resize uBLAS vector",
                 "Distributed vectors not supported by uBLAS backend");
  }

  if (ghost_indices.size() != 0)
  {
    dolfin_error("uBLASVector.cpp",
                 "resize uBLAS vector",
                 "Distributed vectors not supported by uBLAS backend");
  }

  resize(range.second - range.first);
}
//-----------------------------------------------------------------------------
dolfin::uint uBLASVector::size() const
{
  return x->size();
}
//-----------------------------------------------------------------------------
std::pair<dolfin::uint, dolfin::uint> uBLASVector::local_range() const
{
  return std::make_pair(0, size());
}
//-----------------------------------------------------------------------------
bool uBLASVector::owns_index(uint i) const
{
  if (i < size())
    return true;
  else
    return false;
}
//-----------------------------------------------------------------------------
void uBLASVector::get_local(double* block, uint m, const uint* rows) const
{
  for (uint i = 0; i < m; i++)
    block[i] = (*x)(rows[i]);
}
//-----------------------------------------------------------------------------
void uBLASVector::get_local(Array<double>& values) const
{
  values.resize(size());
  for (uint i = 0; i < size(); i++)
    values[i] = (*x)(i);
}
//-----------------------------------------------------------------------------
void uBLASVector::set_local(const Array<double>& values)
{
  dolfin_assert(values.size() == size());
  for (uint i = 0; i < size(); i++)
    (*x)(i) = values[i];
}
//-----------------------------------------------------------------------------
void uBLASVector::add_local(const Array<double>& values)
{
  dolfin_assert(values.size() == size());
  for (uint i = 0; i < size(); i++)
    (*x)(i) += values[i];
}
//-----------------------------------------------------------------------------
void uBLASVector::gather(GenericVector& x, const Array<uint>& indices) const
{
  not_working_in_parallel("uBLASVector::gather)");

  const uint _size = indices.size();
  dolfin_assert(this->size() >= _size);

  x.resize(_size);
  ublas_vector& _x = x.down_cast<uBLASVector>().vec();
  for (uint i = 0; i < _size; i++)
    _x(i) = (*this->x)(indices[i]);
}
//-----------------------------------------------------------------------------
void uBLASVector::gather(Array<double>& x, const Array<uint>& indices) const
{
  not_working_in_parallel("uBLASVector::gather)");

  const uint _size = indices.size();
  x.resize(_size);
  dolfin_assert(x.size() == _size);
  for (uint i = 0; i < _size; i++)
    x[i] = (*this->x)(indices[i]);
}
//-----------------------------------------------------------------------------
void uBLASVector::gather_on_zero(Array<double>& x) const
{
  not_working_in_parallel("uBLASVector::gather_on_zero)");

  get_local(x);
}
//-----------------------------------------------------------------------------
void uBLASVector::set(const double* block, uint m, const uint* rows)
{
  for (uint i = 0; i < m; i++)
    (*x)(rows[i]) = block[i];
}
//-----------------------------------------------------------------------------
void uBLASVector::add(const double* block, uint m, const uint* rows)
{
  for (uint i = 0; i < m; i++)
    (*x)(rows[i]) += block[i];
}
//-----------------------------------------------------------------------------
void uBLASVector::apply(std::string mode)
{
  // Do nothing
}
//-----------------------------------------------------------------------------
void uBLASVector::zero()
{
  x->clear();
}
//-----------------------------------------------------------------------------
double uBLASVector::norm(std::string norm_type) const
{
  if (norm_type == "l1")
    return norm_1(*x);
  else if (norm_type == "l2")
    return norm_2(*x);
  else if (norm_type == "linf")
    return norm_inf(*x);
  else
  {
    dolfin_error("uBLASVector.cpp",
                 "compute norm of uBLAS vector",
                 "Unknown norm type (\"%s\")", norm_type.c_str());
  }

  return 0.0;
}
//-----------------------------------------------------------------------------
double uBLASVector::min() const
{
  double value = *std::min_element(x->begin(), x->end());
  return value;
}
//-----------------------------------------------------------------------------
double uBLASVector::max() const
{
  double value = *std::max_element(x->begin(), x->end());
  return value;
}
//-----------------------------------------------------------------------------
double uBLASVector::sum() const
{
  return ublas::sum(*x);
}
//-----------------------------------------------------------------------------
double uBLASVector::sum(const Array<uint>& rows) const
{
  boost::unordered_set<uint> row_set;
  double _sum = 0.0;
  for (uint i = 0; i < rows.size(); ++i)
  {
    const uint index = rows[i];
    dolfin_assert(index < size());
    if (row_set.find(index) == row_set.end())
    {
      _sum += (*x)[index];
      row_set.insert(index);
    }
  }
  return _sum;
}
//-----------------------------------------------------------------------------
void uBLASVector::axpy(double a, const GenericVector& y)
{
  if (size() != y.size())
  {
    dolfin_error("uBLASVector.cpp",
                 "perform axpy operation with uBLAS vector",
                 "Vectors are not of the same size");
  }

  (*x) += a * y.down_cast<uBLASVector>().vec();
}
//-----------------------------------------------------------------------------
void uBLASVector::abs()
{
  dolfin_assert(x);
  const uint size = x->size();
  for (uint i = 0; i < size; i++)
    (*x)[i] = std::abs((*x)[i]);
}
//-----------------------------------------------------------------------------
double uBLASVector::inner(const GenericVector& y) const
{
  return ublas::inner_prod(*x, y.down_cast<uBLASVector>().vec());
}
//-----------------------------------------------------------------------------
const GenericVector& uBLASVector::operator= (const GenericVector& v)
{
  *this = v.down_cast<uBLASVector>();
  return *this;
}
//-----------------------------------------------------------------------------
const uBLASVector& uBLASVector::operator= (const uBLASVector& v)
{
  if (size() != v.size())
  {
    dolfin_error("uBLASVector.cpp",
                 "assign one vector to another",
                 "Vectors must be of the same length when assigning. "
                 "Consider using the copy constructor instead");
  }

  assert(x);
  *x = v.vec();
  return *this;
}
//-----------------------------------------------------------------------------
const uBLASVector& uBLASVector::operator= (double a)
{
  x->ublas_vector::assign(ublas::scalar_vector<double> (x->size(), a));
  return *this;
}
//-----------------------------------------------------------------------------
const uBLASVector& uBLASVector::operator*= (const double a)
{
  (*x) *= a;
  return *this;
}
//-----------------------------------------------------------------------------
const uBLASVector& uBLASVector::operator*= (const GenericVector& y)
{
  *x = ublas::element_prod(*x,y.down_cast<uBLASVector>().vec());
  return *this;
}
//-----------------------------------------------------------------------------
const uBLASVector& uBLASVector::operator/= (const double a)
{
  (*x) /= a;
  return *this;
}
//-----------------------------------------------------------------------------
const uBLASVector& uBLASVector::operator+= (const GenericVector& y)
{
  *x += y.down_cast<uBLASVector>().vec();
  return *this;
}
//-----------------------------------------------------------------------------
const uBLASVector& uBLASVector::operator-= (const GenericVector& y)
{
  *x -= y.down_cast<uBLASVector>().vec();
  return *this;
}
//-----------------------------------------------------------------------------
std::string uBLASVector::str(bool verbose) const
{
  std::stringstream s;

  if (verbose)
  {
    s << str(false) << std::endl << std::endl;

    s << "[";
    for (ublas_vector::const_iterator it = x->begin(); it != x->end(); ++it)
    {
      std::stringstream entry;
      entry << std::setiosflags(std::ios::scientific);
      entry << std::setprecision(16);
      entry << *it << " ";
      s << entry.str() << std::endl;
    }
    s << "]";
  }
  else
  {
    s << "<uBLASVector of size " << size() << ">";
  }

  return s.str();
}
//-----------------------------------------------------------------------------
LinearAlgebraFactory& uBLASVector::factory() const
{
  return uBLASFactory<>::instance();
}
//-----------------------------------------------------------------------------


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2013, 2014, 2015.
// Modifications copyright (c) 2013-2015 Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_RELATE_AREAL_AREAL_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_RELATE_AREAL_AREAL_HPP

#include <boost/geometry/core/topological_dimension.hpp>

#include <boost/geometry/util/condition.hpp>
#include <boost/geometry/util/range.hpp>

#include <boost/geometry/algorithms/num_interior_rings.hpp>
#include <boost/geometry/algorithms/detail/point_on_border.hpp>
#include <boost/geometry/algorithms/detail/sub_range.hpp>
#include <boost/geometry/algorithms/detail/single_geometry.hpp>

#include <boost/geometry/algorithms/detail/relate/point_geometry.hpp>
#include <boost/geometry/algorithms/detail/relate/turns.hpp>
#include <boost/geometry/algorithms/detail/relate/boundary_checker.hpp>
#include <boost/geometry/algorithms/detail/relate/follow_helpers.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace relate {

// WARNING!
// TODO: In the worst case calling this Pred in a loop for MultiPolygon/MultiPolygon may take O(NM)
// Use the rtree in this case!

// may be used to set EI and EB for an Areal geometry for which no turns were generated
template <typename OtherAreal, typename Result, bool TransposeResult>
class no_turns_aa_pred
{
public:
    no_turns_aa_pred(OtherAreal const& other_areal, Result & res)
        : m_result(res)
        , m_other_areal(other_areal)
        , m_flags(0)
    {
        // check which relations must be analysed

        if ( ! may_update<interior, interior, '2', TransposeResult>(m_result)
          && ! may_update<boundary, interior, '1', TransposeResult>(m_result)
          && ! may_update<exterior, interior, '2', TransposeResult>(m_result) )
        {
            m_flags |= 1;
        }

        if ( ! may_update<interior, exterior, '2', TransposeResult>(m_result)
          && ! may_update<boundary, exterior, '1', TransposeResult>(m_result) )
        {
            m_flags |= 2;
        }
    }

    template <typename Areal>
    bool operator()(Areal const& areal)
    {
        // if those flags are set nothing will change
        if ( m_flags == 3 )
        {
            return false;
        }

        typedef typename geometry::point_type<Areal>::type point_type;
        point_type pt;
        bool const ok = boost::geometry::point_on_border(pt, areal);

        // TODO: for now ignore, later throw an exception?
        if ( !ok )
        {
            return true;
        }

        // check if the areal is inside the other_areal
        // TODO: This is O(N)
        // Run in a loop O(NM) - optimize!
        int const pig = detail::within::point_in_geometry(pt, m_other_areal);
        //BOOST_ASSERT( pig != 0 );

        // inside
        if ( pig > 0 )
        {
            update<interior, interior, '2', TransposeResult>(m_result);
            update<boundary, interior, '1', TransposeResult>(m_result);
            update<exterior, interior, '2', TransposeResult>(m_result);
            m_flags |= 1;

            // TODO: OPTIMIZE!
            // Only the interior rings of other ONE single geometry must be checked
            // NOT all geometries

            // Check if any interior ring is outside
            ring_identifier ring_id(0, -1, 0);
            int const irings_count = boost::numeric_cast<int>(
                                        geometry::num_interior_rings(areal) );
            for ( ; ring_id.ring_index < irings_count ; ++ring_id.ring_index )
            {
                typename detail::sub_range_return_type<Areal const>::type
                    range_ref = detail::sub_range(areal, ring_id);

                if ( boost::empty(range_ref) )
                {
                    // TODO: throw exception?
                    continue; // ignore
                }

                // TODO: O(N)
                // Optimize!
                int const hpig = detail::within::point_in_geometry(range::front(range_ref), m_other_areal);

                // hole outside
                if ( hpig < 0 )
                {
                    update<interior, exterior, '2', TransposeResult>(m_result);
                    update<boundary, exterior, '1', TransposeResult>(m_result);
                    m_flags |= 2;
                    break;
                }
            }
        }
        // outside
        else
        {
            update<interior, exterior, '2', TransposeResult>(m_result);
            update<boundary, exterior, '1', TransposeResult>(m_result);
            m_flags |= 2;

            // Check if any interior ring is inside
            ring_identifier ring_id(0, -1, 0);
            int const irings_count = boost::numeric_cast<int>(
                                        geometry::num_interior_rings(areal) );
            for ( ; ring_id.ring_index < irings_count ; ++ring_id.ring_index )
            {
                typename detail::sub_range_return_type<Areal const>::type
                    range_ref = detail::sub_range(areal, ring_id);

                if ( boost::empty(range_ref) )
                {
                    // TODO: throw exception?
                    continue; // ignore
                }

                // TODO: O(N)
                // Optimize!
                int const hpig = detail::within::point_in_geometry(range::front(range_ref), m_other_areal);

                // hole inside
                if ( hpig > 0 )
                {
                    update<interior, interior, '2', TransposeResult>(m_result);
                    update<boundary, interior, '1', TransposeResult>(m_result);
                    update<exterior, interior, '2', TransposeResult>(m_result);
                    m_flags |= 1;
                    break;
                }
            }
        }

        return m_flags != 3 && !m_result.interrupt;
    }

private:
    Result & m_result;
    OtherAreal const& m_other_areal;
    int m_flags;
};

// The implementation of an algorithm calculating relate() for A/A
template <typename Geometry1, typename Geometry2>
struct areal_areal
{
    // check Linear / Areal
    BOOST_STATIC_ASSERT(topological_dimension<Geometry1>::value == 2
                     && topological_dimension<Geometry2>::value == 2);

    static const bool interruption_enabled = true;

    typedef typename geometry::point_type<Geometry1>::type point1_type;
    typedef typename geometry::point_type<Geometry2>::type point2_type;

    template <typename Result>
    static inline void apply(Geometry1 const& geometry1, Geometry2 const& geometry2, Result & result)
    {
// TODO: If Areal geometry may have infinite size, change the following line:

        // The result should be FFFFFFFFF
        relate::set<exterior, exterior, result_dimension<Geometry2>::value>(result);// FFFFFFFFd, d in [1,9] or T

        if ( BOOST_GEOMETRY_CONDITION(result.interrupt) )
            return;

        // get and analyse turns
        typedef typename turns::get_turns<Geometry1, Geometry2>::turn_info turn_type;
        std::vector<turn_type> turns;

        interrupt_policy_areal_areal<Result> interrupt_policy(geometry1, geometry2, result);

        turns::get_turns<Geometry1, Geometry2>::apply(turns, geometry1, geometry2, interrupt_policy);
        if ( BOOST_GEOMETRY_CONDITION(result.interrupt) )
            return;

        no_turns_aa_pred<Geometry2, Result, false> pred1(geometry2, result);
        for_each_disjoint_geometry_if<0, Geometry1>::apply(turns.begin(), turns.end(), geometry1, pred1);
        if ( BOOST_GEOMETRY_CONDITION(result.interrupt) )
            return;

        no_turns_aa_pred<Geometry1, Result, true> pred2(geometry1, result);
        for_each_disjoint_geometry_if<1, Geometry2>::apply(turns.begin(), turns.end(), geometry2, pred2);
        if ( BOOST_GEOMETRY_CONDITION(result.interrupt) )
            return;

        if ( turns.empty() )
            return;

        if ( may_update<interior, interior, '2'>(result)
          || may_update<interior, exterior, '2'>(result)
          || may_update<boundary, interior, '1'>(result)
          || may_update<boundary, exterior, '1'>(result)
          || may_update<exterior, interior, '2'>(result) )
        {
            // sort turns
            typedef turns::less<0, turns::less_op_areal_areal<0> > less;
            std::sort(turns.begin(), turns.end(), less());

            /*if ( may_update<interior, exterior, '2'>(result)
              || may_update<boundary, exterior, '1'>(result)
              || may_update<boundary, interior, '1'>(result)
              || may_update<exterior, interior, '2'>(result) )*/
            {
                // analyse sorted turns
                turns_analyser<turn_type, 0> analyser;
                analyse_each_turn(result, analyser, turns.begin(), turns.end());

                if ( BOOST_GEOMETRY_CONDITION(result.interrupt) )
                    return;
            }

            if ( may_update<interior, interior, '2'>(result)
              || may_update<interior, exterior, '2'>(result)
              || may_update<boundary, interior, '1'>(result)
              || may_update<boundary, exterior, '1'>(result)
              || may_update<exterior, interior, '2'>(result) )
            {
                // analyse rings for which turns were not generated
                // or only i/i or u/u was generated
                uncertain_rings_analyser<0, Result, Geometry1, Geometry2> rings_analyser(result, geometry1, geometry2);
                analyse_uncertain_rings<0>::apply(rings_analyser, turns.begin(), turns.end());

                if ( BOOST_GEOMETRY_CONDITION(result.interrupt) )
                    return;
            }
        }

        if ( may_update<interior, interior, '2', true>(result)
          || may_update<interior, exterior, '2', true>(result)
          || may_update<boundary, interior, '1', true>(result)
          || may_update<boundary, exterior, '1', true>(result)
          || may_update<exterior, interior, '2', true>(result) )
        {
            // sort turns
            typedef turns::less<1, turns::less_op_areal_areal<1> > less;
            std::sort(turns.begin(), turns.end(), less());

            /*if ( may_update<interior, exterior, '2', true>(result)
              || may_update<boundary, exterior, '1', true>(result)
              || may_update<boundary, interior, '1', true>(result)
              || may_update<exterior, interior, '2', true>(result) )*/
            {
                // analyse sorted turns
                turns_analyser<turn_type, 1> analyser;
                analyse_each_turn(result, analyser, turns.begin(), turns.end());

                if ( BOOST_GEOMETRY_CONDITION(result.interrupt) )
                    return;
            }

            if ( may_update<interior, interior, '2', true>(result)
              || may_update<interior, exterior, '2', true>(result)
              || may_update<boundary, interior, '1', true>(result)
              || may_update<boundary, exterior, '1', true>(result)
              || may_update<exterior, interior, '2', true>(result) )
            {
                // analyse rings for which turns were not generated
                // or only i/i or u/u was generated
                uncertain_rings_analyser<1, Result, Geometry2, Geometry1> rings_analyser(result, geometry2, geometry1);
                analyse_uncertain_rings<1>::apply(rings_analyser, turns.begin(), turns.end());

                //if ( result.interrupt )
                //    return;
            }
        }
    }

    // interrupt policy which may be passed to get_turns to interrupt the analysis
    // based on the info in the passed result/mask
    template <typename Result>
    class interrupt_policy_areal_areal
    {
    public:
        static bool const enabled = true;

        interrupt_policy_areal_areal(Geometry1 const& geometry1,
                                     Geometry2 const& geometry2,
                                     Result & result)
            : m_result(result)
            , m_geometry1(geometry1)
            , m_geometry2(geometry2)
        {}

        template <typename Range>
        inline bool apply(Range const& turns)
        {
            typedef typename boost::range_iterator<Range const>::type iterator;

            for (iterator it = boost::begin(turns) ; it != boost::end(turns) ; ++it)
            {
                per_turn<0>(*it);
                per_turn<1>(*it);
            }

            return m_result.interrupt;
        }

    private:
        template <std::size_t OpId, typename Turn>
        inline void per_turn(Turn const& turn)
        {
            static const std::size_t other_op_id = (OpId + 1) % 2;
            static const bool transpose_result = OpId != 0;

            overlay::operation_type const op = turn.operations[OpId].operation;

            if ( op == overlay::operation_union )
            {
                // ignore u/u
                /*if ( turn.operations[other_op_id].operation != overlay::operation_union )
                {
                    update<interior, exterior, '2', transpose_result>(m_result);
                    update<boundary, exterior, '1', transpose_result>(m_result);
                }*/

                update<boundary, boundary, '0', transpose_result>(m_result);
            }
            else if ( op == overlay::operation_intersection )
            {
                // ignore i/i
                if ( turn.operations[other_op_id].operation != overlay::operation_intersection )
                {
                    update<interior, interior, '2', transpose_result>(m_result);
                    //update<boundary, interior, '1', transpose_result>(m_result);
                }

                update<boundary, boundary, '0', transpose_result>(m_result);
            }
            else if ( op == overlay::operation_continue )
            {
                update<boundary, boundary, '1', transpose_result>(m_result);
                update<interior, interior, '2', transpose_result>(m_result);
            }
            else if ( op == overlay::operation_blocked )
            {
                update<boundary, boundary, '1', transpose_result>(m_result);
                update<interior, exterior, '2', transpose_result>(m_result);
            }
        }

        Result & m_result;
        Geometry1 const& m_geometry1;
        Geometry2 const& m_geometry2;
    };

    // This analyser should be used like Input or SinglePass Iterator
    // IMPORTANT! It should be called also for the end iterator - last
    template <typename TurnInfo, std::size_t OpId>
    class turns_analyser
    {
        typedef typename TurnInfo::point_type turn_point_type;

        static const std::size_t op_id = OpId;
        static const std::size_t other_op_id = (OpId + 1) % 2;
        static const bool transpose_result = OpId != 0;

    public:
        turns_analyser()
            : m_previous_turn_ptr(0)
            , m_previous_operation(overlay::operation_none)
            , m_enter_detected(false)
            , m_exit_detected(false)
        {}

        template <typename Result,
                  typename TurnIt>
        void apply(Result & result, TurnIt it)
        {
            //BOOST_ASSERT( it != last );

            overlay::operation_type const op = it->operations[op_id].operation;

            if ( op != overlay::operation_union
              && op != overlay::operation_intersection
              && op != overlay::operation_blocked
              && op != overlay::operation_continue )
            {
                return;
            }

            segment_identifier const& seg_id = it->operations[op_id].seg_id;
            //segment_identifier const& other_id = it->operations[other_op_id].seg_id;

            const bool first_in_range = m_seg_watcher.update(seg_id);

            if ( m_previous_turn_ptr )
            {
                if ( m_exit_detected /*m_previous_operation == overlay::operation_union*/ )
                {
                    // real exit point - may be multiple
                    if ( first_in_range
                      || ! turn_on_the_same_ip<op_id>(*m_previous_turn_ptr, *it) )
                    {
                        update_exit(result);
                        m_exit_detected = false;
                    }
                    // fake exit point, reset state
                    else if ( op != overlay::operation_union )
                    {
                        m_exit_detected = false;
                    }
                }
                /*else*/
                if ( m_enter_detected /*m_previous_operation == overlay::operation_intersection*/ )
                {
                    // real entry point
                    if ( first_in_range
                      || ! turn_on_the_same_ip<op_id>(*m_previous_turn_ptr, *it) )
                    {
                        update_enter(result);
                        m_enter_detected = false;
                    }
                    // fake entry point, reset state
                    else if ( op != overlay::operation_intersection )
                    {
                        m_enter_detected = false;
                    }
                }
            }

            if ( op == overlay::operation_union )
            {
                // already set in interrupt policy
                //update<boundary, boundary, '0', transpose_result>(m_result);

                // ignore u/u
                //if ( it->operations[other_op_id].operation != overlay::operation_union )
                {
                    m_exit_detected = true;
                }
            }
            else if ( op == overlay::operation_intersection )
            {
                // ignore i/i
                if ( it->operations[other_op_id].operation != overlay::operation_intersection )
                {
                    // already set in interrupt policy
                    //update<interior, interior, '2', transpose_result>(result);
                    //update<boundary, boundary, '0', transpose_result>(result);
                    m_enter_detected = true;
                }
            }
            else if ( op == overlay::operation_blocked )
            {
                // already set in interrupt policy
            }
            else // if ( op == overlay::operation_continue )
            {
                // already set in interrupt policy
            }

            // store ref to previously analysed (valid) turn
            m_previous_turn_ptr = boost::addressof(*it);
            // and previously analysed (valid) operation
            m_previous_operation = op;
        }

        // it == last
        template <typename Result>
        void apply(Result & result)
        {
            //BOOST_ASSERT( first != last );

            if ( m_exit_detected /*m_previous_operation == overlay::operation_union*/ )
            {
                update_exit(result);
                m_exit_detected = false;
            }

            if ( m_enter_detected /*m_previous_operation == overlay::operation_intersection*/ )
            {
                update_enter(result);
                m_enter_detected = false;
            }
        }

        template <typename Result>
        static inline void update_exit(Result & result)
        {
            update<interior, exterior, '2', transpose_result>(result);
            update<boundary, exterior, '1', transpose_result>(result);
        }

        template <typename Result>
        static inline void update_enter(Result & result)
        {
            update<boundary, interior, '1', transpose_result>(result);
            update<exterior, interior, '2', transpose_result>(result);
        }

    private:
        segment_watcher<same_ring> m_seg_watcher;
        TurnInfo * m_previous_turn_ptr;
        overlay::operation_type m_previous_operation;
        bool m_enter_detected;
        bool m_exit_detected;
    };

    // call analyser.apply() for each turn in range
    // IMPORTANT! The analyser is also called for the end iterator - last
    template <typename Result,
              typename Analyser,
              typename TurnIt>
    static inline void analyse_each_turn(Result & res,
                                         Analyser & analyser,
                                         TurnIt first, TurnIt last)
    {
        if ( first == last )
            return;

        for ( TurnIt it = first ; it != last ; ++it )
        {
            analyser.apply(res, it);

            if ( BOOST_GEOMETRY_CONDITION(res.interrupt) )
                return;
        }

        analyser.apply(res);
    }

    template <std::size_t OpId, typename Result, typename Geometry, typename OtherGeometry>
    class uncertain_rings_analyser
    {
        static const bool transpose_result = OpId != 0;
        static const int other_id = (OpId + 1) % 2;

    public:
        inline uncertain_rings_analyser(Result & result,
                                        Geometry const& geom,
                                        OtherGeometry const& other_geom)
            : geometry(geom), other_geometry(other_geom)
            , interrupt(result.interrupt) // just in case, could be false as well
            , m_result(result)
            , m_flags(0)
        {
            // check which relations must be analysed

            if ( ! may_update<interior, interior, '2', transpose_result>(m_result) )
            {
                m_flags |= 1;
            }

            if ( ! may_update<interior, exterior, '2', transpose_result>(m_result)
              && ! may_update<boundary, exterior, '1', transpose_result>(m_result) )
            {
                m_flags |= 2;
            }

            if ( ! may_update<boundary, interior, '1', transpose_result>(m_result)
              && ! may_update<exterior, interior, '2', transpose_result>(m_result) )
            {
                m_flags |= 4;
            }
        }

        inline void no_turns(segment_identifier const& seg_id)
        {
            // if those flags are set nothing will change
            if ( m_flags == 7 )
            {
                return;
            }

            typename detail::sub_range_return_type<Geometry const>::type
                range_ref = detail::sub_range(geometry, seg_id);

            if ( boost::empty(range_ref) )
            {
                // TODO: throw an exception?
                return; // ignore
            }

            // TODO: possible optimization
            // if the range is an interior ring we may use other IPs generated for this single geometry
            // to know which other single geometries should be checked

            // TODO: optimize! e.g. use spatial index
            // O(N) - running it in a loop gives O(NM)
            int const pig = detail::within::point_in_geometry(range::front(range_ref), other_geometry);

            //BOOST_ASSERT(pig != 0);
            if ( pig > 0 )
            {
                update<interior, interior, '2', transpose_result>(m_result);
                m_flags |= 1;

                update<boundary, interior, '1', transpose_result>(m_result);
                update<exterior, interior, '2', transpose_result>(m_result);
                m_flags |= 4;
            }
            else
            {
                update<boundary, exterior, '1', transpose_result>(m_result);
                update<interior, exterior, '2', transpose_result>(m_result);
                m_flags |= 2;
            }

// TODO: break if all things are set
// also some of them could be checked outside, before the analysis
// In this case we shouldn't relay just on dummy flags
// Flags should be initialized with proper values
// or the result should be checked directly
// THIS IS ALSO TRUE FOR OTHER ANALYSERS! in L/L and L/A

            interrupt = m_flags == 7 || m_result.interrupt;
        }

        template <typename TurnIt>
        inline void turns(TurnIt first, TurnIt last)
        {
            // if those flags are set nothing will change
            if ( (m_flags & 6) == 6 )
            {
                return;
            }

            bool found_ii = false;
            bool found_uu = false;

            for ( TurnIt it = first ; it != last ; ++it )
            {
                if ( it->operations[0].operation == overlay::operation_intersection
                  && it->operations[1].operation == overlay::operation_intersection )
                {
                    // ignore exterior ring
                    if ( it->operations[OpId].seg_id.ring_index >= 0 )
                    {
                        found_ii = true;
                    }
                }
                else if ( it->operations[0].operation == overlay::operation_union
                       && it->operations[1].operation == overlay::operation_union )
                {
                    // ignore if u/u is for holes
                    //if ( it->operations[OpId].seg_id.ring_index >= 0
                    //  && it->operations[other_id].seg_id.ring_index >= 0 )
                    {
                        found_uu = true;
                    }
                }
                else // ignore
                {
                    return; // don't interrupt
                }
            }

            // only i/i was generated for this ring
            if ( found_ii )
            {
                //update<interior, interior, '0', transpose_result>(m_result);
                //update<boundary, boundary, '0', transpose_result>(m_result);
                update<boundary, interior, '1', transpose_result>(m_result);
                update<exterior, interior, '2', transpose_result>(m_result);
                m_flags |= 4;
            }

            // only u/u was generated for this ring
            if ( found_uu )
            {
                update<boundary, exterior, '1', transpose_result>(m_result);
                update<interior, exterior, '2', transpose_result>(m_result);
                m_flags |= 2;
            }

            interrupt = m_flags == 7 || m_result.interrupt; // interrupt if the result won't be changed in the future
        }

        Geometry const& geometry;
        OtherGeometry const& other_geometry;
        bool interrupt;

    private:
        Result & m_result;
        int m_flags;
    };

    template <std::size_t OpId>
    class analyse_uncertain_rings
    {
    public:
        template <typename Analyser, typename TurnIt>
        static inline void apply(Analyser & analyser, TurnIt first, TurnIt last)
        {
            if ( first == last )
                return;

            for_preceding_rings(analyser, *first);
            //analyser.per_turn(*first);

            TurnIt prev = first;
            for ( ++first ; first != last ; ++first, ++prev )
            {
                // same multi
                if ( prev->operations[OpId].seg_id.multi_index
                  == first->operations[OpId].seg_id.multi_index )
                {
                    // same ring
                    if ( prev->operations[OpId].seg_id.ring_index
                      == first->operations[OpId].seg_id.ring_index )
                    {
                        //analyser.per_turn(*first);
                    }
                    // same multi, next ring
                    else
                    {
                        //analyser.end_ring(*prev);
                        analyser.turns(prev, first);

                        //if ( prev->operations[OpId].seg_id.ring_index + 1
                        //   < first->operations[OpId].seg_id.ring_index)
                        {
                            for_no_turns_rings(analyser,
                                               *first,
                                               prev->operations[OpId].seg_id.ring_index + 1,
                                               first->operations[OpId].seg_id.ring_index);
                        }

                        //analyser.per_turn(*first);
                    }
                }
                // next multi
                else
                {
                    //analyser.end_ring(*prev);
                    analyser.turns(prev, first);
                    for_following_rings(analyser, *prev);
                    for_preceding_rings(analyser, *first);
                    //analyser.per_turn(*first);
                }

                if ( analyser.interrupt )
                {
                    return;
                }
            }

            //analyser.end_ring(*prev);
            analyser.turns(prev, first); // first == last
            for_following_rings(analyser, *prev);
        }

    private:
        template <typename Analyser, typename Turn>
        static inline void for_preceding_rings(Analyser & analyser, Turn const& turn)
        {
            segment_identifier const& seg_id = turn.operations[OpId].seg_id;

            for_no_turns_rings(analyser, turn, -1, seg_id.ring_index);
        }

        template <typename Analyser, typename Turn>
        static inline void for_following_rings(Analyser & analyser, Turn const& turn)
        {
            segment_identifier const& seg_id = turn.operations[OpId].seg_id;

            signed_index_type
                count = boost::numeric_cast<signed_index_type>(
                            geometry::num_interior_rings(
                                detail::single_geometry(analyser.geometry, seg_id)));

            for_no_turns_rings(analyser, turn, seg_id.ring_index + 1, count);
        }

        template <typename Analyser, typename Turn>
        static inline void for_no_turns_rings(Analyser & analyser,
                                              Turn const& turn,
                                              signed_index_type first,
                                              signed_index_type last)
        {
            segment_identifier seg_id = turn.operations[OpId].seg_id;

            for ( seg_id.ring_index = first ; seg_id.ring_index < last ; ++seg_id.ring_index )
            {
                analyser.no_turns(seg_id);
            }
        }
    };
};

}} // namespace detail::relate
#endif // DOXYGEN_NO_DETAIL

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_RELATE_AREAL_AREAL_HPP