// This file is part of the ACTS project.
//
// Copyright (C) 2016 CERN for the benefit of the ACTS project
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

#pragma once

#include "Acts/Utilities/MultiIndex.hpp"

#include <cstdint>

namespace ActsFatras {

/// Particle identifier that encodes additional event information.
///
/// The barcode has to fulfill two separate requirements: be able to act as
/// unique identifier for particles within an event and to encode details
/// on the event structure for fast lookup. Since we only care about tracking
/// here, we need to support two scenarios:
///
/// *   Identify which primary/secondary vertex particles belong to. No
///     information on intermediate/unstable/invisible particles needs to be
///     retained. This information is already available in the underlying
///     generator event and should not be duplicated.
/// *   If (visible) particles convert, decay, or interact with the detector,
///     we need to be able to identify the initial (primary) particle. Typical
///     examples are pion nuclear interactions or electron/gamma conversions.
///
/// The vertex information is encoded as two numbers that define the
/// primary and secondary vertex. The primary vertex must be non-zero.
/// Particles with a zero secondary vertex originate directly from the primary
/// vertex.
///
/// Within one vertex (primary+secondary) each particle is identified by
/// a particle, generation, and sub-particle number. Particles originating
/// from the vertex must have zero generation and zero sub-particle number;
/// a consequence is that only non-zero generation can have non-zero
/// sub-particle numbers. A non-zero generation indicates that the particle
/// is a descendant of the original particle, e.g. from interactions or decay,
/// while the sub-particle number identifies the descendant particle.
///
/// With this encoding, non-primary particles and their primary parent can
/// be easily identified at the expense of not storing the exact decay history.
///
/// A barcode with all elements set to zero (the default value) is an invalid
/// value that can be used e.g. to mark missing or unknown particles.
///
/// ## Example
///
/// A particle generated in a primary interaction might have the barcode
///
///     2|0|14|0|0 -> vertex=2 (primary), particle=14, generation=0, sub=0
///
/// A simulation module might generate an interaction and create two new
/// particles. These are descendants of the initial particle and the simulation
/// module can generate the new barcodes directly by increasing the
/// generation number and choosing sub-particle identifiers:
///
///     2|0|14|1|0 -> vertex=2 (primary), particle=14, generation=1, sub=0
///     2|0|14|1|1 -> vertex=2 (primary), particle=14, generation=1, sub=1
///
/// If these secondary particles generate further tertiary particles
/// the barcode would be e.g.
///
///     2|0|14|2|0 -> vertex=2 (primary), particle=14, generation=2, sub=0
///
/// ## Possible issues
///
/// The hierarchical nature of the barcode allows barcode creation without
/// a central service. Since the full history is not stored, generated barcodes
/// for higher-generation particles can overlap when generated by independent
/// interactions. Assuming an initial primary particle with barcode
///
///     3|4|5|0|0 -> particle=5
///
/// a first interaction might create a secondary particle by increasing the
/// generation number (without destroying the initial particle)
///
///     3|4|5|1|0 -> particle=5, generation+=1, first sub-particle
///
/// The initial particle gets simulated further and at another step a second
/// interaction also creates a new particle. Since it knows nothing about
/// the previously created particle (no central service), it will generate
///
///     3|4|5|1|0 -> particle=5, generation+=1, first sub-particle
///
/// which is identical to the previously create barcode. These cases can be
/// easily solved by renumbering the sub-particle identifier within each
/// generation to contain unique values. However, this can only be done when all
/// particles are known.
class Barcode : public Acts::MultiIndex<std::uint64_t, 12, 12, 16, 8, 16> {
  using Base = Acts::MultiIndex<std::uint64_t, 12, 12, 16, 8, 16>;

 public:
  using Base::Base;
  using Base::Value;

  // Construct an invalid barcode with all levels set to zero.
  constexpr Barcode() : Base(Base::Zeros()) {}
  Barcode(const Barcode&) = default;
  Barcode(Barcode&&) = default;
  Barcode& operator=(const Barcode&) = default;
  Barcode& operator=(Barcode&&) = default;

  /// Return the primary vertex identifier.
  constexpr Value vertexPrimary() const { return level(0); }
  /// Return the secondary vertex identifier.
  constexpr Value vertexSecondary() const { return level(1); }
  /// Return the particle identifier.
  constexpr Value particle() const { return level(2); }
  /// Return the generation identifier.
  constexpr Value generation() const { return level(3); }
  /// Return the sub-particle identifier.
  constexpr Value subParticle() const { return level(4); }

  /// Set the primary vertex identifier.
  constexpr Barcode& setVertexPrimary(Value id) {
    set(0, id);
    return *this;
  }
  /// Set the secondary vertex identifier.
  constexpr Barcode& setVertexSecondary(Value id) {
    set(1, id);
    return *this;
  }
  /// Set the parent particle identifier.
  constexpr Barcode& setParticle(Value id) {
    set(2, id);
    return *this;
  }
  /// Set the particle identifier.
  constexpr Barcode& setGeneration(Value id) {
    set(3, id);
    return *this;
  }
  /// Set the process identifier.
  constexpr Barcode& setSubParticle(Value id) {
    set(4, id);
    return *this;
  }

  /// Construct a new barcode representing a descendant particle.
  ///
  /// @param sub sub-particle index of the new barcode.
  Barcode makeDescendant(Value sub = 0u) const {
    return Barcode(*this).setGeneration(generation() + 1).setSubParticle(sub);
  }

  /// Reduce the barcode to the vertex identifier.
  constexpr Barcode vertexId() const {
    // The vertex is identified by primary vertex, secondary vertex, and
    // generation. The other components are set to 0 so two particle originating
    // from the same vertex will have the same vertex ID.
    return Barcode(*this).setParticle(0).setSubParticle(0);
  }
};

}  // namespace ActsFatras

// specialize std::hash so Barcode can be used e.g. in an unordered_map
namespace std {
template <>
struct hash<ActsFatras::Barcode> {
  auto operator()(ActsFatras::Barcode barcode) const noexcept {
    return std::hash<ActsFatras::Barcode::Value>()(barcode.value());
  }
};
}  // namespace std
