/*
Copyright 2007-2023. Algoryx Simulation AB.

All AGX source code, intellectual property, documentation, sample code,
tutorials, scene files and technical white papers, are copyrighted, proprietary
and confidential material of Algoryx Simulation AB. You may not download, read,
store, distribute, publish, copy or otherwise disseminate, use or expose this
material unless having a written signed agreement with Algoryx Simulation AB, or having been
advised so by Algoryx Simulation AB for a time limited evaluation, or having purchased a
valid commercial license from Algoryx Simulation AB.

Algoryx Simulation AB disclaims all responsibilities for loss or damage caused
from using this software, unless otherwise stated in written agreements with
Algoryx Simulation AB.
*/

#pragma once

#include <agxTerrain/export.h>
#include <agx/Vec3.h>

namespace agxTerrain
{
  class Terrain;

  AGX_DECLARE_POINTER_TYPES(TerrainGridControl);
  AGX_DECLARE_VECTOR_TYPES(TerrainGridControl);

  /**
  Interface class for accessing and manipulating the underlying 3D grid data in the terrain using terrain index (x,y) as well as depth index (z).
  This is the primary user-interface by which to access the underlying grid data, such as solid occupancy and compaction, in the terrain structure.

  The basic element in the terrain grid is a uniform 3D voxel with the dimension of getElementSize() given by the terrain object.
  These grid elements contains the solid occupancy in the Terrain which is bounded by the surface height field. The solid occupancy
  can be compacted by stress generated by external contacts or by applying external forces via the interface class. Compacted solid
  occupancy occupy less space in a voxel element and thus voxels with compaction can carry more solid occupancy. Soil compaction
  affects physical parameters such as angle of repose, stiffness and local soil density. The maximum compaction of an element is
  given by the maximum density in the BulkMaterial instance.

  The solid occupancy of a grid element is the unit used when interacting with the terrain interface methods is , i.e given nominal
  compaction 1.0, a solid occupancy of 1.0 is enough to occupy a whole voxel element in the Terrain. A solid occupancy of 1.0 with
  compaction 2.0 occupy half the space is a voxel. Utility methods exists to convert occupancy to mass in SI units.
  */
  class AGXTERRAIN_EXPORT TerrainGridControl : public agx::Referenced
  {
  public:
    /**
    Construct given terrain instance.
    \param terrain - terrain instance
    */
    TerrainGridControl(Terrain* terrain);

    /**
    \internal

    Default constructor used in serialization.
    */
    TerrainGridControl();

    /**
    Get solid occupancy at the specified surface index with a depth index.
    \param terrainIndex - The specified x,y terrain coordinate.
    \param z - The specified depth index.
    \return The solid occupancy at the specified 3D terrain index.
    */
    float getOccupancy(const agx::Vec2i& terrainIndex, int z) const;

    /**
    Get the solid occupancy at the specified 3D index coordinate. (x,y) are the regular terrain surface coordinates while z is the depth index.
    \param terrainIndex - The specified 3D index, containing surface coordinates and depth index.
    \return The solid occupancy at the specified 3D terrain index.
    */
    float getOccupancy(const agx::Vec3i& terrainIndex) const;

    /**
    Get compaction at the specified surface index coordinate with a depth index.
    \param terrainIndex - The specified terrain surface index.
    \param z - The specified depth index.
    \return The compaction at the specified surface index and depth index.
    */
    float getCompaction(const agx::Vec2i& terrainIndex, int z) const;

    /**
    Get compaction the specified 3D index coordinate. x,y are the regular terrain coordinate while z is the depth index.
    \param terrainIndex - The specified 3D index, containing surface coordinates and depth index.
    \return The compaction in the 3D terrain index.
    */
    float getCompaction(const agx::Vec3i& terrainIndex) const;

    /**
    Get solid occupancy at the surface grid element given a surface index.
    \param terrainIndex - The specified terrain index.
    \return The solid occupancy at the specified terrain index
    */
    float getSurfaceOccupancy(const agx::Vec2i& terrainIndex) const;

    /**
    Get compaction at the surface element given a specified surface index.
    \param terrainIndex - The specified terrain index.
    \return The compaction at the specified terrain index.
    */
    float getSurfaceCompaction(const agx::Vec2i& terrainIndex) const;

