#ifndef Spatial_SPSize3D_h
#define Spatial_SPSize3D_h

#include <Spatial/Structures.h>
#include <Spatial/SPRect3D.h>
#include <Spatial/SPAffineTransform3D.h>
#include <Spatial/SPProjectiveTransform3D.h>
#include <math.h>

// MARK: - Public API

// MARK: - Creating a size

/*!
 @abstract Creates a size structure with the specified dimensions.
 
 @param width The width.
 @param height The height.
 @param depth The depth.
 @returns A new size stucture.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DMake(double width, double height, double depth)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Creates a size structure with dimensions specified as a 3-element SIMD vector.
 
 @param xyz The source vector.
 @returns A new size stucture.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DMakeWithVector(simd_double3 xyz)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Creates a size structure with dimensions specified as a Spatial vector.
 
 @param xyz The source vector.
 @returns A new size stucture.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DMakeWithVector(SPVector3D xyz)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Creates a size structure with dimensions specified as the coordinates of a  point structure.
 
 @param point The source point.
 @returns A new size stucture.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DMakeWithPoint(SPPoint3D point)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

// MARK: - Querying size properties

/*!
 @abstract Returns the @p width, @p height, and @p depth components of the size expressed as a simd vector.
 
 @param size The source size.
 @returns A vector that represents the size.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
simd_double3 SPSize3DGetVector(SPSize3D size)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/// Returns @p true if both sizes are equal.
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
bool SPSize3DEqualToSize(SPSize3D size1, SPSize3D size2)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

// MARK: - Transforming a size

/*!
 @abstract Returns a size with dimensions scaled by the specified values.
 
 @param size The source size.
 @param x The scale value along the @p width dimension.
 @param y The scale value along the @p height dimension.
 @param z The scale value along the @p depth dimension.
 @returns A size that's scaled by the specified values.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DScaleBy(SPSize3D size, double x, double y, double z)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Returns a size with dimensions scaled by the specified size.
 
 @param size The source size.
 @param scale The scale value.
 @returns A size that's scaled by the specified value.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DScaleBySize(SPSize3D size, SPSize3D scale)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Returns a size with dimensions uniformly scaled by the specified value.
 
 @param size The source size.
 @param scale The scale value.
 @returns A size that's uniformly scaled by the specified value.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DScaleUniform(SPSize3D size, double scale)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Returns a size that's rotated by the specified rotation around the origin.
 
 @param size The source size.
 @param rotation The rotation.
 @returns A size that's rotated by the specified rotation.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DRotate(SPSize3D size, SPRotation3D rotation)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Returns a size that's rotated by the specified quaternion around the origin.
 
 @param size The source size.
 @param quaternion The quaternion that defines the rotation.
 @returns A size that's rotated by the specified rotation.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DRotateByQuaternion(SPSize3D size, simd_quatd quaternion)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Returns a size that's transformed by the specified affine transform.
 
 @param size The source size.
 @param transform The affine transform that the function applies to the size.
 @returns The transformed size.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DApplyAffineTransform(SPSize3D size,
                                      SPAffineTransform3D transform)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Returns a size that's transformed by the specified projective transform.
 
 @param size The source size.
 @param transform The projective transform that the function applies to the size.
 @returns The transformed size.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DApplyProjectiveTransform(SPSize3D size,
                                          SPProjectiveTransform3D transform)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Returns a size that's transformed by the inverse of the specified affine transform.
 
 @param size The source size.
 @param transform The affine transform that the function unapplies to the size.
 @returns The transformed size.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DUnapplyAffineTransform(SPSize3D size,
                                        SPAffineTransform3D transform)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Returns a size that's transformed by the inverse of the specified projective transform.
 
 @param size The source size.
 @param transform The projective transform that the function unapplies to the size.
 @returns The transformed size.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DUnapplyProjectiveTransform(SPSize3D size,
                                            SPProjectiveTransform3D transform)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Returns a size that's sheared over an axis by shear factors for the other two axes.
 
 @param size The source size.
 @param shearAxis The shear axis.
 @param shearFactor0 The first shear factor.
 @param shearFactor1 The second shear factor.
 @returns The transformed size.
 @discussion
 When the shear axis is @p x , @p shearFactor0 is the @p y shear factor and @p shearFactor0 is the @p z shear factor.
 
 When the shear axis is @p y , @p shearFactor0 is the @p x  shear factor and @p shearFactor0 is the @p z shear factor.
 
 When the shear axis is @p z , @p shearFactor0 is the @p x  shear factor and @p shearFactor0 is the @p y shear factor..
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DShear(SPSize3D size,
                       SPAxis shearAxis,
                       double shearFactor0,
                       double shearFactor1)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

// MARK: - Deriving new sizes

/*!
 @abstract Returns the smallest size that contains two sizes.
 
 @param size The first size.
 @param other The second size.
 @returns A size structure that is the smallest size that contains two sizes.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DUnion(SPSize3D size, SPSize3D other)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Returns the intersection of the two sizes.
 
 @param size The first size.
 @param other The second size.
 @returns A size structure that is the intersection of the two sizes.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DIntersection(SPSize3D size, SPSize3D other)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

// MARK: - Volumetric operations

/*!
 @abstract Returns a Boolean value that indicates whether the first size contains the second size.
 
 @param size The first size.
 @param other The second size.
 @returns A Boolean value that indicates whether the first size contains the second size.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
bool SPSize3DContainsSize(SPSize3D size, SPSize3D other)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Returns a Boolean value that indicates whether the size contains the point.
 
 @param size The size.
 @param other The point.
 @returns A Boolean value that indicates whether the size contains the point.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
bool SPSize3DContainsPoint(SPSize3D size, SPPoint3D other)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

/*!
 @abstract Returns a Boolean value that indicates whether the size contains any of the specified points.
 
 @param size The size.
 @param points The points.
 @param pointCount The number of points.
 @returns A Boolean value that indicates whether the size contains any of the specified points.
*/
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
bool SPSize3DContainsAnyPoint(SPSize3D size,
                               const SPPoint3D *points, int pointCount)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

