// Copyright (c) 2012-2017 VideoStitch SAS
// Copyright (c) 2018 stitchEm

// Ambisonic encoder and decoder

#pragma once

#include "audioObject.hpp"
#include "audioBlock.hpp"
#include "ambDecoderDef.hpp"

#include "matrix.hpp"

#include <mutex>

namespace VideoStitch {
namespace Audio {

// We manage only first order for the moment
enum class AmbisonicOrder {
  FIRST_ORDER,
  SECOND_ORDER,  // TODO
  THIRD_ORDER,   // TODO
  UNKNOWN
};

enum class AmbisonicNorm {
  SN3D,  // Prioritary
  FUMA   // In a second time
};

/**
 * @brief Angular coordinates
 */
struct AngularPosition {
  double az;  // in radians
  double el;  // in radians
};

/**
 * @brief Returns the number of ambisonic channels corresponding to the ambisonic order
 * @param order Ambisonic order
 */
VS_EXPORT int getNbAmbisonicChannelsFromOrder(AmbisonicOrder order);

/**
 * @brief Returns a string corresponding of the ambisonic normalization type
 * @param norm Type of the ambisonic normalization (FUMA or SN3D)
 */
VS_EXPORT const char *getStringFromAmbisonicNorm(AmbisonicNorm norm);

/**
 * @brief Returns a string corresponding of the ambisonic order
 * @param order Ambisonic order (FIRST_ORDER, SECOND_ORDER, THIRD_ORDER)
 */
VS_EXPORT const char *getStringFromAmbisonicOrder(AmbisonicOrder order);

/**
 * @brief Returns the ambisonic order corresponding to the string
 * @param string (FIRST_ORDER, SECOND_ORDER, THIRD_ORDER)
 */
VS_EXPORT AmbisonicOrder getAmbisonicOrderFromString(const std::string &s);

/**
 * @brief Returns the ambisonic order corresponding to `order`
 * @param order [1 .. 3]
 */
VS_EXPORT AmbisonicOrder getAmbisonicOrderFromInt(int order);

/**
 * @brief Returns a channel layout corresponding to the ambisonic order
 * @param order Ambisonic order (FIRST_ORDER, SECOND_ORDER, THIRD_ORDER)
 */
VS_EXPORT ChannelLayout getChannelLayoutFromAmbisonicOrder(AmbisonicOrder order);

/**
 * @brief Returns a channel ambisonic corresponding to the index
 *        Follow this standard: http://ambisonics.ch/standards/channels/
 * @param i Index (or
 */
VS_EXPORT ChannelMap getChannelAmbFromChanIndex(int i);

/**
 * Ambisonic encoder:
 * Implements encoder from the FIRST_ORDER to the THIRD_ORDER. For the moment only the FIRST_ORDER is officially
 * supported. The normalization type can be set to SN3D (default value) or FUMA. The encoder supports only the following
 * layouts:
 * - MONO: you can set a position in azimuth and elevation
 * - STEREO
 * - 5.1
 * - 7.1
 */
class VS_EXPORT AmbEncoder : public AudioObject {
 public:
  /**
   * @brief Constructor.
   * @param order ambisonic order
   * @param norm ambisonic normalization type FUMA or SN3D
   * @param inLayout layout of the source
   */
  AmbEncoder(AmbisonicOrder order, AmbisonicNorm norm);
  /**
   * @brief Default destructor.
   */
  ~AmbEncoder();

  /**
   * @brief Sets the position of the source and update the encoding coefficients accordingly
   * @param azimuth and elevation in radians
   */
  void setMonoSourcePosition(const AngularPosition &pos);
  void step(AudioBlock &out, const AudioBlock &in);
  void step(AudioBlock &inout);

 private:
  /**
   * @brief Initializes encoding coefficients for a stereo input
   */
  void initializeStereoCoef();

  /**
   * @brief Initializes encoding coefficients for a 5.1 input
   */
  void initialize51Coef();

  /**
   * @brief Initialize encoding coefficients for a 7.1 input
   */
  void initialize71Coef();

  /**
   * @brief Returns a vector of coefficients for a sub woofer channel
   *        The sub woofer channel is considered omnidirectional so it has speciacl encoding coefficients.
   *        for a first order (1,0,0,0)
   *        for a second order (1,0,0,0,0,0,0,0,0)
   *        for a third order (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
   * @param order ambisonic order (FIRST_ORDER, SECOND_ORDER, THIRD_ORDER)
   * @param norm ambisonic normalization type (SN3D or FUMA)
   */
  std::map<ChannelMap, double> makeSubWooferCoef(AmbisonicOrder order, AmbisonicNorm norm);

