////////////////////////////////////////////////////////////////////////////////////////
//
//  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.
//
////////////////////////////////////////////////////////////////////////////////////////

#pragma once


#include <ovito/crystalanalysis/CrystalAnalysis.h>

namespace Ovito {

struct Cluster;
struct ClusterTransition;

/// Two transitions matrices are considered equal if their elements don't differ by more than this value.
#define CA_TRANSITION_MATRIX_EPSILON                1e-4f

/**
 * A cluster transition T_12 is a transformation matrix that connects the
 * reference frames of two clusters 1 and 2.
 * A cluster transition also corresponds to a directed edge in the cluster graph.
 *
 * For every cluster transition T_12 there exists a reverse transition
 * T_21 = (T_12)^-1.
 *
 * If clusters 1 and 2 are adjacent in the input structure, then we can determine
 * the transition matrix T_12 from the neighboring atoms at the common border of the two
 * clusters.
 *
 * Given two cluster transitions T_12 and T_23, we can construct a third
 * cluster transition T_13 = T_23 * T_12, which connects clusters 1 and 3.
 *
 * Every cluster has a so-called self-transition (or identity transition),
 * which is the reverse of itself.
 */
struct OVITO_CRYSTALANALYSIS_EXPORT ClusterTransition
{
    using MatType = Ovito::Matrix3F; ///< Type alias for a 3x3 matrix.
    using VecType = Ovito::Vector3F;  ///< Type alias for a 3D vector.

    /// The first cluster.
    /// The transition matrix transforms vectors from this cluster to the coordinate system of cluster 2.
    Cluster* cluster1;

    /// The second cluster.
    /// The transition matrix transforms vectors from cluster 1 to the coordinate system of this cluster.
    Cluster* cluster2;

    /// The transformation matrix that transforms vectors from the reference frame of cluster 1 to the frame
    /// of cluster 2.
    MatType tm;

    /// Pointer to the reverse transition from cluster 2 to cluster 1.
    /// The transformation matrix of the reverse transition is the inverse of this transition's matrix.
    ClusterTransition* reverse;

    /// The cluster transitions form the directed edges of the cluster cluster graph (with the clusters being the nodes).
    /// Each node's outgoing edges are stored in a linked list. This field points
    /// to the next element in the linked list of cluster 1.
    ClusterTransition* next;

    /// The distance of clusters 1 and 2 in the cluster graph.
    /// The cluster transition is of distance 1 if the two cluster are immediate
    /// neighbors (i.e. they have a common border).
    /// From two transitions A->B and B->C we can derive a new transition A->C, which
    /// is the concatenation of the first two. The distance associated with the transition
    /// A->C is the sum of distances of A->B and B->C.
    /// The distance of a self-transition A->A is defined to be zero.
    int distance;

    /// The number of bonds that are part of this cluster transition.
    int area;

    /// Returns true if this is the self-transition that connects a cluster with itself.
    /// The transformation matrix of an identity transition is always the identity matrix.
    bool isSelfTransition() const {
        OVITO_ASSERT((reverse != this) || (cluster1 == cluster2));
        OVITO_ASSERT((reverse != this) || tm.equals(MatType::Identity(), CA_TRANSITION_MATRIX_EPSILON));
        OVITO_ASSERT((reverse != this) || (distance == 0));
        return reverse == this;
    }

    /// Transforms a vector from the coordinate space of cluster 1 to the coordinate space of cluster 2.
    VecType transform(const VecType& v) const {
        if(isSelfTransition())
            return v;
        else
            return tm * v;
    }

    /// Back-transforms a vector from the coordinate space of cluster 2 to the coordinate space of cluster 1.
    VecType reverseTransform(const VecType& v) const {
        if(isSelfTransition())
            return v;
        else
            return reverse->tm * v;
    }
};

/**
 * A cluster is a connected group of atoms in the input structure that all match
 * one pattern, i.e. they form a contiguous arrangement with long-range order.
 *
 * A cluster constitutes a node in the so-called cluster graph, which is generated
 * during the pattern matching procedure.
 *
 * Every cluster is associated with an internal frame of reference (which is implicitly defined by the
 * template structure used to create the atomic pattern). When a cluster is created for a group
 * of atoms, an average orientation matrix is calculated that transforms vectors from the cluster's reference frame to the global
 * simulation frame (in a least-square sense).
 *
 * Two clusters that are adjacent in the input structure can have a specific
 * crystallographic orientation relationship, which can be determined from the atoms
 * at their common border. Vectors given in the local coordinate frame of one of the clusters
 * can be transformed to the other cluster's coordinate space. The corresponding transformation
 * matrix is referred to as a 'cluster transition', which constitutes a directed edge in the so-called cluster graph.
 */
struct OVITO_CRYSTALANALYSIS_EXPORT Cluster
{
    using MatType = ClusterTransition::MatType;  ///< Type alias for a 3x3 matrix.
    using VecType = ClusterTransition::VecType;  ///< Type alias for a 3D vector.

