#pragma once
#include "AudioTools/AudioLibs/Desktop/NoArduino.h"
#include "AudioTools/CoreAudio/AudioStreams.h"
#include "AudioTools/CoreAudio/AudioOutput.h"
#include "AudioTools/AudioCodecs/CodecWAV.h"
#include <string.h>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <stdio.h>
#include "nlohmann/json.hpp"
#include "xtl/xbase64.hpp"

namespace audio_tools {

/**
 * @brief Simple layer for Print object to write to a c++ file
 */
class FileOutput : public Print {
public:
    FileOutput(std::fstream &stream){
        p_audio_stream = &stream;
    }
    size_t write(const uint8_t *data, size_t len) override {
         p_audio_stream->write((const char*)data,len);
         return len;
    }
    int availableForWrite() override {
      return 1024;
    }
protected:
    std::fstream *p_audio_stream=nullptr;
};


/**
 * @brief Displays audio in a Jupyter as chart
 * Just wrapps a stream to provide the chart data
 */
template <typename T> 
class ChartT {
public:
  void setup(std::string fName, int channelCount, int channelNo) {
    this->fname = fName;
    this->channels = channelCount;
    if (this->channels==0){
      LOGE("Setting channels to 0");
    }
    this->channel = channelNo;
  }

  int getChannels() {
    return this->channels;
  }

  int getChannel() {
    return this->channel;
  }

  /// Provides data as svg polyline
  const std::string chartData() {
    str.clear();
    str.str("");
    // reset buffer;
    if (channel<channels){
        ifstream is;
        is.open(fname, is.binary);
        is.seekg(wav_header_size, is.beg);
        std::list<int16_t> audioList;
        T buffer[channels];
        size_t rec_size = channels*sizeof(T);
        while(is.read((char *)buffer, rec_size)){
            audioList.push_back(transform(buffer[channel]));
        }
        string str_size = "102400"; //std::to_string(audioList.size());
        str << "<style>div.x-svg {width: "<< str_size <<"px; }</style>";
        str << "<div class='x-svg'><svg viewBox='0 0 "<< str_size << " 100'> <polyline fill='none' stroke='blue' stroke-width='1' points ='";
        // copy data from input stream
        size_t idx = 0;
        for(int16_t sample: audioList){
            str << idx++ << "," << sample  << " ";
        }
        str << "'/></svg></div>";
    } else {
        str << "<p>Channel " << channel << " of " << channels <<  " does not exist!</p>";
    }
    return str.str();
  }

protected:
  std::stringstream str;
  std::string fname;
  const int wav_header_size = 44;
  int channels=0;
  int channel=0;

  int transform(int x){
    int result = x / 1000; // scale -32 to 32
    result += 60; // shift down
    return result;
  }
};

/// @brief Default chart type for Jupyter integration
/// @ingroup io
using Chart = ChartT<int16_t>;

/**
 * @brief Output to Jupyter. We write the data just to a file from where we can
 * load the data again for different representations.
 */
template <typename T> 
class JupyterAudioT : public AudioStream {
public:

  JupyterAudioT(const char* fileName, AudioStream &stream, int bufferCount=20, int bufferSize=1024) {
    buffer_count = bufferCount;
    p_audio_stream = &stream;
    cfg = stream.audioInfo();
    copier.resize(bufferSize);
    fname = fileName;
    if (fileExists()){
        remove(fileName);
    }
  }

  ChartT<T> &chart(int channel=0) {
    createWAVFile();
    assert(cfg.channels>0);
    chrt.setup(fname, cfg.channels, channel);
    return chrt;
  }

  // provide the file name
  const std::string &name() const {
    return fname;
  }

  // provides the absolute file path as string
  const std::string path() const {
    std::filesystem::path p = fname;
    std::string result = std::filesystem::absolute(p);
    return result;
  }

  // fills a wav file with data once, the first time it was requested
  void createWAVFile(){
    try{
      if (!fileExists()){
          std::fstream fstream(fname, fstream.binary | fstream.trunc | fstream.out);
          FileOutput fp(fstream);
          wave_encoder.setAudioInfo(audioInfo());
          out.setOutput(&fp);
          out.setEncoder(&wave_encoder);
          out.begin(); // output to decoder
          copier.begin(out, *p_audio_stream);
          copier.copyN(buffer_count);
          fstream.close();
      }
    } catch(const std::exception& ex){
        std::cerr << ex.what();
    }
  }

  bool fileExists() {
    ifstream f(fname.c_str());
    return f.good();
  }

  int bufferCount(){
    return buffer_count;
  }

  // provides the wav data as bas64 encded string
  std::string audio() {
    std::ifstream fin(fname, std::ios::binary);
    std::stringstream m_buffer;
    m_buffer << fin.rdbuf();
    return xtl::base64encode(m_buffer.str());
  }

  // Provides the audion information
  AudioInfo audioInfo() {
    return cfg;
  }

protected:
  AudioStream *p_audio_stream=nullptr;
  ChartT<T> chrt;
  WAVEncoder wave_encoder;
  EncodedAudioOutput out;
  StreamCopyT<T> copier;
  AudioInfo cfg;
  string fname;
  size_t buffer_count=0;
};

/// @brief Default Jupyter audio output with 16-bit samples
/// @ingroup io
using JupyterAudio = JupyterAudioT<int16_t>;

} // namespace audio_tools

/// Disply Chart in Jupyterlab xeus
nl::json mime_bundle_repr(Chart &in) {
  auto bundle = nl::json::object();
  bundle["text/html"] =  in.chartData();
  return bundle;
}

/// Disply Audio player in Jupyterlab xeus
nl::json mime_bundle_repr(JupyterAudio &in) {
  auto bundle = nl::json::object();
  in.createWAVFile();
  bundle["text/html"] = "<audio controls "
                        "src='data:audio/wav;base64," +
                        in.audio() + "'/>";
  return bundle;
}