  /**
   * @brief Initialize encoding coefficients for a mono source
   * @param coord azimuth and elevation in radians
   * @param map channel map of the concerned channel
   * @param order ambisonic order (FIRST_ORDER, SECOND_ORDER, THIRD_ORDER)
   * @param norm ambisonic normalization type (SN3D or FUMA)
   */
  std::map<ChannelMap, double> makeMonoCoef(const AngularPosition &coord, AmbisonicOrder order,
                                            AmbisonicNorm norm = AmbisonicNorm::SN3D);

  void showCoef() const;

  /**
   * @brief Updates the encoding coefficients with the new position
   * @param coord azimuth and elevation in radians
   * @param layout layout of the coefficients to update
   * @param m Channel map of the coefficients to update
   */
  void updateCoef(AngularPosition pos, ChannelLayout layout, ChannelMap m);

  AmbisonicOrder _order;
  AmbisonicNorm _norm;
  ambCoefTable_t _coefficients;  // maps the channel map to a vector of encoding coefficients
};

enum class AmbisonicDecodeType { Basic, MaxRe, InPhase };

/**
 * Ambisonic decoder:
 * Implements decoder for the FIRST_ORDER only. And it supports any layout, at the condition it as the corresponding
 * coefficients loaded. For the moment only STEREO and 5.1 layouts have been tested.
 */
class VS_EXPORT AmbDecoder : public AudioObject {
 public:
  /**
   * @brief Constructor.
   * @param outLayout layout of the output
   * @param coefficients table of decoding coefficients
   */
  explicit AmbDecoder(ChannelLayout l, const ambCoefTable_t &coef);

  /**
   * @brief Default destructor.
   */
  ~AmbDecoder();

  /**
   * @brief Process the decoding of the input to the output
   * @param out Output audio block
   * @param in Input audio block
   */
  void step(AudioBlock &out, const AudioBlock &in);
  void step(AudioBlock &inout);

  /**
   * @brief Sets table of decoding coefficients.
   * @param coefficients table of decoding coefficients
   */
  void setCoefficients(const ambCoefTable_t &coefficients);

 private:
  ChannelLayout _outLayout;
  ambCoefTable_t _coefficients;  // maps the channel map to a vector of decoding coefficients
};

/**
 * Ambisonic rotator:
 * Implements rotations for the FIRST_ORDER only.
 */
class VS_EXPORT AmbRotator : public AudioObject {
 public:
  /**
   * @brief Constructor.
   * @param outLayout layout of the output
   * @param coefficients table of decoding coefficients
   */
  explicit AmbRotator(const AmbisonicOrder o);

  /**
   * @brief Default destructor.
   */
  ~AmbRotator();

  /**
   * @brief Apply rotation
   * @param out Output audio block
   * @param in Input audio block
   */
  void step(AudioBlock &out, const AudioBlock &in);
  void step(AudioBlock &inout);

  /**
   * @brief Sets rotations.
   * @param Yaw, pitch, and roll of audio sound field.
   */
  void setRotation(double yaw, double pitch, double roll);

  /**
   * @brief Sets an offset to apply on the rotation.
   * @param Yaw, pitch, and roll of audio sound field.
   */
  void setRotationOffset(double yaw, double pitch, double roll);

  /**
   * @brief Gets rotations.
   * @param Yaw, pitch, and roll of audio sound field.
   */
  Vector3<double> getRotation() {
    std::lock_guard<std::mutex> lk(_rotMutex);
    return _rotation;
  }

  /**
   * @brief Gets offset rotation.
   * @param Yaw, pitch, and roll of audio sound field.
   */
  Vector3<double> getRotationOffset() {
    std::lock_guard<std::mutex> lk(_rotMutex);
    return _offset;
  }

 private:
  enum { YAW = 0, PITCH, ROLL };
  AmbisonicOrder _order;
  std::mutex _rotMutex;
  Vector3<double> _rotation;  // yaw, pitch, roll
  Vector3<double> _offset;    // yaw, pitch, roll
};

}  // namespace Audio
}  // namespace VideoStitch