#pragma once

#include <unordered_map>
#include <vector>

#include "LatticeSite.hpp"
#include "OrbitList.hpp"
#include "Structure.hpp"
#include "VectorOperations.hpp"

/**
@brief
    A LocalOrbitListGenerator is used to create orbit lists for supercells.

@details
    The constructor takes a primitive orbit list, which is an orbit list
    generated for the primitive cell, and a supercell. Objects of this class
    can then generate orbit lists for that supercell. There are two types of
    orbit lists for a supercell: local orbit lists and full orbit lists.
    
    A local orbit list is an orbit list with the same number of clusters as
    the primitive orbit list, but where each cluster refers to a specific
    "offset", i.e., a part of the supercell referred to in multiples of
    the primitive lattice vectors. Local orbit lists can be used to
    calculate cluster vector differences when the occupation of one
    site has changed. This, however, requires that all clusters including
    that specific site are included in the orbit list, which is achieved
    by setting `selfContained = true` when generating the local orbit list.
    
    A full orbit list contains all the clusters of the supercell and can
    thus be used for, e.g., calculation of cluster vectors. They are
    generated by adding together the local orbit lists for each unique
    offset of the supercell. These local orbit lists should not be
    self-contained, otherwise clusters would be double-counted. 
*/

class LocalOrbitListGenerator
{
public:
    /// Constructor.
    LocalOrbitListGenerator(const OrbitList &, std::shared_ptr<Structure>, const double);

    /// Generates and returns the local orbit list with the input index.
    OrbitList getLocalOrbitList(const Vector3i &, bool);

    /// Generates the full orbit list from this structure.
    OrbitList getFullOrbitList();

    /// Returns the number of unique offsets.
    size_t getNumberOfUniqueOffsets() const { return _uniquePrimitiveCellOffsets.size(); }

    /// Returns the unique primitive cells
    std::vector<Vector3i> getUniquePrimitiveCellOffsets() const { return _uniquePrimitiveCellOffsets; }

private:
    /// Primitive orbit list.
    OrbitList _primitiveOrbitList;

    /// Primitive structure on which the supercell is based.
    std::shared_ptr<Structure> _primitiveStructure;

    /// Supercell structure on which the local orbit list will be based.
    std::shared_ptr<Structure> _supercell;

    /// Maps a lattice site from the primitive cell to an equivalent lattice site in the supercell.
    std::unordered_map<LatticeSite, LatticeSite> _primitiveToSupercellMap;

    /// The unique offsets of the primitive cell required to "cover" the supercell.
    std::vector<Vector3i> _uniquePrimitiveCellOffsets;

    /// Tolerance applied when comparing positions in Cartesian coordinates.
    double _fractionalPositionTolerance;
};