    /**
    Set the compaction at the surface element given a specified surface index.
    \note - Default behavior is that the amount of solid occupancy in the voxel
            is scaled so that the total occupancy in the element is kept
            the same, i.e the surface profile is not changed.

    \param terrainIndex - The specified surface index.
    \param compaction - The specified compaction.
    \param compressOccupancy - Set to True if the solid occupancy of the surface grid element should be compressed.
                               This will keep the amount of solid occupancy in the voxel but will change
                               the surface profile of the terrain as occupancy is lowered.
    \param compressToBottom - Set to True if the compression value should be set to each soild grid element down
                              to maximum terrain depth.
    */
    void setSurfaceCompaction(
      const agx::Vec2i& terrainIndex, float compaction, bool compressOccupancy = false, bool compressToBottom = false);

    /**
    Set the compaction at the surface element given a specified surface index.
    \note - Default behavior is that the amount of solid occupancy in the voxel
            is scaled so that the total occupancy in the element is kept
            the same, i.e the surface profile is not changed.

    \param terrainIndex - The specified surface index.
    \param compaction - The specified compaction.
    \param depth - The depth that the compression should be set under the surface
    \return The compaction at the specified surface index.
    */
    void setSurfaceCompactionToDepth(const agx::Vec2i& terrainIndex, float compaction, float depth);

    /**
    Set the compaction at an element given a specified surface index with a depth index.
    \note - Default behavior is that the amount of solid occupancy in the voxel
            is scaled so that the total occupancy in the element is kept
            the same, i.e the surface profile is not changed.

    \param terrainIndex - The specified terrain index.
    \param z - Index of the cell in the z-coord.
    \param compaction - The specified compaction.
    \param compressOccupancy - Set to True if the solid occupancy of the grid element should be compressed.
                               This will keep the amount of solid occupancy in the voxel but will change
                               the surface profile of the terrain as occupancy is lowered.
    \return The compaction at the specified terrain index.
    */
    void setCompaction(const agx::Vec2i& terrainIndex, int z, float compaction, bool compressOccupancy = false);

    /**
    Set the compaction at an element given a specified 3D index where x,y are the regular terrain coordinates while z is the depth index.
    \note - Default behavior is that the amount of solid occupancy in the voxel
            is scaled so that the total occupancy in the element is kept
            the same, i.e the surface profile is not changed.

    \param terrainIndex - The specified 3D index, containing surface coordinates and depth index.
    \param compaction - The specified compaction.
    \param compressOccupancy - Set to True if the solid occupancy of the grid element should be compressed.
    \return The compaction at the specified terrain index.
    */
    void setCompaction(const agx::Vec3i& terrainIndex, float compaction, bool compressOccupancy = false);

    /**
    Add solid occupancy in the specified terrain coordinate on the surface with given compaction.
    The solid occupancy is added on the surface of the terrain, successively filling up grid elements column wise.

    \param terrainIndex - The specified terrain index.
    \param mass - The solid mass that should be added at the surface index.
    \param compaction - The compaction of the occupancy that should be added to the column.
    \param shouldAvalanche - True if the terrainIndex should be queried for avalanching.
    \return The solid occupancy that have been added to the surface index.
    */
    float addSolidOccupancyInColumn(
      const agx::Vec2i& terrainIndex, float mass, float compaction, bool shouldAvalanche = true);

    /**
    Add solid occupancy in several terrain coordinates on the surface with given compaction.
    The solid occupancy is added on the surface of the terrain, successively filling up grid elements column wise.

    \param table - Hashtable containing all terrain coordinates and data to add occupancy.
    The elements of the table contains terrain coordinate, occupancy and compaction, respectively.
    \param shouldAvalanche - True if the terrainIndex should be queried for avalanching.
    */
    void addSolidOccupancyInColumns(agx::HashTable<agx::Vec2i, std::pair<float, float>> table, bool shouldAvalanche = true);

    /**
    Remove solid occupancy in the specified surface coordinate. The occupancy is removed from the surface of the terrain,
    successively removing occupancy from grid elements column wise.
    \param terrainIndex - The specified surface index.
    \param occupancy - The solid occupancy that should be removed in the column.
    \param shouldAvalanche - True if the terrainIndex should be queried for avalanching.
    \return The solid occupancy that have been removed at the column of the surface index.
    */
    float removeSolidOccupancyInColumn(const agx::Vec2i& terrainIndex, float occupancy, bool shouldAvalanche = true);