// MARK: Header inline implementations

// MARK: - Creating a size

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DMake(double width, double height, double depth) {
    return (SPSize3D){ .width = width, .height = height, .depth = depth };
}

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DMakeWithVector(simd_double3 xyz) {
    return (SPSize3D){ .vector = xyz };
}

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DMakeWithVector(SPVector3D xyz) {
    return (SPSize3D){ .vector = xyz.vector };
}

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DMakeWithPoint(SPPoint3D point) {
    return (SPSize3D){ .vector = point.vector };
}

// MARK: - Querying size properties

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
simd_double3 SPSize3DGetVector(SPSize3D size) {
    return size.vector;
}

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
bool SPSize3DEqualToSize(SPSize3D size1, SPSize3D size2) {
    return simd_equal(size1.vector, size2.vector);
}
// MARK: - Transforming a size

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DScaleBy(SPSize3D size, double x, double y, double z) {
    
    return (SPSize3D){ .vector = size.vector * simd_make_double3(x, y, z)};
}

SPATIAL_SWIFT_NAME(Size3D.scaled(self:by:))
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DScaleBySize(SPSize3D size, SPSize3D scale) {
    
    return (SPSize3D){ .vector = size.vector * scale.vector };
}

SPATIAL_SWIFT_NAME(Size3D.uniformlyScaled(self:by:))
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DScaleUniform(SPSize3D size, double scale) {
    
    return (SPSize3D){ .vector = size.vector * scale };
}

SPATIAL_SWIFT_NAME(Size3D.rotated(self:by:))
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DRotate(SPSize3D size, SPRotation3D rotation) {
    
    SPAffineTransform3D t = SPAffineTransform3DMakeRotation(rotation);

    return SPSize3DApplyAffineTransform(size, t);
}

SPATIAL_SWIFT_NAME(Size3D.rotated(self:by:))
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DRotateByQuaternion(SPSize3D size, simd_quatd quaternion) {

    SPRotation3D r = SPRotation3DMakeWithQuaternion(quaternion);
  
    return SPSize3DRotate(size, r);
}

SPATIAL_SWIFT_NAME(Size3D.applying(self:_:))
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DApplyAffineTransform(SPSize3D size, SPAffineTransform3D transform) {
    
    simd_double4 rhs = simd_make_double4(size.vector, 0);
    
    simd_double3 transformed = simd_mul(transform.matrix, rhs).xyz;
    
    return (SPSize3D){ .vector = transformed };
}

