////////////////////////////////////////////////////////////////////////////////////////
//
//  Copyright 2026 OVITO GmbH, Germany
//
//  This file is part of OVITO (Open Visualization Tool).
//
//  OVITO is free software; you can redistribute it and/or modify it either under the
//  terms of the GNU General Public License version 3 as published by the Free Software
//  Foundation (the "GPL") or, at your option, under the terms of the MIT License.
//  If you do not alter this notice, a recipient may use your version of this
//  file under either the GPL or the MIT License.
//
//  You should have received a copy of the GPL along with this program in a
//  file LICENSE.GPL.txt.  You should have received a copy of the MIT License along
//  with this program in a file LICENSE.MIT.txt
//
//  This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
//  either express or implied. See the GPL or the MIT License for the specific language
//  governing rights and limitations.
//
////////////////////////////////////////////////////////////////////////////////////////

#include <ovito/particles/Particles.h>
#include <ovito/particles/util/NearestNeighborFinder.h>
#include <ovito/stdobj/simcell/SimulationCell.h>
#include <ovito/core/dataset/animation/AnimationSettings.h>
#include <ovito/core/dataset/pipeline/ModificationNode.h>
#include <ovito/core/utilities/concurrent/ParallelFor.h>
#include "AcklandJonesModifier.h"

