#include <grpcpp/grpcpp.h>
#include "eci.pb.h"
#include "eci.grpc.pb.h"
#include "eci_wrapper.h"
#include <codecvt>
#include <locale>
#include <thread>

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;

static std::wstring widen(const std::string& s){
  std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> cv; return cv.from_bytes(s);
}

bool EciEngine::init(const std::wstring& dllPath, unsigned long voiceLangId, std::string& err){
  if(!eci_.load(dllPath, err)){ return false; }
  if(!voiceLangId) voiceLangId = 65536;
  h_ = eci_.eciNewEx(voiceLangId);
  if(!h_){ err = "eciNewEx failed (voice_lang_id=" + std::to_string(voiceLangId) + ")"; return false; }
  buf_.resize(kChunkSamples);
  eci_.eciSetOutputBuffer(h_, (int)buf_.size(), buf_.data());
  eci_.eciRegisterCallback(h_, &EciEngine::cbThunk, this);
  return true;
}

void EciEngine::setSinks(AudioSink a, IndexSink i){ std::lock_guard<std::mutex> lk(mu_); audio_=std::move(a); index_=std::move(i);}  

int EciEngine::cbThunk(int h, int msg, int lp, int dt, void* user){
  return static_cast<EciEngine*>(user)->onCallback(msg, lp);
}

int EciEngine::onCallback(int msg, int lp){
  std::lock_guard<std::mutex> lk(mu_);
  if(!speaking_) return 2;
  if(msg==0){ // audio
    const char* bytes = reinterpret_cast<const char*>(buf_.data());
    stash_.append(bytes, bytes + lp*2);
    if(stash_.size() >= kChunkSamples*2){ if(audio_) audio_(stash_.data(), stash_.size()); stash_.clear(); }
  } else if(msg==2){ // index
    if(lp != 0xFFFF){ if(index_) index_(lp); }
    else { if(audio_ && !stash_.empty()){ audio_(stash_.data(), stash_.size()); stash_.clear(); } if(index_) index_(-1); }
  }
  return 1;
}

void EciEngine::addText(const std::string& mbcs){ eci_.eciAddText(h_, mbcs.c_str()); }
void EciEngine::insertIndex(int idx){ eci_.eciInsertIndex(h_, idx); }
void EciEngine::synthesize(){ speaking_ = true; eci_.eciSynthesize(h_); }
void EciEngine::stop(){ speaking_ = false; stash_.clear(); eci_.eciStop(h_); }
void EciEngine::setParam(int p,int v){ eci_.eciSetParam(h_, p, v); }
void EciEngine::setVParam(int p,int v,bool temporary){ eci_.eciSetVoiceParam(h_,0,p,v); }
int  EciEngine::getVParam(int p){ return eci_.eciGetVoiceParam(h_,0,p); }
void EciEngine::copyVoice(int variant){ eci_.eciCopyVoice(h_,variant,0); }
std::unordered_map<unsigned,int> EciEngine::currentVParams(){
  std::unordered_map<unsigned,int> m; for(int p: {1,2,3,4,5,6,7}) m[(unsigned)p]=eci_.eciGetVoiceParam(h_,0,p); return m; }

class EciService final : public eci::ECI::Service {
public:
  Status Init(ServerContext* ctx, const eci::InitRequest* req, eci::InitReply* rep) override {
    std::string err; unsigned long lang = req->voice_lang_id();
    std::wstring dll = widen(req->eci_path().empty()? std::string("eloquence/eci.dll"): req->eci_path());
    if(!eng_.init(dll, lang, err)){
      rep->set_ok(false); rep->set_message(err); return Status::OK; }
    rep->set_ok(true); rep->set_message("ok");
    rep->set_sample_rate(EciEngine::kSampleRate);
    rep->set_channels(EciEngine::kChannels);
    rep->set_sample_bits(EciEngine::kBits);
    return Status::OK;
  }

  Status Tts(ServerContext* ctx, grpc::ServerReaderWriter<eci::TtsEvent, eci::TtsRequest>* stream) override {
    std::mutex qmu; bool done=false;

    auto sendAudio = [&](const char* d, size_t n){ std::lock_guard<std::mutex> lk(qmu); eci::TtsEvent ev; auto* a=ev.mutable_audio(); a->set_pcm(d, n); stream->Write(ev); };
    auto sendIndex = [&](int v){ std::lock_guard<std::mutex> lk(qmu); eci::TtsEvent ev; auto* i=ev.mutable_index(); i->set_value(v); stream->Write(ev); };

    eng_.setSinks(sendAudio, sendIndex);

    eci::TtsRequest req;
    while(stream->Read(&req)){
      if(req.has_add_text()){
        // ECI expects MBCS input; we pass-through assuming caller encodes accordingly.
        eng_.addText(req.add_text().text());
      } else if(req.has_insert_index()){
        eng_.insertIndex(req.insert_index().index());
      } else if(req.has_synth()){
        eng_.synthesize();
      } else if(req.has_pause()){
        // no-op: audio device is client-side; pause implemented there.
      } else if(req.has_terminate()){
        break;
      }
    }

    // flush stopped event
    eci::TtsEvent ev; ev.mutable_stopped()->set_dummy(true); stream->Write(ev);
    return Status::OK;
  }

  Status SetParam(ServerContext*, const eci::SetParamRequest* r, eci::Empty*) override { eng_.setParam((int)r->param(), (int)r->value()); return Status::OK; }
  Status SetVParam(ServerContext*, const eci::SetVParamRequest* r, eci::Empty*) override { eng_.setVParam((int)r->param(), (int)r->value(), r->temporary()); return Status::OK; }
  Status GetVParam(ServerContext*, const eci::GetVParamRequest* r, eci::GetVParamReply* rep) override { rep->set_value(eng_.getVParam((int)r->param())); return Status::OK; }
  Status CopyVoice(ServerContext*, const eci::CopyVoiceRequest* r, eci::Empty*) override { eng_.copyVoice((int)r->variant()); return Status::OK; }
  Status Stop(ServerContext*, const eci::Empty*, eci::Empty*) override { eng_.stop(); return Status::OK; }

private:
  EciEngine eng_;
};

static void RunServer(const std::string& addr){
  EciService svc;
  ServerBuilder b; b.AddListeningPort(addr, grpc::InsecureServerCredentials()); b.RegisterService(&svc);
  std::unique_ptr<Server> server(b.BuildAndStart());
  printf("ECI gRPC server (x86) listening on %s\n", addr.c_str());
  server->Wait();
}

int main(int argc, char** argv){
  std::string addr = "127.0.0.1:18951";
  std::string eciArg;
  for(int i=1;i<argc;i++){
    std::string a = argv[i];
    if(a=="--addr" && i+1<argc) addr = argv[++i];
    else if(a=="--eci" && i+1<argc) eciArg = argv[++i];
  }
  // If you pass --eci, pre-initialize so failure is visible in console:
  if(!eciArg.empty()){
    EciEngine tmp;
    std::string err;
    std::wstring wpath(eciArg.begin(), eciArg.end());
    if(!tmp.init(wpath, 65536, err)){
      fprintf(stderr, "ECI preload failed: %s\n", err.c_str());
      return 2;
    }
  }
  RunServer("127.0.0.1:18951");
}