/**
 * Copyright (c) 2015, Samsung Electronics Co., Ltd
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 *  * Neither the name of Samsung Electronics nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @file  audio.h
 *
 * @brief
 * This module demonstrates usage of pp::Audio interface. Besides simple
 * WAVE file loading and playing, it also shows how to mix together more
 * than one sound source.
 */

#ifndef AUDIO_SRC_AUDIO_H_
#define AUDIO_SRC_AUDIO_H_

#include <stdlib.h>
#include <string>
#include <map>
#include <vector>

#include "ppapi/cpp/audio.h"
#include "ppapi/cpp/audio_config.h"
#include "ppapi/cpp/completion_callback.h"
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/url_loader.h"
#include "ppapi/cpp/url_request_info.h"
#include "ppapi/cpp/var.h"
#include "ppapi/utility/completion_callback_factory.h"

// buffer for reading data from file
#define READ_BUFFER_SIZE 32768
const int kNumberOfInputSounds = 3;

// WAVE format header
struct WAVEFileHeader {
  char chunk_id[4];
  int32_t chunk_size;
  char format[4];

  char subchunk1_id[4];
  int32_t subchunk1_size;
  int16_t audio_format;
  int16_t num_channels;
  int32_t sample_rate;
  int32_t byte_rate;
  int16_t block_align;
  int16_t bits_per_sample;

  char subchunk2_id[4];
  int32_t subchunk2_size;
};

// Only stereo sounds supported
static const unsigned MAX_CHANNELS_NUMBER = 2u;
// The sample count we will request in one frame
static const unsigned SAMPLE_FRAME_COUNT = 4096u;

// struct to keep sound data
struct SoundInstance {
  int id_;
  /**
   * Number of channels in audio file.
   */
  uint16_t channels_;
  /**
   * Number of samples in audio file.
   */
  uint32_t sample_data_size_;
  /**
   * Pointer to audio samples data.
   */
  std::unique_ptr<uint16_t[]> sample_data_;
  /**
   * Offset indicating actual position in playing samples stream.
   */
  uint32_t sample_data_offset_;
  /**
   * Constructor
   */
  SoundInstance(int id, uint16_t channels, uint32_t sample_data_size,
      std::unique_ptr<uint16_t>* sample_data);
};


class AudioInstance: public pp::Instance {
 public:
  typedef std::map<int, std::shared_ptr<SoundInstance> > SoundInstances;

  explicit AudioInstance(PP_Instance instance);

  virtual ~AudioInstance();

  virtual void HandleMessage(const pp::Var& var_message);

  virtual bool Init(uint32_t argc, const char* argn[],
      const char* argv[]);

 private:
  /**
   * Combines a command message containing of command literal and sound id.
   *
   * @param [in] command message to JavaScript
   * @param [in] id which sound to control with the created message
   *
   * @return combined message
   */
  std::string CreateCommandMessage(const char* command, int id);
  /**
   * Gets an id of sound from received message
   *
   * @param [in] message received from JavaScript
   * @param [in] command control command
   *
   * @return id of a sound
   */
  int GetIdFromMessage(const std::string& message, const char* command);
  // Audio handling methods
  /**
   * Interprets data from file, reading header and samples data.
   *
   * @param [in] data read from file
   */
  void ReadWAVE(const std::string& data);
  /**
   * Checks if the header of read WAVE file is correct.
   *
   * @param header header of the read file
   *
   * @return "OK" if the header is correct and error message otherwise
   */
  std::string CheckForWAVEHeaderErrors(const WAVEFileHeader& header);
  /**
   * Starts playback of given sound.
   *
   * @param [in] number of sound
   */
  void Play(int number);
  /**
   * Pause playback of given sound.
   *
   * @param [in] number of sound
   */
  void Pause(int number);
  /**
   * Stops playback of given sound.
   *
   * @param [in] number of sound
   */
  void Stop(int number);
  /**
   * Callback, that is called when browser needs samples to play
   *
   * @param samples pointer to memory, where samples should be delivered
   * @param buffer_size size of memory pointed by samples
   * @param data user data
   */
  static void AudioCallback(void* samples, uint32_t buffer_size, void* data);
  /**
   * Safe add for sound samples that prevents an overflow of a 16-bit value.
   *
   * @param dest add destination
   * @param arg add argument
   */
  void SafeAdd(int16_t *dest, int16_t arg) const;

  // URLLoader handling methods
  void PrepareReadingFile(const std::string &file_name_command);
  /**
   * Called when file is opened. Starts reading file.
   *
   * @param [in] result of opening URL
   */
  void OnOpenURLCallback(int32_t result);
  /**
   * Reads opened file into a buffer.
   */
  void ReadFile(void);
  /**
   * Called when part of file is read into a partial read buffer. Calls ReadFile() if
   * there still is something to read and FileReadCompleted() if reading
   * finished or there was an error.
   *
   * @param [in] result of read
   */
  void OnReadFileCallback(int32_t result);
  /**
   * Rewrites data from a reading buffer to a buffer containing whole file data.
   *
   * @param [in] count of bytes to be rewritten
   */
  void AppendData(int32_t bytes_count);
  /**
   * Called when reading file is completed. If no error occurred,
   * calls ReadWAVE().
   *
   * @param [in] result if file reading finished with success
   */
  void FileReadCompleted(bool result);

 private:
  /**
   * WAVE format header.
   */
  WAVEFileHeader* header_;
  /**
   * Number of currently read sound file.
   */
  uint file_number_;
  /**
   * Vector with names of loaded files.
   */
  std::vector<std::string> file_names_;

  // Audio interface specific members
  /**
   * Audio resource for playing audio stream.
   */
  pp::Audio audio_;
  /**
   * Number of samples in one frame.
   */
  uint32_t sample_frame_count_;
  /**
   * Buffer for data read from file.
   */
  std::unique_ptr<char[]> file_data_bytes_;
  /**
   * Vector of sound instances read from files.
   */
  SoundInstances sound_instances_;
  /**
   * Number of active sounds.
   */
  uint16_t active_sounds_number_;
  /**
   * Array to hold information which sounds are active.
   */
  bool active_sounds_[kNumberOfInputSounds];

  // URLLoader interface specific members
  /**
   * Path to a file to load.
   */
  std::string url_;
  /**
   * Resource for handling URL requests.
   */
  std::unique_ptr<pp::URLRequestInfo> url_request_;
  /**
   * Resource for loading a file.
   */
  std::unique_ptr<pp::URLLoader> url_loader_;
  /**
   * Data read from file.
   */
  std::string file_data_;
  /**
   * Buffer for partial read.
   */
  std::unique_ptr<char[]> buffer_;
  /**
   * Factory for callbacks used in loading file.
   */
  pp::CompletionCallbackFactory<AudioInstance> callback_factory_;
};

#endif  // AUDIO_SRC_AUDIO_H_