    /**
    Add solid occupancy layer with specific height and compaction on the terrain surface in the specified terrain coordinate.
    The solid occupancy is added on top of the surface of the terrain, successively filling up grid elements column wise up
    to layer height.

    \note - Function will not execute on negative layer heights.

    \param terrainIndex - The specified terrain index.
    \param layerHeight- The specified height of the solid occupancy layer to be added at the surface index.
    \param compaction - The compaction of the occupancy that should be added to the column.
    \param shouldAvalanche - True if the terrainIndex should be queried for avalanching.
    \return The solid occupancy of the layer that have been added to the surface index up to the layer height.
    */
    float addSolidOccupancyLayerInColum(const agx::Vec2i& terrainIndex, agx::Real layerHeight, float compaction, bool shouldAvalanche = true);

    /**
    Add solid occupancy in the specified terrain coordinate on the surface with given compaction up to a specific height.
    The solid occupancy is added on the surface of the terrain, successively filling up grid elements column wise until the
    specified height in the terrain is reached.

    \note - If the supplied height is lower than the height field at that grid index, the method will return zero added solid occupancy.

    \param terrainIndex - The specified terrain index.
    \param height - The specified height that solid occupancy is added at the surface index to reach.
    \param compaction - The compaction of the occupancy that should be added to the column.
    \param shouldAvalanche - True if the terrainIndex should be queried for avalanching.
    \return The solid occupancy that have been added to the surface index up to the specific height.
    */
    float addSolidOccupancyInColumnToHeight(const agx::Vec2i& terrainIndex, agx::Real height, float compaction, bool shouldAvalanche = true);

    /**
    Remove fluid occupancy in the specified 3D grid coordinate, if any exists.
    \param terrainIndex - The specified 3D index, containing surface coordinates and depth index.
    \param fluidOccupancy - The fluid occupancy that should be removed in the grid point.
    \return The fluid occupancy that was removed from the grid point.
    */
    float removeFluidOccupancy(const agx::Vec3i& terrainIndex, float fluidOccupancy);

    /**
    Get local angle of repose in the specified 3D index.
    \note - The angle of repose in a terrain grid element is dependent on the compaction in the element as well as the material properties.
    \param terrainIndex - The specified 3D index.
    \return The local angle of repose in the grid element given by the specified 3D terrain index.
    */
    agx::Real getLocalAngleOfRepose(const agx::Vec3i& terrainIndex) const;

    /**
    Get surface angle of repose in the 2D terrain index.
    \note - The angle of repose in a terrain grid element is dependent on the compaction in the element as well as the material properties.
    \param terrainIndex - The specified surface index.
    \return The angle of repose in the surface grid element given by the specified index.
    */
    agx::Real getSurfaceAngleOfRepose(const agx::Vec2i& terrainIndex) const;

    /**
    Apply a surface force (N) at the specified surface index in order to compact the soil. The surface force will be
    converted to a surface stress of the grid element area.
    \note - If the force is insufficient to compress the soil, i.e the resulting
            stress is under the pre-consolidation stress in any of the affected
            grid points, nothing will happen.

    \param terrainIndex - The specified surface index.
    \param surfaceForce - The specified surface force (N) to be applied.
    \param contactTime - The time that the force is active, which is used to calculate
                         the time relaxation of the compaction. The longer the contact
                         time, the more compaction will occur. See BulkMaterial for the
                         compaction relaxation constant.
    \return True if the force was enough to compact the soil.
    */
    bool applySurfaceForce(const agx::Vec2i& terrainIndex, agx::Real surfaceForce, agx::Real contactTime=-1);

    /**
    Apply a surface stress (Pa) at the specified surface index in order to compact the soil.
    \note - If the force is insufficient to compress the soil, i.e under the pre-consolidation
            stress in any of the affected grid points, nothing will happen.

    \param terrainIndex - The specified surface index.
    \param surfaceStress - The specified surface stress (Pa) to be applied.
    \param contactTime - The time that the force is active, which is used calculate the
                         time relaxation of the compaction. The longer the contact time,
                         the more compaction will occur. See BulkMaterial for the compaction
                         relaxation constant.
    \return True if the force was enough to compact the soil.
    */
    bool applySurfaceStress(const agx::Vec2i& terrainIndex, agx::Real surfaceStress, agx::Real contactTime = -1);

    /**
    Check is the given 3D index coordinate is within terrain bounds.
    \param terrainIndex - The specified 3D index.
    \return True if the specified terrain index is within terrain bounds.
    */
    bool isIndexValid(const agx::Vec3i& terrainIndex) const;