SPATIAL_SWIFT_NAME(Size3D.applying(self:_:))
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DApplyProjectiveTransform(SPSize3D size, SPProjectiveTransform3D transform) {

    simd_double4 rhs = simd_make_double4(size.vector, 0);
    
    simd_double3 transformed = simd_mul(transform.matrix, rhs).xyz;
    
    return (SPSize3D){ .vector = transformed };
}

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DUnapplyAffineTransform(SPSize3D size, SPAffineTransform3D transform) {
  
    SPAffineTransform3D invertedTransform = SPAffineTransform3DInverted(transform);
    
    return SPSize3DApplyAffineTransform(size, invertedTransform);
}

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DUnapplyProjectiveTransform(SPSize3D size, SPProjectiveTransform3D transform) {

    SPProjectiveTransform3D invertedTransform = SPProjectiveTransform3DInverted(transform);
    
    return SPSize3DApplyProjectiveTransform(size, invertedTransform);
}

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DShear(SPSize3D size,
                       SPAxis shearAxis,
                       double shearFactor0,
                       double shearFactor1) {
    
    SPRect3D rect = SPRect3DMakeAtOrigin(SPPoint3DZero,
                                         size);
    
    SPSize3D transformed = SPRect3DShear(rect, shearAxis,
                                         shearFactor0, shearFactor1).size;
    
    return transformed;
}

SPATIAL_SWIFT_NAME(Size3D.contains(self:_:))
SPATIAL_OVERLOADABLE
bool SPSize3DContainsSize(SPSize3D size, SPSize3D other)  {
    
    simd_double3 v0 = size.vector;
    simd_double3 v1 = other.vector;
    
    return simd_equal(v0 >= v1, (simd_long3){-1, -1, -1});
}

// MARK: - Deriving new sizes

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DIntersection(SPSize3D size, SPSize3D other) {
    
    SPRect3D rect = (SPRect3D){ SPPoint3DZero, size };
    
    SPRect3D otherRect = (SPRect3D){ SPPoint3DZero, other };
    
    return SPRect3DIntersection(rect, otherRect).size;
}

SPATIAL_SWIFT_NAME(Size3D.union(self:_:))
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DUnion(SPSize3D size, SPSize3D other) {
    
    SPRect3D rect = (SPRect3D){SPPoint3DZero,
        size};
    
    SPRect3D otherRect = (SPRect3D){SPPoint3DZero,
        other};
    
    return SPRect3DUnion(rect, otherRect).size;
}

// MARK: - Volumetric operations

SPATIAL_SWIFT_NAME(Size3D.contains(self:point:))
SPATIAL_OVERLOADABLE
bool SPSize3DContainsPoint(SPSize3D size, SPPoint3D other) {
    
    SPRect3D rect = (SPRect3D){SPPoint3DZero, size};
    
    return SPRect3DContainsPoint(rect, other);
}

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
bool SPSize3DContainsAnyPoint(SPSize3D size, const SPPoint3D *points, int pointCount) {
    
    SPRect3D rect = (SPRect3D){ SPPoint3DZero, size };
    
    return SPRect3DContainsAnyPoint(rect, points, pointCount);
}

// MARK: - Transform by Pose

/*!
 @abstract Returns a size that's transformed by the specified pose.
 
 @param size The source size.
 @param pose The pose that the function applies to the size.
 @returns The transformed size.
 */
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DApplyPose(SPSize3D size,
                           SPPose3D pose)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DApplyPose(SPSize3D size,
                           SPPose3D pose) {
    
    simd_double3 transformed = simd_act(simd_normalize(pose.rotation.quaternion),
                                        size.vector);
    
    return (SPSize3D){ .vector = transformed };
}

/*!
 @abstract Returns a size that's transformed by the inverse of the specified pose.
 
 @param size The source size.
 @param pose The pose that the function unapplies to the size.
 @returns The transformed size.
 */
SPATIAL_INLINE
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DUnapplyPose(SPSize3D size,
                             SPPose3D pose)
__API_AVAILABLE(macos(13.0), ios(16.0), watchos(9.0), tvos(16.0));

SPATIAL_REFINED_FOR_SWIFT
SPATIAL_OVERLOADABLE
SPSize3D SPSize3DUnapplyPose(SPSize3D size,
                             SPPose3D pose) {
    
    simd_double4 v = simd_make_double4(size.vector, 0);
    simd_double4x4 m = simd_matrix4x4(pose.rotation.quaternion);
    m.columns[3].xyz = pose.position.vector;
    m = simd_inverse(m);
    
    simd_double3 transformed = simd_mul(m, v).xyz;
    
    return (SPSize3D){ .vector = transformed };
}

#endif /* Spatial_SPSize3D_h */