    /// The identifier of the cluster.
    int id;

    /// The structural pattern formed by atoms of the cluster.
    int structure;

    /// The number of atoms that belong to the cluster.
    int atomCount = 0;

    /// Linked list of transitions from this cluster to other clusters. They form the directed edges
    /// of the cluster graph.
    ///
    /// The elements in the linked list are always ordered in ascending distance order.
    /// Thus, the self-transition (having distance 0) will always be a the head of the linked list (if it has already been created).
    ClusterTransition* transitions = nullptr;

    /// This is a work variable used only during a recursive path search in the
    /// cluster graph. It points to the preceding node in the path.
    ClusterTransition* predecessor;

    union {
        /// This is a work variable used only during a recursive shortest path search in the
        /// cluster graph. It keeps track of the distance of this cluster from the
        /// start node of the path search.
        int distanceFromStart;

        /// Used by the disjoint-set forest algorithm using union-by-rank and path compression.
        int rank;
    };

    /// Transformation matrix that transforms vectors from the cluster's internal coordinate space
    /// to the global simulation frame. Note that this describes the (average) orientation of the
    /// atom group in the simulation coordinate system.
    MatType orientation = MatType::Identity();

    /// An additional symmetry transformation applied to the orientation of this cluster to align it
    /// with one of the preferred crystal orientation as much as possible.
    int symmetryTransformation = 0;

    /// The center of mass of the cluster.
    /// Note: This is currently not computed by OVITO, but may be loaded from a CA file produced by an external code (e.g. CAT).
    Point3 centerOfMass = Point3::Origin();

    /// The visualization color of the atom cluster.
    Color color;

    /// The transition from this cluster to its parent if the cluster
    /// is a child cluster.
    ClusterTransition* parentTransition = nullptr;

    /// Constructor.
    Cluster(int _id, int _structure) : id(_id), structure(_structure), color(1,1,1) {}

    /// Inserts a transition into this cluster's list of transitions.
    void insertTransition(ClusterTransition* newTransition) {
        OVITO_ASSERT(newTransition->cluster1 == this);
        // Determine the point of insertion to keep the linked list of transitions sorted.
        ClusterTransition* appendAfter = nullptr;
        for(ClusterTransition* t = this->transitions; t != nullptr && t->distance < newTransition->distance; t = t->next)
            appendAfter = t;
        if(appendAfter) {
            newTransition->next = appendAfter->next;
            appendAfter->next = newTransition;
            OVITO_ASSERT(appendAfter->distance < newTransition->distance);
        }
        else {
            newTransition->next = this->transitions;
            this->transitions = newTransition;
        }
    }

    /// Removes a transition from the cluster's list of transitions.
    void removeTransition(ClusterTransition* t) {
        if(this->transitions == t) {
            this->transitions = t->next;
            t->next = nullptr;
            return;
        }
        for(ClusterTransition* iter = this->transitions; iter != nullptr; iter = iter->next) {
            if(iter->next == t) {
                iter->next = t->next;
                t->next = nullptr;
                return;
            }
        }
        OVITO_ASSERT(false); // Transition was not in the list. This should not happen.
    }

    /// Returns the transition to the given cluster or NULL if there is no direct transition.
    ClusterTransition* findTransition(Cluster* clusterB) const {
        for(ClusterTransition* t = transitions; t != nullptr; t = t->next) {
            if(t->cluster2 == clusterB)
                return t;
        }
        return nullptr;
    }

    /// Returns true if the given transition is in this cluster's list of transitions.
    bool hasTransition(ClusterTransition* transition) const {
        for(ClusterTransition* t = transitions; t != nullptr; t = t->next)
            if(t == transition) return true;
        return false;
    }
};

}   // End of namespace