    /**
    Get the current stress in a 3D index that resulted from active TerrainContacts this time-step.
    \param terrainIndex - The specified 3D index.
    \return The current stress in the grid element given by the specified index.
    */
    agx::Real getCurrentStressInTerrainIndex(const agx::Vec3i& terrainIndex) const;

    /**
    Get the local pre-consolidation stress in the terrain grid element specified by a 3D terrain index.
    The pre-consolidation stress is the most recent stress that has been used to compress the solid occupancy
    in the grid element, which is either given by the BulkMaterial when the grid element is in nominal compaction,
    by the compaction stress generated from the terrain contacts or by the user via the grid interface.
    In order to compress the terrain further, the stress applied has to exceed the pre-consolidation stress.
    \param terrainIndex - The specified 3D terrain index.
    \return The pre-consolidation stress of the grid element given by the specified terrain index.
    */
    agx::Real getLocalPreconsolidationStress(const agx::Vec3i& terrainIndex) const;

    /**
    Get the surface pre-consolidation stress in the terrain grid element specified by a 3D terrain index.
    The pre-consolidation stress is the most recent stress that has been used to compress the solid occupancy in the
    grid element, which is either given by the BulkMaterial when the grid element is in nominal compaction, by the
    compaction stress generated from the terrain contacts or by the user via the grid interface. In order to compress
    the terrain further, the stress applied has to exceed the pre-consolidation stress.
    \param terrainIndex - The specified surface index.
    \return The pre-consolidation stress of the surface grid element given by the specified surface index.
    */
    agx::Real getSurfacePreconsolidationStress(const agx::Vec2i& terrainIndex) const;

    /**
    Get the world center position of a terrain grid element given a specified 3D index
    \param terrainIndex - the specified 3D terrain grid element index
    \return the world position of the center point of the grid element with specified index
    */
    agx::Vec3 getGridElementPositionWorld(const agx::Vec3i& terrainIndex) const;

    /**
    Get the surface hardness multiplier, derived from hardness rate and local compaction, that is used to
    scale the local contact point Young's modulus in a voxel given a specified surface index.
    The hardness multiplier is a function of the bulk material hardening rate and the voxel compaction.
    \param terrainIndex - the specified 2D surface index
    \return the surface hardness multiplier given a specified voxel compaction and bulk material hardening rate.
    */
    agx::Real getSurfaceHardnessMultiplier(const agx::Vec2i& terrainIndex) const;

    /**
    Get the local hardness multiplier, derived from hardness rate and local compaction, that is used to
    scale the local contact point Young's modulus in a voxel given a specified surface index.
    The hardness multiplier is a function of the bulk material hardening rate and the voxel compaction.
    \param terrainIndex - the specified 3D terrain grid element index for the voxel.
    \return the local hardness multiplier given a specified voxel compaction and bulk material hardening rate.
    */
    agx::Real getLocalHardnessMultiplier(const agx::Vec3i& terrainIndex) const;

    /**
    Find surface depth index given the terrain index x,y coordinates.
    \param terrainIndex - The specified terrainIndex.
    \return The terrain depth index at the surface.
    */
    int findSurfaceIndex(const agx::Vec2i& terrainIndex) const;

    /**
    Find the depth index under the surface at terrain index x,y coordinates and depth distance.
    \param terrainIndex - The specified terrainIndex.
    \param depth - depth distance
    \return The depth index at specific depth under the surface as the specified terrain index.
    */
    int findIndexFromSurfaceDepth(const agx::Vec2i& terrainIndex, agx::Real depth) const;

    /**
    Get the lowest allowable depth index in the terrain. This is dependent on the terrain depth and the element size.
    */
    int getLowestAllowableDepthIndex() const;

    /**
    Utility method used to convert solid occupancy to SI(Kg) units.
    */
    agx::Real convertOccupancyToMassKg(float occupancy) const;

    /**
    Utility method used to convert mass (kg) to occupancy.
    */
    agx::Real convertMassKgToOccupancy(float mass) const;

  public:
    DOXYGEN_START_INTERNAL_BLOCK()
    AGXTERRAIN_STORE_RESTORE_INTERFACE;
    DOXYGEN_END_INTERNAL_BLOCK()

  protected:
    agx::Vec3i convertTerrainIndexAndDepthToVoxelIndex(const agx::Vec3i& terrainIndex) const;
    agx::Vec3i convertSurfaceTerrainIndex(const agx::Vec2i& terrainIndex) const;

    virtual ~TerrainGridControl();

  private:
    Terrain* m_terrain;
  };
}