namespace Ovito {

IMPLEMENT_CREATABLE_OVITO_CLASS(AcklandJonesModifier);
OVITO_CLASSINFO(AcklandJonesModifier, "DisplayName", "Ackland-Jones analysis");
OVITO_CLASSINFO(AcklandJonesModifier, "Description", "Identify common crystalline structures based on local bond angles.");
OVITO_CLASSINFO(AcklandJonesModifier, "ModifierCategory", "Structure identification");

/******************************************************************************
* Constructor.
******************************************************************************/
void AcklandJonesModifier::initializeObject(ObjectInitializationFlags flags)
{
    StructureIdentificationModifier::initializeObject(flags);

    if(!flags.testFlag(ObjectInitializationFlag::DontInitializeObject)) {
        // Create the structure types.
        createStructureType(OTHER, ParticleType::PredefinedStructureType::OTHER);
        createStructureType(FCC, ParticleType::PredefinedStructureType::FCC);
        createStructureType(HCP, ParticleType::PredefinedStructureType::HCP);
        createStructureType(BCC, ParticleType::PredefinedStructureType::BCC);
        createStructureType(ICO, ParticleType::PredefinedStructureType::ICO);
    }
}

/******************************************************************************
* Performs the actual analysis.
******************************************************************************/
void AcklandJonesModifier::AcklandJonesAnalysisAlgorithm::identifyStructures()
{
    if(simulationCell().is2D())
        throw Exception(tr("The Ackland-Jones analysis algorithm does not support 2d simulation cells."));

    TaskProgress progress(this_task::ui());
    progress.setText(tr("Performing Ackland-Jones analysis"));

    // Prepare the neighbor finder.
    NearestNeighborFinder neighborFinder(14, particles()->expectProperty(Particles::PositionProperty), simulationCell(), particleSelection());

    // Perform analysis on each particle.
    BufferReadAccess<SelectionIntType> selectionAcc(particleSelection());
    BufferWriteAccess<int32_t, access_mode::discard_write> structureAcc(structures());
    parallelFor(particles()->elementCount(), 1024, progress, [&](size_t index) {
        structureAcc[index] =
            (!selectionAcc || selectionAcc[index]) // Skip particles that are not included in the analysis.
                ? determineStructure(neighborFinder, index)
                : OTHER;
    });
}

/******************************************************************************
* Determines the coordination structure of a single particle using the
* bond-angle analysis method.
******************************************************************************/
AcklandJonesModifier::StructureType AcklandJonesModifier::AcklandJonesAnalysisAlgorithm::determineStructure(NearestNeighborFinder& neighFinder, size_t particleIndex) const
{
    // Find 14 nearest neighbors of current particle.
    NearestNeighborFinder::Query<14> neighborQuery(neighFinder);
    neighborQuery.findNeighbors(particleIndex);

    // Reject under-coordinated particles.
    if(neighborQuery.results().size() < 6)
        return OTHER;

    // Mean squared distance of 6 nearest neighbors.
    FloatType r0_sq = 0;
    for(int j = 0; j < 6; j++)
        r0_sq += neighborQuery.results()[j].distanceSq;
    r0_sq /= 6;

    // n0 near neighbors with: distsq<1.45*r0_sq
    // n1 near neighbors with: distsq<1.55*r0_sq
    FloatType n0_dist_sq = FloatType(1.45) * r0_sq;
    FloatType n1_dist_sq = FloatType(1.55) * r0_sq;
    int n0 = 0;
    for(auto n = neighborQuery.results().begin(); n != neighborQuery.results().end(); ++n, ++n0) {
        if(n->distanceSq > n0_dist_sq) break;
    }
    auto n0end = neighborQuery.results().begin() + n0;
    int n1 = n0;
    for(auto n = n0end; n != neighborQuery.results().end(); ++n, ++n1) {
        if(n->distanceSq >= n1_dist_sq) break;
    }

    // Evaluate all angles <(r_ij,rik) for all n0 particles with: distsq<1.45*r0_sq
    int chi[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    for(auto j = neighborQuery.results().begin(); j != n0end; ++j) {
        FloatType norm_j = sqrt(j->distanceSq);
        for(auto k = j + 1; k != n0end; ++k) {
            FloatType norm_k = sqrt(k->distanceSq);
            FloatType bond_angle = j->delta.dot(k->delta) / (norm_j*norm_k);

            // Build histogram for identifying the relevant peaks.
            if(bond_angle < FloatType(-0.945)) { chi[0]++; }
            else if(FloatType(-0.945) <= bond_angle && bond_angle < FloatType(-0.915)) { chi[1]++; }
            else if(FloatType(-0.915) <= bond_angle && bond_angle < FloatType(-0.755)) { chi[2]++; }
            else if(FloatType(-0.755) <= bond_angle && bond_angle < FloatType(-0.195)) { chi[3]++; }
            else if(FloatType(-0.195) <= bond_angle && bond_angle < FloatType(0.195)) { chi[4]++; }
            else if(FloatType(0.195) <= bond_angle && bond_angle < FloatType(0.245)) { chi[5]++; }
            else if(FloatType(0.245) <= bond_angle && bond_angle < FloatType(0.795)) { chi[6]++; }
            else if(FloatType(0.795) <= bond_angle) { chi[7]++; }
        }
    }

    // Calculate deviations from the different lattice structures.
    FloatType delta_bcc = FloatType(0.35) * chi[4] / (FloatType)(chi[5] + chi[6] - chi[4]);
    FloatType delta_cp = std::abs(FloatType(1) - (FloatType)chi[6] / 24);
    FloatType delta_fcc = FloatType(0.61) * (FloatType)(std::abs(chi[0] + chi[1] - 6) + chi[2]) / 6;
    FloatType delta_hcp = (FloatType)(std::abs(chi[0] - 3) + std::abs(chi[0] + chi[1] + chi[2] + chi[3] - 9)) / 12;

    // Identification of the local structure according to the reference.
    if(chi[0] == 7)       { delta_bcc = 0; }
    else if(chi[0] == 6)  { delta_fcc = 0; }
    else if(chi[0] <= 3)  { delta_hcp = 0; }

    if(chi[7] > 0) return OTHER;
    else if(chi[4] < 3) {
        if(!typeIdentificationEnabled(ICO) || n1 > 13 || n1 < 11) return OTHER;
        else return ICO;
    }
    else if(delta_bcc <= delta_cp) {
        if(!typeIdentificationEnabled(BCC) || n1 < 11) return OTHER;
        else return BCC;
    }
    else if(n1 > 12 || n1 < 11) return OTHER;
    else if(delta_fcc < delta_hcp) {
        if(typeIdentificationEnabled(FCC)) return FCC;
        else return OTHER;
    }
    else if(typeIdentificationEnabled(HCP)) return HCP;
    else return OTHER;
}

/******************************************************************************
* Computes the structure identification statistics.
******************************************************************************/
std::vector<int64_t> AcklandJonesModifier::AcklandJonesAnalysisAlgorithm::computeStructureStatistics(const Property* structures, PipelineFlowState& state, const OOWeakRef<const PipelineNode>& createdByNode, const std::any& modifierParameters) const
{
    std::vector<int64_t> typeCounts = StructureIdentificationModifier::Algorithm::computeStructureStatistics(structures, state, createdByNode, modifierParameters);

    // Also output structure type counts, which have been computed by the base class.
    state.addAttribute(QStringLiteral("AcklandJones.counts.OTHER"), QVariant::fromValue(typeCounts.at(OTHER)), createdByNode);
    state.addAttribute(QStringLiteral("AcklandJones.counts.FCC"), QVariant::fromValue(typeCounts.at(FCC)), createdByNode);
    state.addAttribute(QStringLiteral("AcklandJones.counts.HCP"), QVariant::fromValue(typeCounts.at(HCP)), createdByNode);
    state.addAttribute(QStringLiteral("AcklandJones.counts.BCC"), QVariant::fromValue(typeCounts.at(BCC)), createdByNode);
    state.addAttribute(QStringLiteral("AcklandJones.counts.ICO"), QVariant::fromValue(typeCounts.at(ICO)), createdByNode);

    return typeCounts;
}

}   // End of namespace
