/*
  MIT License

Copyright (c) 2022 Michael Hansen, 2025 Derek L Davies

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <sys/time.h>
#include <array>
#include <chrono>
#include <condition_variable>
#include <fstream>
#include <functional>
#include <limits>
#include <filesystem>
#include <sstream>
#include <mutex>
#include <stdexcept>
#include <vector>

#include <espeak-ng/speak_lib.h>
#include <onnxruntime_cxx_api.h>
#include <json.hpp>
#include <piper.hpp>
#include <utf8.h>
#include <wavfile.hpp>
#include <rubberband/RubberBandStretcher.h>

#include "spd_audio.h"
#include <speechd_types.h>
#include "module_utils.h"

#define MODULE_NAME     "Cxxpiper"
#define DBG_MODNAME     "Cxxpiper"
#define MODULE_VERSION  "0.0"
#define DEBUG_MODULE 5
DECLARE_DEBUG();
//}
namespace cxxpiper {
    using namespace std;
    using json = nlohmann::json;
    // rubberband stretcher.  "Realtime" mode, streaming (single pass, no "study"), clipping be damned.
    // Used for real time pitch, rate and volume adjustment.
    using RubberBand::RubberBandStretcher;
    using namespace piper;

#ifdef _CXXPIPER_VERSION
#define _STR(x) #x
#define STR(x) _STR(x)
    const std::string VERSION = STR(_CXXPIPER_VERSION);
#else
    const std::string VERSION = "";
#endif
    std::string getVersion() { return VERSION; }
    const std::string instanceName{"cxxpiper"};

    const float MIN_WAV_VALUE = -32766.0f;
    const float MAX_WAV_VALUE = 32767.0f;

    static const int bs = 1024;
    static float **cbuf;
    static piper::PiperConfig piperConfig;
    static char *cmdInp;
    static int stop_requested;

    static size_t cbCnt = 0; // DEBUG only
    static size_t cbTot = 0;
    static size_t prCnt = 0;
    static size_t prTot = 0;
    static size_t izCnt = 0;
    static size_t izTot = 0;
    static size_t ezCnt = 0;
    static size_t ezTot = 0;

    extern "C" {
	static int cxxpiper_free_cbuf();
	static void cxxpiper_free_voice_list();
    }

// Copied from piper distribution.
// True if the string is a single UTF-8 codepoint
    bool isSingleCodepoint(std::string s)
    {
	return utf8::distance(s.begin(), s.end()) == 1;
    }

// Copied from piper distribution.
// Get the first UTF-8 codepoint of a string
    Phoneme getCodepoint(std::string s)
    {
	utf8::iterator character_iter(s.begin(), s.begin(), s.end());
	return *character_iter;
    }

// Copied from piper distribution.
    void parsePhonemizeConfig(json &configRoot, PhonemizeConfig &phonemizeConfig)
    {
	// {
	//     "espeak": {
	//         "voice": "<language code>"
	//     },
	//     "phoneme_type": "<espeak or text>",
	//     "phoneme_map": {
	//         "<from phoneme>": ["<to phoneme 1>", "<to phoneme 2>", ...]
	//     },
	//     "phoneme_id_map": {
	//         "<phoneme>": [<id1>, <id2>, ...]
	//     }
	// }
	if (configRoot.contains("espeak")) {
	    auto espeakValue = configRoot["espeak"];
	    if (espeakValue.contains("voice")) {
		phonemizeConfig.eSpeak.voice = espeakValue["voice"].get<std::string>();
	    }
	}
	if (configRoot.contains("phoneme_type")) {
	    auto phonemeTypeStr = configRoot["phoneme_type"].get<std::string>();
	    if (phonemeTypeStr == "text") {
		phonemizeConfig.phonemeType = TextPhonemes;
	    }
	}
	// phoneme to [id] map
	// Maps phonemes to one or more phoneme ids (required).
	if (configRoot.contains("phoneme_id_map")) {
	    auto phonemeIdMapValue = configRoot["phoneme_id_map"];
	    for (auto &fromPhonemeItem : phonemeIdMapValue.items()) {
		std::string fromPhoneme = fromPhonemeItem.key();
		if (!isSingleCodepoint(fromPhoneme)) {
		    std::stringstream idsStr;
		    for (auto &toIdValue : fromPhonemeItem.value()) {
			PhonemeId toId = toIdValue.get<PhonemeId>();
			idsStr << toId << ",";
		    }
		    //error("\"{}\" is not a single codepoint (ids={})", fromPhoneme,
		    //idsStr.str());
		    throw std::runtime_error(
			"Phonemes must be one codepoint (phoneme id map)");
		}
		auto fromCodepoint = getCodepoint(fromPhoneme);
		for (auto &toIdValue : fromPhonemeItem.value()) {
		    PhonemeId toId = toIdValue.get<PhonemeId>();
		    phonemizeConfig.phonemeIdMap[fromCodepoint].push_back(toId);
		}
	    }
	}
	// phoneme to [phoneme] map
	// Maps phonemes to one or more other phonemes (not normally used).
	if (configRoot.contains("phoneme_map")) {
	    if (!phonemizeConfig.phonemeMap) {
		phonemizeConfig.phonemeMap.emplace();
	    }
	    auto phonemeMapValue = configRoot["phoneme_map"];
	    for (auto &fromPhonemeItem : phonemeMapValue.items()) {
		std::string fromPhoneme = fromPhonemeItem.key();
		if (!isSingleCodepoint(fromPhoneme)) {
		    //error("\"{}\" is not a single codepoint", fromPhoneme);
		    throw std::runtime_error(
			"Phonemes must be one codepoint (phoneme map)");
		}
		auto fromCodepoint = getCodepoint(fromPhoneme);
		for (auto &toPhonemeValue : fromPhonemeItem.value()) {
		    std::string toPhoneme = toPhonemeValue.get<std::string>();
		    if (!isSingleCodepoint(toPhoneme)) {
			throw std::runtime_error(
			    "Phonemes must be one codepoint (phoneme map)");
		    }
		    auto toCodepoint = getCodepoint(toPhoneme);
		    (*phonemizeConfig.phonemeMap)[fromCodepoint].push_back(toCodepoint);
		}
	    }
	}
    }

// Copied from piper distribution.
    void parseSynthesisConfig(json &configRoot, SynthesisConfig &synthesisConfig)
    {
	if (configRoot.contains("audio")) {
	    auto audioValue = configRoot["audio"];
	    if (audioValue.contains("sample_rate")) {
		// Default sample rate is 22050 Hz
		synthesisConfig.sampleRate = audioValue.value("sample_rate", 22050);
	    }
	}
	if (configRoot.contains("inference")) {
	    // Overrides default inference settings
	    auto inferenceValue = configRoot["inference"];
	    if (inferenceValue.contains("noise_scale")) {
		synthesisConfig.noiseScale = inferenceValue.value("noise_scale", 0.667f);
	    }
	    if (inferenceValue.contains("length_scale")) {
		synthesisConfig.lengthScale = inferenceValue.value("length_scale", 1.0f);
	    }
	    if (inferenceValue.contains("noise_w")) {
		synthesisConfig.noiseW = inferenceValue.value("noise_w", 0.8f);
	    }
	    if (inferenceValue.contains("phoneme_silence")) {
		// phoneme -> seconds of silence to add after
		synthesisConfig.phonemeSilenceSeconds.emplace();
		auto phonemeSilenceValue = inferenceValue["phoneme_silence"];
		for (auto &phonemeItem : phonemeSilenceValue.items()) {
		    std::string phonemeStr = phonemeItem.key();
		    if (!isSingleCodepoint(phonemeStr)) {
			//error("\"{}\" is not a single codepoint", phonemeStr);
			throw std::runtime_error(
			    "Phonemes must be one codepoint (phoneme silence)");
		    }
		    auto phoneme = getCodepoint(phonemeStr);
		    (*synthesisConfig.phonemeSilenceSeconds)[phoneme] =
			phonemeItem.value().get<float>();
		}
	    } // if phoneme_silence
	} // if inference
    }

// Copied from piper distribution.
    void parseModelConfig(json &configRoot, ModelConfig &modelConfig)
    {
	modelConfig.numSpeakers = configRoot["num_speakers"].get<SpeakerId>();
	if (configRoot.contains("speaker_id_map")) {
	    if (!modelConfig.speakerIdMap) {
		modelConfig.speakerIdMap.emplace();
	    }
	    auto speakerIdMapValue = configRoot["speaker_id_map"];
	    for (auto &speakerItem : speakerIdMapValue.items()) {
		std::string speakerName = speakerItem.key();
		(*modelConfig.speakerIdMap)[speakerName] =
		    speakerItem.value().get<SpeakerId>();
		//fprintf(stderr, "DLD| %lu speakerName %s\n",
		//speakerItem.value().get<SpeakerId>(), speakerName);
	    }
	}
    }

// Copied from piper distribution.
    void initialize(PiperConfig &config)
    {
	if (config.useESpeak) {
	    // Set up espeak-ng for calling espeak_TextToPhonemesWithTerminator
	    // See: https://github.com/rhasspy/espeak-ng
	    //debug("Initializing eSpeak");
	    int result = espeak_Initialize(AUDIO_OUTPUT_SYNCHRONOUS, 0, config.eSpeakDataPath.c_str(), 0);
	    if (result < 0) {
		throw std::runtime_error("Failed to initialize eSpeak-ng");
	    }
	}
	// Load onnx model for libtashkeel
	// https://github.com/mush42/libtashkeel/
	if (config.useTashkeel) {
	    //debug("Using libtashkeel for diacritization");
	    if (!config.tashkeelModelPath) {
		throw std::runtime_error("No path to libtashkeel model");
	    }
	    //debug("Loading libtashkeel model from {}",
	    //config.tashkeelModelPath.value());
	    config.tashkeelState = std::make_unique<tashkeel::State>();
	    tashkeel::tashkeel_load(config.tashkeelModelPath.value(),
				    *config.tashkeelState);
	}
	//info("Initialized piper");
    }

    void terminate(PiperConfig &config)
    {
	(void)cxxpiper_free_cbuf();
	(void)cxxpiper_free_voice_list();
	if (config.useESpeak) {
	    espeak_Terminate();
	}
	//piper::terminate(piperConfig);
    }

// Copied from piper distribution.
    void loadModel(std::string modelPath, ModelSession &session, bool useCuda)
    {
	//info("Loading onnx model from {}", modelPath);
	session.env = Ort::Env(OrtLoggingLevel::ORT_LOGGING_LEVEL_WARNING,
			       instanceName.c_str());
	session.env.DisableTelemetryEvents();
	if (useCuda) {
	    // Use CUDA provider
	    OrtCUDAProviderOptions cuda_options{};
	    cuda_options.cudnn_conv_algo_search = OrtCudnnConvAlgoSearchHeuristic;
	    session.options.AppendExecutionProvider_CUDA(cuda_options);
	}
	// Slows down performance by ~2x
	// session.options.SetIntraOpNumThreads(1);
	// Roughly doubles load time for no visible inference benefit
	// session.options.SetGraphOptimizationLevel(
	//     GraphOptimizationLevel::ORT_ENABLE_EXTENDED);
	session.options.SetGraphOptimizationLevel(
	    GraphOptimizationLevel::ORT_DISABLE_ALL);
	// Slows down performance very slightly
	// session.options.SetExecutionMode(ExecutionMode::ORT_PARALLEL);
	session.options.DisableCpuMemArena();
	session.options.DisableMemPattern();
	session.options.DisableProfiling();
	//auto startTime = std::chrono::steady_clock::now();
	auto modelPathStr = modelPath.c_str();
	session.onnx =Ort::Session(session.env, modelPathStr, session.options);
	//auto endTime = std::chrono::steady_clock::now();
	//info("Loaded onnx model in {} second(s)",
	//std::chrono::duration<double>(endTime - startTime).count());
    }

// Copied from piper distribution.
    void loadVoice(PiperConfig &config, std::string modelPath,
		   std::string modelConfigPath, Voice &voice,
		   std::optional<SpeakerId> &speakerId, bool useCuda)
    {
	//debug("loadVoice: Parsing voice config from {}", modelConfigPath);
	std::ifstream modelConfigFile(modelConfigPath);
	voice.configRoot = json::parse(modelConfigFile);
	parsePhonemizeConfig(voice.configRoot, voice.phonemizeConfig);
	parseSynthesisConfig(voice.configRoot, voice.synthesisConfig);
	parseModelConfig(voice.configRoot, voice.modelConfig);
	if (voice.modelConfig.numSpeakers > 1) {
	    if (speakerId) {
		DBG(DBG_MODNAME "Using Multi-speaker model, found configured speaker id %d", speakerId);
		voice.synthesisConfig.speakerId = speakerId;
	    } else {
		DBG(DBG_MODNAME "Using Multi-speaker model with default speaker id 0");
		voice.synthesisConfig.speakerId = 0;
	    }
	}
	DBG(DBG_MODNAME "Model contains %d speaker(s)", voice.modelConfig.numSpeakers);
	loadModel(modelPath, voice.session, useCuda);
    }

// Copied from piper distribution.
    void synthesize(std::vector<PhonemeId> &phonemeIds,
		    SynthesisConfig &synthesisConfig, ModelSession &session,
		    std::vector<int16_t> &audioBuffer, SynthesisResult &result)
    {
	//debug("Synthesizing audio for {} phoneme id(s)", phonemeIds.size());
	auto memoryInfo = Ort::MemoryInfo::CreateCpu(
	    OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault);
	// Allocate
	std::vector<int64_t> phonemeIdLengths{(int64_t)phonemeIds.size()};
	std::vector<float> scales{synthesisConfig.noiseScale,
				  synthesisConfig.lengthScale,
				  synthesisConfig.noiseW};
	std::vector<Ort::Value> inputTensors;
	std::vector<int64_t> phonemeIdsShape{1, (int64_t)phonemeIds.size()};
	inputTensors.push_back(Ort::Value::CreateTensor<int64_t>(
				   memoryInfo, phonemeIds.data(), phonemeIds.size(), phonemeIdsShape.data(),
				   phonemeIdsShape.size()));
	std::vector<int64_t> phomemeIdLengthsShape{(int64_t)phonemeIdLengths.size()};
	inputTensors.push_back(Ort::Value::CreateTensor<int64_t>(
				   memoryInfo, phonemeIdLengths.data(), phonemeIdLengths.size(),
				   phomemeIdLengthsShape.data(), phomemeIdLengthsShape.size()));
	std::vector<int64_t> scalesShape{(int64_t)scales.size()};
	inputTensors.push_back(
	    Ort::Value::CreateTensor<float>(memoryInfo, scales.data(), scales.size(),
					    scalesShape.data(), scalesShape.size()));
	// Add speaker id.
	// NOTE: These must be kept outside the "if" below to avoid being deallocated.
	std::vector<int64_t> speakerId{
	    (int64_t)synthesisConfig.speakerId.value_or(0)};
	std::vector<int64_t> speakerIdShape{(int64_t)speakerId.size()};
	if (synthesisConfig.speakerId) {
	    inputTensors.push_back(Ort::Value::CreateTensor<int64_t>(
				       memoryInfo, speakerId.data(), speakerId.size(), speakerIdShape.data(),
				       speakerIdShape.size()));
	}
	// From export_onnx.py
	std::array<const char *, 4> inputNames = {"input", "input_lengths", "scales",
						  "sid"};
	std::array<const char *, 1> outputNames = {"output"};
	// Infer
	auto startTime = std::chrono::steady_clock::now();
	auto outputTensors = session.onnx.Run(
	    Ort::RunOptions{nullptr}, inputNames.data(), inputTensors.data(),
	    inputTensors.size(), outputNames.data(), outputNames.size());
	auto endTime = std::chrono::steady_clock::now();
	if ((outputTensors.size() != 1) || (!outputTensors.front().IsTensor())) {
	    throw std::runtime_error("Invalid output tensors");
	}
	auto inferDuration = std::chrono::duration<double>(endTime - startTime);
	result.inferSeconds = inferDuration.count();
	const float *audio = outputTensors.front().GetTensorData<float>();
	auto audioShape =
	    outputTensors.front().GetTensorTypeAndShapeInfo().GetShape();
	int64_t audioCount = audioShape[audioShape.size() - 1];
	result.audioSeconds = (double)audioCount / (double)synthesisConfig.sampleRate;
	result.realTimeFactor = 0.0;
	if (result.audioSeconds > 0) {
	    result.realTimeFactor = result.inferSeconds / result.audioSeconds;
	}
	//fprintf(stderr, "Synthesized %f second(s) of audio in %f second(s)\n",
	//result.audioSeconds, result.inferSeconds);
	// Get max audio value for scaling
	float maxAudioValue = 0.01f;
	for (int64_t i = 0; i < audioCount; i++) {
	    float audioValue = abs(audio[i]);
	    if (audioValue > maxAudioValue) {
		maxAudioValue = audioValue;
	    }
	}
	// We know the size up front
	audioBuffer.reserve(audioCount);
	// Scale audio to fill range and convert to int16
	float audioScale = (MAX_WAV_VALUE / std::max(0.01f, maxAudioValue));
	for (int64_t i = 0; i < audioCount; i++) {
	    int16_t intAudioValue = static_cast<int16_t>(
		std::clamp(audio[i] * audioScale,
			   static_cast<float>(std::numeric_limits<int16_t>::min()),
			   static_cast<float>(std::numeric_limits<int16_t>::max())));
	    audioBuffer.push_back(intAudioValue);
	}
	// Clean up
	for (std::size_t i = 0; i < outputTensors.size(); i++) {
	    Ort::detail::OrtRelease(outputTensors[i].release());
	}
	for (std::size_t i = 0; i < inputTensors.size(); i++) {
	    Ort::detail::OrtRelease(inputTensors[i].release());
	}
    }

// Copied from piper . distribution.
    void textToAudio(PiperConfig &config, Voice &voice, std::string text,
		     std::vector<int16_t> &audioBuffer, SynthesisResult &result,
		     const std::function<void()> &audioCallback)
    {
	std::size_t sentenceSilenceSamples = 0;
	if (voice.synthesisConfig.sentenceSilenceSeconds > 0) {
	    sentenceSilenceSamples = (std::size_t)(
		voice.synthesisConfig.sentenceSilenceSeconds *
		voice.synthesisConfig.sampleRate * voice.synthesisConfig.channels);
	}
	if (config.useTashkeel) {
	    if (!config.tashkeelState) {
		throw std::runtime_error("Tashkeel model is not loaded");
	    }
	    //debug("Diacritizing text with libtashkeel: {}", text);
	    text = tashkeel::tashkeel_run(text, *config.tashkeelState);
	}
	// Phonemes for each sentence
	std::vector<std::vector<Phoneme>> phonemes;
	if (voice.phonemizeConfig.phonemeType == eSpeakPhonemes) {
	    // Use espeak-ng for phonemization
	    eSpeakPhonemeConfig eSpeakConfig;
	    eSpeakConfig.voice = voice.phonemizeConfig.eSpeak.voice;
	    phonemize_eSpeak(text, eSpeakConfig, phonemes);
	} else {
	    // Use UTF-8 codepoints as "phonemes"
	    CodepointsPhonemeConfig codepointsConfig;
	    phonemize_codepoints(text, codepointsConfig, phonemes);
	}
	// Synthesize each sentence independently.
	std::vector<PhonemeId> phonemeIds;
	std::map<Phoneme, std::size_t> missingPhonemes;
	for (auto phonemesIter = phonemes.begin(); phonemesIter != phonemes.end();
	     ++phonemesIter) {
	    std::vector<Phoneme> &sentencePhonemes = *phonemesIter;
	    std::vector<std::shared_ptr<std::vector<Phoneme>>> phrasePhonemes;
	    std::vector<SynthesisResult> phraseResults;
	    std::vector<size_t> phraseSilenceSamples;
	    // Use phoneme/id map from config
	    PhonemeIdConfig idConfig;
	    idConfig.phonemeIdMap =
		std::make_shared<PhonemeIdMap>(voice.phonemizeConfig.phonemeIdMap);
	    if (voice.synthesisConfig.phonemeSilenceSeconds) {
		// Split into phrases
		std::map<Phoneme, float> &phonemeSilenceSeconds =
		    *voice.synthesisConfig.phonemeSilenceSeconds;
		auto currentPhrasePhonemes = std::make_shared<std::vector<Phoneme>>();
		phrasePhonemes.push_back(currentPhrasePhonemes);
		for (auto sentencePhonemesIter = sentencePhonemes.begin();
		     sentencePhonemesIter != sentencePhonemes.end();
		     sentencePhonemesIter++) {
		    Phoneme &currentPhoneme = *sentencePhonemesIter;
		    currentPhrasePhonemes->push_back(currentPhoneme);
		    if (phonemeSilenceSeconds.count(currentPhoneme) > 0) {
			// Split at phrase boundary
			phraseSilenceSamples.push_back(
			    (std::size_t)(phonemeSilenceSeconds[currentPhoneme] *
					  voice.synthesisConfig.sampleRate *
					  voice.synthesisConfig.channels));
			currentPhrasePhonemes = std::make_shared<std::vector<Phoneme>>();
			phrasePhonemes.push_back(currentPhrasePhonemes);
		    }
		}
	    } else {
		// Use all phonemes
		phrasePhonemes.push_back(
		    std::make_shared<std::vector<Phoneme>>(sentencePhonemes));
	    }
	    // Ensure results/samples are the same size
	    while (phraseResults.size() < phrasePhonemes.size()) {
		phraseResults.emplace_back();
	    }
	    while (phraseSilenceSamples.size() < phrasePhonemes.size()) {
		phraseSilenceSamples.push_back(0);
	    }
	    // phonemes -> ids -> audio
	    for (size_t phraseIdx = 0; phraseIdx < phrasePhonemes.size(); phraseIdx++) {
		if (phrasePhonemes[phraseIdx]->size() <= 0) {
		    continue;
		}
		// phonemes -> ids
		phonemes_to_ids(*(phrasePhonemes[phraseIdx]), idConfig, phonemeIds,
				missingPhonemes);
		// ids -> audio
		synthesize(phonemeIds, voice.synthesisConfig, voice.session, audioBuffer,
			   phraseResults[phraseIdx]);
		// Add end of phrase silence
		for (std::size_t i = 0; i < phraseSilenceSamples[phraseIdx]; i++) {
		    audioBuffer.push_back(0);
		}
		result.audioSeconds += phraseResults[phraseIdx].audioSeconds;
		result.inferSeconds += phraseResults[phraseIdx].inferSeconds;
		phonemeIds.clear();
	    }
	    // Add end of sentence silence
	    if (sentenceSilenceSamples > 0) {
		for (std::size_t i = 0; i < sentenceSilenceSamples; i++) {
		    audioBuffer.push_back(0);
		}
	    }
	    if (audioCallback) {
		// Call back must copy audio since it is cleared afterwards.
		audioCallback();
		audioBuffer.clear();
	    }
	    phonemeIds.clear();
	}
	if (missingPhonemes.size() > 0) {
	    //warn("Missing {} phoneme(s) from phoneme/id map!",
	    //missingPhonemes.size());
	    for (auto phonemeCount : missingPhonemes) {
		std::string phonemeStr;
		utf8::append(phonemeCount.first, std::back_inserter(phonemeStr));
		//warn("Missing \"{}\" (\\u{:04X}): {} time(s)", phonemeStr,
		//(uint32_t)phonemeCount.first, phonemeCount.second);
	    }
	}
	if (result.audioSeconds > 0) {
	    result.realTimeFactor = result.inferSeconds / result.audioSeconds;
	}
    }

    RubberBandStretcher::Options initOptions()
    {
	RubberBandStretcher::Options options = 0;
	options |= RubberBandStretcher::OptionProcessRealTime;
	options |= RubberBandStretcher::OptionPhaseIndependent;
	switch (0) {
	case 0:
	    options |= RubberBandStretcher::OptionThreadingAuto;
	    break;
	case 1:
	    options |= RubberBandStretcher::OptionThreadingNever;
	    break;
	case 2:
	    options |= RubberBandStretcher::OptionThreadingAlways;
	    break;
	}
	options |= RubberBandStretcher::OptionTransientsCrisp;
	options |= RubberBandStretcher::OptionDetectorCompound;

	return options;
    }

    void internalizeSamples(vector<int16_t> ibuf, float** cbuf, const int channels, const int start, const int count)
    {
	//debug("Internalizing: ibuf.size: [], start: [], count: []", ibuf.size(), start, count);
	for (int c = 0; c < channels; ++c) {
	    for (int i = 0; i < count; ++i) {
		cbuf[c][i] = (float)(ibuf[start + (i * channels) + c]);
	    }
	    if (count < bs) {
		for (size_t i = count; i < bs; ++i) {
		    cbuf[c][i] = MIN_WAV_VALUE;
		}
	    }
	}
	++izCnt;
	izTot += count;
    }

    void externalizeSamples(float **cbuf, vector<int16_t>& ibuf, const float& gain, const int channels, const int start, const int count)
    {
	//debug("Externalizing: ibuf.size: [], start: [], count: []", ibuf.size(), start, count);
	for (int c = 0; c < channels; ++c) {
	    for (int i = 0; i < count; ++i) {
		float value = gain * cbuf[c][i];
		if (value < MIN_WAV_VALUE) value = MIN_WAV_VALUE;
		if (value > MAX_WAV_VALUE) value = MAX_WAV_VALUE;
		ibuf.push_back((int16_t)value);
	    }
	}
	++ezCnt;
	ezTot += count;
    }

    int adjust(const int samplerate, const int channels, double ratio, double pitchshift, float gain,
	       vector<int16_t>& audioBuffer, vector<int16_t>& sharedAudioBuffer)
    {
	DBG("adjust, ab size: %lu ratio: %f pitchshift %f gain %f", audioBuffer.size(), ratio, pitchshift, gain);
	const size_t abSize = audioBuffer.size();
	assert(abSize > 0);
	double frequencyshift = 1.0;
	if (pitchshift != 0.0) frequencyshift *= pow(2.0, pitchshift / 12.0);
	RubberBandStretcher ts(samplerate, channels, initOptions(), ratio, frequencyshift);
	ts.setExpectedInputDuration(audioBuffer.size());
	ts.setMaxProcessSize(bs);
	size_t countIn = 0;
	size_t countOut = 0;
	// "ib" stands for "input block", "ob" for "output block".
	size_t ibSize = bs;
	size_t ibLastFullIdx = abSize / bs;
	assert(ibLastFullIdx >= 0);
	for (size_t ibIdx = 0; ibIdx < ibLastFullIdx; ++ibIdx) {
	    assert(countIn % bs == 0);
	    ibSize = (ibIdx == ibLastFullIdx) ? abSize - countIn : bs;
	    assert(ibSize >= 0);
	    assert(ibSize > 0);
	    assert(ibSize <= bs);
	    internalizeSamples(audioBuffer, cbuf, channels, countIn, ibSize);
	    countIn += ibSize;
	    //DBG("About to ts.process, countIn is now %lu, final? %d", countIn, ibIdx == ibLastFullIdx);
	    ts.process(cbuf, ibSize, ibIdx == ibLastFullIdx);
	    ++prCnt;
	    prTot += ibSize;
	    int avail;
	    while ((avail = ts.available()) > 0) {
		// All data processed for this block.
		if (avail == -1) break;
		size_t obSize = bs;
		const size_t obLastFullIdx = avail / bs;
		// Do all the full blocks.
		for (size_t obIdx = 0; obIdx < obLastFullIdx; ++obIdx) {
		    assert(obSize <= bs);
		    ts.retrieve(cbuf, obSize);
		    (void)externalizeSamples(cbuf, sharedAudioBuffer, gain, channels, obIdx * bs, obSize);
		    countOut += obSize;
		}
		// do any remaining partial block.
		size_t remaining = avail - (obLastFullIdx * bs);
		assert(remaining >= 0);
		if (remaining > 0) {
		    //DBG("Retrieving remaining: %lu", remaining);
		    assert(remaining <= bs);
		    ts.retrieve(cbuf, remaining);
		    (void)externalizeSamples(cbuf, sharedAudioBuffer, gain, channels, obLastFullIdx* bs, remaining);
		    countOut += remaining;
		}
	    }
	    // All data processed, all done.
	    if (avail == -1) break;
	}
	return 0;
    }

    extern "C" {

	// OutputType and RunConfig are copied from piper and are , needed for synthesis
	// and populated from the arguments to piper's command line demo
	// program 'piper'.  We, as an SD oOM, populate this mostly from
	// the output module config file, cxxpiper.conf .  But some stuff
	// like speaker id may be updated/overridden per-query from SSIP data.
	enum OutputType { OUTPUT_FILE, OUTPUT_DIRECTORY, OUTPUT_STDOUT, OUTPUT_RAW };
	struct RunConfig {
	    // Path to .onnx voice file
	    filesystem::path modelPath;
	    // Path to JSON voice config file
	    filesystem::path modelConfigPath;
	    char* defaultVoiceName;
	    OutputType outputType = OUTPUT_STDOUT;
	    optional<filesystem::path> outputPath = filesystem::path(".");
	    // Numerical id of the default speaker (multi-speaker voices)
	    optional<piper::SpeakerId> speakerId;
	    optional<float> noiseScale;
	    // Speed of speaking (1 = normal, < 1 is faster, > 1 is slower)
	    optional<float> lengthScale;
	    optional<float> noiseW;
	    // Seconds of silence to add after each sentence
	    optional<float> sentenceSilenceSeconds;
	    // Path to espeak-ng data directory (default is next to piper executable)
	    optional<filesystem::path> eSpeakDataPath;
	    // Path to libtashkeel ort model
	    optional<filesystem::path> tashkeelModelPath;
	    bool jsonInput = false;
	    // Seconds of extra silence to insert after a single phoneme
	    optional<std::map<piper::Phoneme, float>> phonemeSilenceSeconds;
	    // true to use CUDA execution provider
	    bool useCuda = false;
	};
	static RunConfig runConfig;
	static piper::Voice voice;
	static SPDVoice **cxxpiper_voice_list = NULL;

	static int cxxpiper_alloc_cbuf();
	static SPDVoice **cxxpiper_allocate_voice_list();
	static void cxxpiper_free_voice_list();
	static void cxxpiper_handle_text(const char *);
	static void cxxpiper_handle_sound_icon(const char *);
	static void cxxpiper_set_language(char *);
	static void cxxpiper_set_synthesis_voice(char *voice_name);
	static void cxxpiper_set_voice_type(SPDVoiceType);
	static int cxxpiper_voice_name_to_speaker_id(const char *);

	MOD_OPTION_1_INT(UseCUDA)
	MOD_OPTION_1_STR(ModelPath)
	MOD_OPTION_1_STR(ConfigPath)
	MOD_OPTION_1_STR(SoundIconFolder)
	MOD_OPTION_1_INT(SoundIconVolume)
	// SIC: see below regarding NONE and ALL
	MOD_OPTION_1_STR(PunctSome)
	MOD_OPTION_1_STR(PunctMost)
	MOD_OPTION_1_STR(ESpeakNGDataDirPath)

	int module_load(void)
	{
	    INIT_SETTINGS_TABLES();
	    REGISTER_DEBUG();
	    MOD_OPTION_1_INT_REG(UseCUDA, 0);
	    MOD_OPTION_1_STR_REG(ModelPath, "");
	    MOD_OPTION_1_STR_REG(ConfigPath, "");
	    MOD_OPTION_1_STR_REG(SoundIconFolder, "/usr/share/sounds/sound-icons/");
	    MOD_OPTION_1_INT_REG(SoundIconVolume, 0);
	    // NB:  NONE and ALL are constant so no config.  NONE means omit as much as possible
	    // while ALL  means omit as little as possible.
	    // NB: Don't provide defaults, since empty string means fallback to SD's definitions.
	    MOD_OPTION_1_STR_REG(PunctSome, "");
	    MOD_OPTION_1_STR_REG(PunctMost, "");
	    MOD_OPTION_1_STR_REG(ESpeakNGDataDirPath, "/usr/share/espeak-ng-data/");
	    module_register_available_voices();
	    module_register_settings_voices();
	    return 0;
	}

	int module_init(char **status_info)
	{
	    module_audio_set_server();
	    module_audio_init(status_info);
	    runConfig.useCuda = UseCUDA;
	    runConfig.modelPath = filesystem::path(ModelPath);
	    runConfig.modelConfigPath = filesystem::path(ConfigPath);
	    runConfig.eSpeakDataPath = filesystem::path(ESpeakNGDataDirPath);
	    runConfig.outputPath = nullopt;
	    char *default_voice = module_getdefaultvoice();
	    runConfig.defaultVoiceName = default_voice;
	    runConfig.speakerId = cxxpiper_voice_name_to_speaker_id(runConfig.defaultVoiceName);
	    DBG("Default Voice is %s", runConfig.defaultVoiceName);
	    try {
		cxxpiper::loadVoice(piperConfig, runConfig.modelPath.string(), runConfig.modelConfigPath.string(),
				    voice, runConfig.speakerId, runConfig.useCuda);
		if (voice.phonemizeConfig.phonemeType == piper::eSpeakPhonemes) {
		    piperConfig.eSpeakDataPath = runConfig.eSpeakDataPath.value().string();
		}
		else {
		    piperConfig.useESpeak = false;
		}
		cxxpiper::initialize(piperConfig);
		cxxpiper_voice_list = cxxpiper_allocate_voice_list();
		cxxpiper_alloc_cbuf();
		*status_info = g_strdup(DBG_MODNAME " Initialized successfully.");
	    }
	    catch (const Ort::Exception& e) {
		DBG(DBG_MODNAME " Could not initialize,caught onnx runtime (model) exception: %s", e.what());
		*status_info = g_strdup(e.what());
		return -1;
	    }
	    catch (const json::parse_error& e) {
		DBG(DBG_MODNAME " Could not initialize,caught JSON exception: %s", e.what());
 		*status_info = g_strdup(e.what());
		return -1;
	    }
	    catch (const std::runtime_error& e) {
		DBG(DBG_MODNAME " Could not initialize,caught runtime_error exception: %s", e.what());
 		*status_info = g_strdup(e.what());
		return -1;
	    }
	    return 0;
	}

	SPDVoice **module_list_voices(void)
	{
	    return cxxpiper_voice_list;
	}

	void module_speak_sync(const char *data, size_t bytes, SPDMessageType msgtype)
	{
	    stop_requested = 0;
	    UPDATE_STRING_PARAMETER(voice.language, cxxpiper_set_language);
	    UPDATE_PARAMETER(voice_type, cxxpiper_set_voice_type);
	    UPDATE_STRING_PARAMETER(voice.name, cxxpiper_set_synthesis_voice);
	    module_speak_ok();
	    switch (msgtype) {
	    case SPD_MSGTYPE_CHAR:
	    case SPD_MSGTYPE_KEY:
	    case SPD_MSGTYPE_SPELL:
	    case SPD_MSGTYPE_TEXT:
		cxxpiper_handle_text(data);
		break;
	    case SPD_MSGTYPE_SOUND_ICON:
		cxxpiper_handle_sound_icon(data);
		break;
	    }
	}

	int module_stop(void)
	{
	    stop_requested = 1;
	    return 0;
	}

	size_t module_pause(void)
	{
	    stop_requested = 1;
	    return 0;
	}

	int module_close()
	{
	    if (piperConfig.useESpeak) espeak_Terminate();
	    (void)cxxpiper::terminate(piperConfig);
	    return 0;
	}

	static int cxxpiper_alloc_cbuf()
	{
	    const int channels = voice.synthesisConfig.channels;
	    cbuf = g_new0(float *, channels);
	    if ( ! cbuf) {
		throw std::runtime_error("Failure allocating dynamic memory for adjustment buffer");
	    }
	    for (size_t c = 0; c < channels; ++c) {
		cbuf[c] = g_new0(float, bs);
		if ( ! cbuf[c]) {
		    throw std::runtime_error("Failure allocating dynamic memory for adjustment buffer values");
		}
	    }
	    return 0;
	}

	static int cxxpiper_free_cbuf()
	{
	    const int channels = voice.synthesisConfig.channels;
	    for (size_t c = 0; c < channels; ++c) g_free(cbuf[c]);
	    g_free(cbuf);
	    return 0;
	}

	static SPDVoice **cxxpiper_allocate_voice_list()
	{
	    SPDVoice **result = NULL;
	    int num_spkrs = voice.configRoot["num_speakers"];
	    DBG("Model config reports num spkrs %d", num_spkrs);
	    // result consistes of an array of SPDVoice* addresses,
	    // one address per voice and one more NULL address as
	    // delimiter.  The non-NULL elements point to SPDVoice
	    // structs and we'll dynamically allocate memory for them,
	    // below.  First the "root" array:
	    result = g_new0(SPDVoice *, num_spkrs + 1);
	    if ( ! result) {
		throw std::runtime_error("Failure allocating dynamic memory for voice list");
	    }
	    if (num_spkrs > 1) {
		if ( ! voice.configRoot.contains("speaker_id_map")) {
		    //warn("ERROR: Using multispeaker model but could not find speaker_id_map in json model configureation file");
		    //throw std::runtime_error("Using multispeaker model but could not find speaker_id_map in json model configureation file");
		}
		auto speakerIdMapValue = voice.configRoot["speaker_id_map"];
		DBG("Model config speaker_id_map has %lu entries", speakerIdMapValue.size());
		SPDVoice **reg_voices = module_list_registered_voices();
		int i = 0;
		for (auto &speakerItem : speakerIdMapValue.items()) {
		    std::string spkr_name = speakerItem.key();
		    int spkr_id = speakerItem.value().get<SpeakerId>();
		    // Dynamically allocate an SPDVoice and point to it from result[[], the "root" array.
		    SPDVoice *v = g_new0(SPDVoice, 1);
		    v->name = g_strdup_printf("%s~%d~%s", runConfig.modelPath.stem().string().c_str(), spkr_id, spkr_name.c_str());
		    v->language = g_strdup(voice.configRoot["language"]["code"].get<std::string>().c_str());
		    char *var_str = NULL;
		    for (int j = 0; reg_voices && reg_voices[j]; ++j) {
			SPDVoice* r_v = reg_voices[j];
			if (strncasecmp(v->name, r_v->name, strlen(v->name)) == 0) {
			    var_str = r_v->variant;
			    DBG("matched configured voice type %s with voice %s", var_str, r_v->name);
			    break;
			}
		    }
		    if (strncasecmp(v->name, runConfig.defaultVoiceName, strlen(v->name)) == 0) {
			v->variant = g_strdup((var_str == NULL) ? "(default)" : strcat(var_str, " (default)"));
		    }
		    else {
			v->variant = g_strdup((var_str == NULL) ? "" : var_str);
		    }
		    result[i] = v;
		    ++i;
		}
	    }
	    else {
		// Theoretically, there is no need to copy voice type string into varient b/c there's only one voice
		// and it will be used.  However, voice list should show the type string, assuming proper AddVoice configuration.
		DBG("Running with single-speaker model");
		SPDVoice *v = g_new0(SPDVoice, 1);
		v->name = g_strdup(runConfig.modelPath.stem().string().c_str());
		v->language = g_strdup(voice.configRoot["language"]["code"].get<std::string>().c_str());
		v->variant = g_strdup("");
		result[0] = v;
	    }
	    // Delimit the "root array.
	    result[num_spkrs] = NULL;
	    return result;
	}

	static void cxxpiper_free_voice_list()
	{
#ifdef ESPEAK_NG_INCLUDE
	    free(espeak_variants_array);
#endif
	    if (cxxpiper_voice_list != NULL) {
		for (int i = 0; cxxpiper_voice_list[i] != NULL; i++) {
		    g_free(cxxpiper_voice_list[i]->name);
		    g_free(cxxpiper_voice_list[i]->language);
		    g_free(cxxpiper_voice_list[i]->variant);
		    g_free(cxxpiper_voice_list[i]);
		}
		g_free(cxxpiper_voice_list);
		cxxpiper_voice_list = NULL;
	    }
	}

	static char *cxxpiper_voice_enum_to_str(SPDVoiceType voice_type)
	{
	    char *voicename;
	    switch (voice_type) {
	    case SPD_MALE1:
		voicename = g_strdup("MALE1");
		break;
	    case SPD_MALE2:
		voicename = g_strdup("MALE2");
		break;
	    case SPD_MALE3:
		voicename = g_strdup("MALE3");
		break;
	    case SPD_FEMALE1:
		voicename = g_strdup("FEMALE1");
		break;
	    case SPD_FEMALE2:
		voicename = g_strdup("FEMALE2");
		break;
	    case SPD_FEMALE3:
		voicename = g_strdup("FEMALE3");
		break;
	    case SPD_CHILD_MALE:
		voicename = g_strdup("CHILD_MALE");
		break;
	    case SPD_CHILD_FEMALE:
		voicename = g_strdup("CHILD_FEMALE");
		break;
	    case SPD_UNSPECIFIED:
		// SIC:  fall through to default
	    default:
		voicename = NULL;
		break;
	    }
	    return voicename;
	}

	static const char *cxxpiper_search_for_sound_icon(const char *icon_name)
	{
	    char *fn = NULL;
	    if (strlen(SoundIconFolder) == 0) return fn;
	    GString *filename = g_string_new(SoundIconFolder);
	    filename = g_string_append(filename, icon_name);
	    if (g_file_test(filename->str, G_FILE_TEST_EXISTS)) fn = filename->str;
	    /* if the file was found, the pointer *fn  points to the character data
	       of the string filename. In this situation the string filename must be
	       freed but its character data must be preserved.
	       If the file is not found, the pointer *fn contains NULL. In this
	       situation the string filename must be freed, including its character
	       data.
	    */
	    return g_string_free(filename, (fn == NULL));
	}

	void cxxpiper_stretch_and_copy(const int samplerate, const int channels, vector<int16_t>& audioBuffer, vector<int16_t>& sharedAudioBuffer)
	{
	    // In rubberband stretcher units, 1.0 is no duration change.
	    // Map -100 .. 100 onto 0.0 .. 2.0 .
	    double ratio = (((double)msg_settings.rate) + 100.0) / 100.0;
	    // Pitch is converted to frequency internally, as per 'piper'
	    // command line utility.  The 'adjust' function uses the same
	    // conversion.  I think -10.0 to 10.0
	    // provides a usable range plus a bit of margin.
	    // .  Map -100 .. 100 onto -10.0 .. 10.0
	    // Also clamp input values otherwise we will crash!
	    double mS = ((double)msg_settings.pitch);
	    if (mS < -100.0) mS = -100.0;
	    if (mS > 100.0) mS = 100.0;
	    double pitchshift = mS / 10.0;
	    //  In rubberband stretcher units, 1.0 is no gain/attenuation.
	    // Map -100 .. 100 onto 0.0 .. 1.0
	    float gain = (((float)msg_settings.volume) + 100.0f) / 200.0f;
	    (void)adjust(samplerate, channels, ratio, pitchshift, gain, audioBuffer, sharedAudioBuffer);
	}

	static void cxxpiper_handle_text(const char *data)
	{
	    DBG("Input data before strip XML: %s", data);
	    cmdInp = (char *) module_strip_ssml(data);
	    DBG("Input after strip XML: %s", cmdInp);
	    mutex mutAudio;
	    condition_variable cvAudio;
	    bool audioReady = false;
	    cbCnt = 0;
	    cbTot = 0;
	    prCnt = 0;
	    prTot = 0;
	    izCnt =0;
	    izTot =0;
	    ezCnt =0;
	    ezTot =0;
	    runConfig.lengthScale = msg_settings.rate / 100.0;
	    vector<int16_t> audioBuffer;
	    vector<int16_t> sharedAudioBuffer;
	    piper::SynthesisResult result;
	    auto audioCallback = [&audioBuffer, &sharedAudioBuffer, &mutAudio,
				  &cvAudio, &audioReady]()
	    {
		unique_lock lockAudio(mutAudio);
#if 0
		copy(audioBuffer.begin(), audioBuffer.end(), back_inserter(sharedAudioBuffer));
#else
		cxxpiper_stretch_and_copy(voice.synthesisConfig.sampleRate, voice.synthesisConfig.channels, audioBuffer, sharedAudioBuffer);
#endif
		audioReady = true;
		++cbCnt;
		cbTot += audioBuffer.size();	    //cerr << "CVaudio notivy 1" << endl;
		cvAudio.notify_one();
	    };
	    DBG("Sending begin event");
	    module_report_event_begin();
	    (void)cxxpiper::textToAudio(piperConfig, voice, cmdInp,
					audioBuffer, result, audioCallback);
	    DBG("Did synthesis, ab size is %lu sab size is %lu",
		audioBuffer.size(), sharedAudioBuffer.size());
	    AudioFormat format = SPD_AUDIO_LE;
	    AudioTrack track;
	    track.bits = voice.synthesisConfig.sampleWidth * 8;
	    track.num_samples = sharedAudioBuffer.size();
	    track.samples = &sharedAudioBuffer[0]; // outbuf;
	    track.num_channels = voice.synthesisConfig.channels;
	    track.sample_rate = voice.synthesisConfig.sampleRate;
	    DBG("callback called %lu times, total: %lu", cbCnt, cbTot);
	    DBG("process called %lu times, total: %lu", prCnt, prTot);
	    DBG("internalize called %lu times, total: %lu", izCnt, izTot);
	    DBG("externalize called %lu times, total: %lu", ezCnt, ezTot);
	    DBG("sending track to audio output");
	    module_tts_output_server(&track, format);
	    DBG("Sending end event");
	    module_report_event_end();
	}

	static void cxxpiper_handle_sound_icon(const char *icon_name)
	{
	    int fallback_to_speech = 0;
	    const char *icon_path = cxxpiper_search_for_sound_icon(icon_name);
	    if (icon_path != NULL) {
		module_report_event_begin();
		(void)module_play_file(icon_path);
		module_report_icon(icon_path);
		module_report_event_end();
	    }
	    else {
		fallback_to_speech = 1;
	    }
	    if (fallback_to_speech) {

	{
	    int i = 0;
	    int index = -1;
	    char *tstr = cxxpiper_voice_enum_to_str(voice_type);

	    DBG("%s, lang=%s, voice_type=%d, name=%s",
		__FUNCTION__, lang, (int)voice_type, name ? name : "");
	    // -1 is explicitly "unspecified", 0 is plain old undefined.
	    if (voice_type > 0) {
		// The idea is that if the user submits a query with a
		// type, we should honor that type even if we can't
		// find a language match.  We prefer language and type
		// to match, of course.  Name is insignificant when type
		// is included except that if we find no type match at
		// all we fall back to language/name matching, as if
		// no type were given in the query.
		SPDVoice **vs = module_list_registered_voices();
		SPDVoice *first_close_match = NULL;
		for (int i = 0; vs && vs[i]; ++i) {
		    SPDVoice* v = vs[i];
		    DBG("typematch voice name: %s lang: %s variant: %s", v->name, v->language, v->variant);
		    int type_match_p = strncasecmp(tstr, v->variant, strlen(tstr));
		    if (type_match_p == 0 &&
			strncasecmp(lang, v->language, strlen(lang)) == 0) {
			DBG("strong match on language and type, $voice is now %s.", v->name);
			voice.synthesisConfig.speakerId = cxxpiper_voice_name_to_speaker_id(v->name);
			runConfig.speakerId = voice.synthesisConfig.speakerId;
			return;
		    }
		    else if (type_match_p) {
			first_close_match = v;
		    }
		}
		if (first_close_match) {
		    DBG("Weak match on type only!  Assuming configuration is sane, new voice is %s", first_close_match->name);
		    voice.synthesisConfig.speakerId = cxxpiper_voice_name_to_speaker_id(first_close_match->name);
		    runConfig.speakerId = voice.synthesisConfig.speakerId;
		    return;
		}
	    }
	    SPDVoice **lst = cxxpiper_voice_list;
	    if (name && *name) {
		for (i = 0; lst[i]; ++i) {
		    if (!strcasecmp(lst[i]->name, name)) {
			voice.synthesisConfig.speakerId = cxxpiper_voice_name_to_speaker_id(lst[i]->name);
			runConfig.speakerId = voice.synthesisConfig.speakerId;
			index = i;
			break;
		    }
		}
	    }
	    if ((index == -1) && lang) {
		char *langbase;	// requested base language + '-'
		char *dash = strchr(lang, '-');
		if (dash)
		    langbase = g_strndup(lang, dash-lang+1);
		else
		    langbase = g_strdup_printf("%s-", lang);
		for (i = 0; lst[i]; ++i) {
		    if (!strcasecmp(lst[i]->language, lang)) {
			DBG("strong match on language new voice is %s", lst[i]->name);
			voice.synthesisConfig.speakerId = cxxpiper_voice_name_to_speaker_id(lst[i]->name);
			runConfig.speakerId = voice.synthesisConfig.speakerId;
			index = i;
			break;
		    }
		    if (index == -1) {
			/* Try base language matching as fallback */
			if (!strncasecmp(lst[i]->language, langbase, strlen(langbase))) {
			    DBG("Base language match, new voice is %s", lst[i]->name);
			    voice.synthesisConfig.speakerId = cxxpiper_voice_name_to_speaker_id(lst[i]->name);
			    runConfig.speakerId = voice.synthesisConfig.speakerId;
			    index = i;
			}
		    }
		}
		g_free(langbase);
	    }
	    if (index == -1) { // no matching voice: choose the first available voice
		if (!cxxpiper_voice_list[0])
		    voice.synthesisConfig.speakerId = cxxpiper_voice_name_to_speaker_id(cxxpiper_voice_list[0]->name);
		runConfig.speakerId = voice.synthesisConfig.speakerId;
		index = 0;
		return;
	    }
	}

	static void cxxpiper_set_voice_type(SPDVoiceType voice_type)
	{
	    const char *vt_str = cxxpiper_voice_enum_to_str(voice_type);
	    // NB: spd-say -t female1 twice in a row results in voice_type 4 in the first request, then -1 in the second!!
	    SPDVoice **vs = module_list_registered_voices();
	    if (vs != NULL) {
		if (vt_str == NULL) {
		    MSG(3, "Warning:  voice type from server is NULL!!  Should not happen.");
		    return;
		}
		for (int i = 0; vs[i] != NULL; i++) {
		    const char *vari = vs[i]->variant;
		    if (strlen(vari) <= strlen(vt_str) &&
			strncasecmp(vt_str, vari, strlen(vt_str)) == 0) {
			int spkr_id = cxxpiper_voice_name_to_speaker_id(vs[i]->name);
			voice.synthesisConfig.speakerId = spkr_id;
			runConfig.speakerId = spkr_id;
			return;
		    }
		}
		MSG(3, "Warning:  No definition of type %s, check 'AddVoice' directives in cxxpiper.conf file.", vt_str);
	    }
	    else {
		MSG(3, "Warning:  no configuredc voice types, please use AddVoice in cxxpiper.conf to define voice types needed at runtime.");
	    }
	}

	static void cxxpiper_set_language(char *lang)
	{
	    cxxpiper_set_language_and_voice(lang, msg_settings.voice_type, NULL);
	}

	static void cxxpiper_set_synthesis_voice(char *voice_name)
	{
	    if (voice_name == NULL) {
		return;
	    }
	    int spkr_id = -1;
	    if (msg_settings.voice.name != NULL) {
		spkr_id = cxxpiper_voice_name_to_speaker_id(msg_settings.voice.name);
	    }
	    else {
		spkr_id = cxxpiper_voice_name_to_speaker_id(runConfig.defaultVoiceName); // cxxpiper_voice_name_to_speaker_id(default_voice);
	    }
	    voice.synthesisConfig.speakerId = spkr_id;
	    runConfig.speakerId = voice.synthesisConfig.speakerId;
	    return;
	}

	static int cxxpiper_voice_name_to_speaker_id(const char *voice_name)
	{
	    // The form of a voice name is:
	    // <model-name>~{}<speaker-id>~<speaker-name>
	    int spkr_id;
	    int num_matches = sscanf(voice_name, "%*[^~]~%d~%*s", &spkr_id);
	    if (num_matches != 1) return -1;
	    return spkr_id;
	}

    } // extern "C"
} // namespace cxxpiperb

		MSG(3, "Warning:  Speaking sound icon name, %s, as a fallback, since audio file can not be found.", icon_name);
		cxxpiper_handle_text(icon_name);
	    }
	}


/*
 * kali.cpp - Speech Dispatcher backend for Kali
 *
 * Copyright (C)2016 Hypra
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * $Id: kali.c,v 1.59 2008-06-09 10:38:02 hanke Exp $
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <kali/Kali/kali.h>

#include <speechd_types.h>

#include "module_utils.h"

#define MODULE_NAME     "kali"
#define MODULE_VERSION  "0.0"
#define DEBUG_MODULE 1
DECLARE_DEBUG();

/* Thread and process control */
static int kali_speaking = 0;

static char *buf;

signed int kali_volume = 0;
SPDVoice **kali_voice_list = NULL;

/* Internal functions prototypes */
static void kali_set_rate(signed int rate);
static void kali_set_pitch(signed int pitch);
static void kali_set_volume(signed int volume);

static void kali_set_voice(char *voice);

static SPDVoice **kali_get_voices();

static int kali_stop = 0;

MOD_OPTION_1_INT(KaliMaxChunkLength);
MOD_OPTION_1_STR(KaliDelimiters);
MOD_OPTION_1_INT(KaliNormalRate);
MOD_OPTION_1_INT(KaliNormalVolume);
MOD_OPTION_1_INT(KaliNormalPitch);
MOD_OPTION_1_STR(KaliVoiceParameters);
MOD_OPTION_1_INT(KaliExpandAbbreviations);
MOD_OPTION_1_INT(KaliMulticasesWords);

/* Public functions */

int module_load(void)
{
	INIT_SETTINGS_TABLES();

	REGISTER_DEBUG();

	MOD_OPTION_1_INT_REG(KaliMaxChunkLength, 4999);
	MOD_OPTION_1_STR_REG(KaliDelimiters, ".");
	MOD_OPTION_1_INT_REG(KaliNormalRate, 70);
	MOD_OPTION_1_INT_REG(KaliNormalVolume, 10);
	MOD_OPTION_1_INT_REG(KaliNormalPitch, 6);
	MOD_OPTION_1_STR_REG(KaliVoiceParameters, "Patrick");
	MOD_OPTION_1_INT_REG(KaliExpandAbbreviations, 1);
	MOD_OPTION_1_INT_REG(KaliMulticasesWords, 1);

	return 0;
}

int module_init(char **status_info)
{
	DBG("Module init");

	module_audio_set_server();

	/* Init kali and register a new voice */
	initGlobal();
	initParle();
	initTrans();
	initAnalyse();
	initKali();
	SetSortieSonMultiKaliStd(0, false);	//sound output
	SetSortieBufMultiKaliStd(0, true);	//Buffer output
	SetDebitKali(KaliNormalRate);
	SetVolumeKali(KaliNormalVolume);
	SetHauteurKali(KaliNormalPitch);
	kali_voice_list = kali_get_voices();
	if (!kali_voice_list) {
		*status_info = g_strdup("Kali has no voice installed");
		return -1;
	}
	kali_set_voice(KaliVoiceParameters);

	DBG("KaliMaxChunkLength = %d\n", KaliMaxChunkLength);
	DBG("KaliDelimiters = %s\n", KaliDelimiters);
	DBG("KaliExpandAbbreviations = %d\n", KaliExpandAbbreviations);
	DBG("KaliMulticasesWords = %d\n", KaliMulticasesWords);

	kali_speaking = 0;

	buf = (char *)g_malloc((KaliMaxChunkLength + 1) * sizeof(char));

	*status_info = g_strdup("Kali initialized successfully.");

	return 0;
}

SPDVoice **module_list_voices(void)
{
	return kali_voice_list;
}

void module_speak_sync(const char * data, size_t len, SPDMessageType msgtype)
{
	DBG("Requested data: |%s|\n", data);

	if (kali_speaking) {
		module_speak_error();
		DBG("Speaking when requested to write");
		return;
	}

	kali_speaking = 1;
	kali_stop = 0;

	AudioTrack track;
#if defined(BYTE_ORDER) && (BYTE_ORDER == BIG_ENDIAN)
	AudioFormat format = SPD_AUDIO_BE;
#else
	AudioFormat format = SPD_AUDIO_LE;
#endif
	const AudioTrackKali *wav;
	char *kali_message;

	kali_message = module_strip_ssml(data);
	if (KaliMulticasesWords)
		kali_message = module_multicases_string(kali_message);

	module_speak_ok();

	/* Setting voice */
	UPDATE_PARAMETER(rate, kali_set_rate);
	UPDATE_PARAMETER(volume, kali_set_volume);
	UPDATE_PARAMETER(pitch, kali_set_pitch);
	UPDATE_PARAMETER(punctuation_mode, kali_set_punctuation_mode);
	UPDATE_STRING_PARAMETER(voice.name, kali_set_voice);
	kali_set_voice(msg_settings.voice.name);

	unsigned int pos;
	int bytes;

	pos = 0;
	module_report_event_begin();
	while (1) {
		/* Process server events in case we were told to stop in between */
		module_process(STDIN_FILENO, 0);

		if (kali_stop) {
			DBG("Stop in child, terminating");
			module_report_event_stop();
			break;
		}

		bytes =
		    module_get_message_part(kali_message, buf, &pos,
					    KaliMaxChunkLength,
					    KaliDelimiters);

		if (bytes < 0) {
			DBG("End of message");
			module_report_event_end();
			break;
		}

		if (bytes == 0) {
			DBG("No data");
			module_report_event_end();
			break;
		}

		buf[bytes] = 0;
		DBG("Returned %d bytes from get_part\n", bytes);
		DBG("Text to synthesize is '%s'\n", buf);

		DBG("Trying to synthesize text");
		MessageKali((unsigned char *)buf);
		DBG("Waiting for synthesis");
		while (QueryIndexKali() > 0)
			usleep(1000);
		wav =
		    (const AudioTrackKali *)
		    GetBufMultiKaliStd(0);
		DBG("Got buffer");

		if (wav == NULL) {
			DBG("Stop in child, terminating");
			module_report_event_stop();
			break;
		}

		track.num_samples = wav->num_samples;
		track.num_channels = wav->num_channels;
		track.sample_rate = wav->sample_rate;
		track.bits = wav->bits;
		track.samples = (signed short *)wav->samples;

		DBG("Got %d samples", track.num_samples);
		if (track.samples != NULL) {
			DBG("Playing part of the message");
			module_tts_output_server(&track, format);
		}
	}
	g_free(kali_message);
	kali_speaking = 0;
}

int module_stop(void)
{
	DBG("kali: stop()\n");

	kali_stop = 1;

	return 0;
}

size_t module_pause(void)
{
	DBG("pause requested\n");
	if (kali_speaking) {
		DBG("Kali doesn't support pause, stopping\n");

		module_stop();

		return -1;
	} else {
		return 0;
	}
}

int module_close(void)
{

	DBG("kali: close()\n");

	DBG("Stopping speech");
	if (kali_speaking) {
		module_stop();
	}

	quitteAnalyse();
	quitteTrans();
	quitteParle();
	quitteGlobal();

	return 0;
}

/* Internal functions */

static void kali_set_rate(signed int rate)
{
	short speed;

	assert(rate >= -100 && rate <= +100);
	if (rate < 0)
	  speed = GetDebitDefautKaliStd() - rate * (GetDebitMinKaliStd() - GetDebitDefautKaliStd()) / 100;
	else
	  speed = GetDebitDefautKaliStd() + rate * (GetDebitMaxKaliStd() - GetDebitDefautKaliStd()) / 100;
	SetDebitKali(speed);
}

static void kali_set_volume(signed int volume)
{
	short vol;

	assert(volume >= -100 && volume <= +100);
	if (volume < 0)
	  vol = GetVolumeDefautKaliStd() - volume * (GetVolumeMinKaliStd() - GetVolumeDefautKaliStd()) / 100;
	else
	  vol = GetVolumeDefautKaliStd() + volume * (GetVolumeMaxKaliStd() - GetVolumeDefautKaliStd()) / 100;
	SetVolumeKali(vol);
}

static void kali_set_pitch(signed int pitch)
{
	short ptch;

	assert(pitch >= -100 && pitch <= +100);
	if (pitch < 0)
	  ptch = GetHauteurDefautKaliStd() - pitch * (GetHauteurMinKaliStd() - GetHauteurDefautKaliStd()) / 100;
	else
	  ptch = GetHauteurDefautKaliStd() + pitch * (GetHauteurMaxKaliStd() - GetHauteurDefautKaliStd()) / 100;
	SetHauteurKali(ptch);
}

void kali_set_punctuation_mode(SPDPunctuation punct)
{
	switch (punct) {
	case SPD_PUNCT_NONE:
		if (KaliExpandAbbreviations)
			SetModeLectureKali(0);
		else
			SetModeLectureKali(1);
		break;
	case SPD_PUNCT_SOME:
		SetModeLectureKali(2);
		break;
	case SPD_PUNCT_MOST:
		/* XXX approximation */
		SetModeLectureKali(2);
		break;
	case SPD_PUNCT_ALL:
		SetModeLectureKali(3);
		break;
	default:
		break;
	}
}

static void kali_set_voice(char *voice)
{
	short i, nlang;
	char *v = voice;

	if (v == NULL)
		v = KaliVoiceParameters;

	for (i = 0; kali_voice_list[i] != NULL; i++) {
		if (strcasecmp(kali_voice_list[i]->name, v) == 0) {
			nlang = GetNLangueVoixKaliStd(i + 1);
			SetLangueKali(nlang);
			SetVoixKali(i + 1);
			break;
		}
	}
}

static SPDVoice **kali_get_voices()
{
	short i;
	SPDVoice **result = NULL;
	short num_voices;
	static char voice[128];
	short nlang;
	static char language[128];

	num_voices = GetNbVoixKali();
	if (num_voices == 0)
		return NULL;
	DBG("Kali: %d voices total.", num_voices);
	result = g_new0(SPDVoice *, num_voices+1);

	for (i = 0; i < num_voices; i++) {
		result[i] = g_new0(SPDVoice, 1);
		GetNomVoixKali(i + 1, voice);
		if (!memchr(voice, '\0', sizeof(voice))) {
			voice[sizeof(voice)-1] = '\0';
			MSG(1, "Kali: voice name overflow! truncated to %s but possibly overflowed memory...", voice);
		}
		result[i]->name = (char *)g_strdup(voice);

		nlang = GetNLangueVoixKaliStd(i + 1);
		GetNomLangueKali(nlang, language);
		if (!memchr(language, '\0', sizeof(language))) {
			language[sizeof(language)-1] = '\0';
			MSG(1, "Kali: language name overflow! truncated to %s but possibly overflowed memory...", language);
		}
		result[i]->language = (char *)g_strdup(language);

		result[i]->variant = NULL;
	}
	result[i] = NULL;

	return result;
}

static void kali_set_punctuation_mode(SPDPunctuation punct);


/*
 * kali_Kali_shim.c - Shim for Kali
 * to be able to build the Kali module without the Kali SDK.
 *
 * Copyright (C) 2018 Hypra
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <kali/Kali/kali.h>
#include <assert.h>

extern "C" {

bool initKali(void) {
	assert(0);
}

const AudioTrackKali *GetBufMultiKaliStd(short nK) {
	assert(0);
}

void SetSortieBufMultiKaliStd(short nK, bool sortieBuf) {
	assert(0);
}

void SetSortieSonMultiKaliStd(short nK, bool sortieSon) {
	assert(0);
}

short GetDebitDefautKaliStd(void) {
	assert(0);
}

short GetDebitMinKaliStd(void) {
	assert(0);
}

short GetDebitMaxKaliStd(void) {
	assert(0);
}

void SetDebitKali(short debit) {
	assert(0);
}

short GetVolumeDefautKaliStd(void) {
	assert(0);
}

short GetVolumeMinKaliStd(void) {
	assert(0);
}

short GetVolumeMaxKaliStd(void) {
	assert(0);
}

void SetVolumeKali(short volume) {
	assert(0);
}

short GetHauteurDefautKaliStd(void) {
	assert(0);
}

short GetHauteurMinKaliStd(void) {
	assert(0);
}

	assert(0);
}

void SetModeLectureKali(short modeLecture) {
	assert(0);
}

short GetNLangueVoixKaliStd(short nVoix) {
	assert(0);
}

char *GetNomLangueKali(short nLangue, char *nomLangue) {
	assert(0);
}

void SetLangueKali(short nLangue) {
	assert(0);
}

short GetNbVoixKali(void) {
	assert(0);
}

char *GetNomVoixKali(short nVoix, char *nomVoix) {
	assert(0);
}

void SetVoixKali(short nVoix) {
	assert(0);
}

short MessageKali(unsigned char* texte) {
	assert(0);
}

short QueryIndexKali(void) {
	assert(0);
}

}

short GetHauteurMaxKaliStd(void) {
	assert(0);
}


#ifndef DBHC_ALGORITHM_H
#define DBHC_ALGORITHM_H

#include <algorithm>
#include <array>
#include <cassert>
#include <iostream>
#include <map>
#include <deque>
#include <list>
#include <forward_list>
#include <numeric>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#define DBG_ASSERT(x) assert(x)

namespace dbhc {

  template<typename T>
  int num_elems(std::vector<std::vector<T>>& v) {
    int i = 0;
    for (auto t : v) {
      i += t.size();
    }
    return i;
  }

  template<typename I>
  std::vector<unsigned> inds(const std::vector& elems) {
    std::vector<unsigned> is(elems.size());
    std::iota(begin(is), end(is), 0);
    return is;
  }

  template<typename T>
  void concat(T& extend, const T& add) {
    extend.insert(end(extend), begin(add), end(add));
  }

  template<typename E, typename T>
  void remove(E e, T& t) {
    t.erase(std::remove(begin(t), end(t), e), end(t));
  }

  template<typename E, typename T>
  bool elem(E e, std::vector<T> t) {
    return std::find(begin(t), end(t), e) != end(t);
  }

  template<typename T>
  bool elem(T e, const std::unordered_set<T>& t) {
    return t.find(e) != end(t);
  }

  template<typename T>
  bool elem(T e, const std::deque<T>& t) {
    return std::find(begin(t), end(t), e) != end(t);
  }

  template<typename T>
  bool elem(T e, const std::set<T>& t) {
    return t.find(e) != end(t);
  }

  template<typename InputIt, typename OutputIt, typename F>
  OutputIt apply_between(InputIt s, InputIt e, OutputIt r, F f) {
    while (s != (e - 1)) {
      *r = f(*s, *(s + 1));
      ++r;
      ++s;
    }
    return r;
  }

  template<typename InputIt, typename F>
  void apply_between(InputIt s, InputIt e, F f) {
    if (s == e) { return; }
    while (s != (e - 1)) {
      f(*s, *(s + 1));
      ++s;
    }
  }

  template<typename InputIt, typename F>
  bool all_between(InputIt s, InputIt e, F f) {
    while (s != (e - 1)) {
      if (!f(*s, *(s + 1))) { return false; };
      ++s;
    }
    return true;
  }

  template<typename InputIt, typename F>
  bool any_between(InputIt s, InputIt e, F f) {
    while (s != (e - 1)) {
      if (f(*s, *(s + 1))) { return true; };
      ++s;
    }
    return false;
  }

  template<typename InputIt, typename F>
  void greedy_adjacent_chains(InputIt s, InputIt e, F f) {
    if (s == e) { return; }
    while (s < e - 1) {
      auto n = s + 1;
      bool found_adjacent = false;
      while (n < e) {
	if (f(*s, *n)) {
	  found_adjacent = true;
	  break;
	}
	n++;
      }
      if (found_adjacent) { std::swap(*(s + 1), *n); }
      s++;
    }
  }

  // Could use adjacent_find in place of this function, but I
  // found that the return policy of giving the first element of
  // the matching pair if a match occurs, and end otherwise made
  // iteration awkward when you want to construct new data structures
  // out of the subranges produced by calls to adjacent_find
  template<typename InputIt, typename F>
  std::pair<InputIt, InputIt> find_between(InputIt s, InputIt e, F f) {
    while (s != (e - 1)) {
      if (f(*s, *(s + 1))) { return std::pair<InputIt, InputIt>(s, s + 1); }
      ++s;
    }
    return std::pair<InputIt, InputIt>(s, e);
  }

  template<typename I, typename F>
  std::vector<std::vector> split_by(const std::vector& elems, F f) {
    std::vector<std::vector> split;
    split_by(elems, split, f);
    return split;
  }

  template<typename I, typename F>
  void split_by(const std::vector& elems, std::vector<std::vector>& res, F f) {
    auto it = elems.begin();
    auto not_f = [&f](const I& i, const I& j) { return !f(i, j); };
    while (it != elems.end()) {
      auto r = find_between(it, end(elems), not_f);
      res.push_back(std::vector(it, r.first + 1));
      it = r.second;
    }
  }

  template<typename I, typename F>
  std::vector<std::vector> group_unary(const std::vector& elems, F f) {
    std::vector<std::vector> grouped;
    auto match = [&f](const I& i, const I& j) { return f(i) == f(j); };
    split_by(elems, grouped, match);
    return grouped;
  }

  template<typename I, typename F>
  void delete_if(I& c, F f) {
    c.erase(remove_if(begin(c), end(c), f), end(c));
  }

  template<typename T>
  void subtract(T& a, const T& b) {
    delete_if(a,
	      [&b](const typename T::value_type& i)
	      { return std::find(begin(b), end(b), i) != end(b); });
  }

  template<typename T, typename Q>
  std::pair<T, Q>
  mk_pair(T t, Q q) { return std::pair<T, Q>(t, q); }

  // TODO: Figure out why this implementation always calls
  // the default constructor
  template<typename InputIt1, typename InputIt2, typename OutputIt>
  OutputIt
  zip(InputIt1 ps, InputIt1 pe, InputIt2 qs, OutputIt r) {
    while (ps != pe) {
      *r = mk_pair(*ps, *qs);
      ++ps;
      ++qs;
      ++r;
    }
    return r;
  }

  template<typename T, typename F>
  void drop_while(T& t, F f) {
    auto r = std::find_if_not(begin(t), end(t), f);
    auto s = std::distance(begin(t), r);
    std::rotate(begin(t), r, end(t));
    t.erase(begin(t) + t.size() - s, end(t));
  }

  template<typename T, typename F>
  void take_while(T& t, F f) {
    auto r = find_if_not(begin(t), end(t), f);
    auto s = distance(begin(t), r);
    t.erase(begin(t) + s, end(t));
  }

  template<typename T, typename F>
  std::vector<T> select(const std::vector<T>& v, F f) {
    std::vector<T> selected;
    for (auto e : v) {
      if (f(e)) {
	selected.push_back(e);
      }
    }
    return selected;
  }

  template<typename I, typename P>
  std::vector<unsigned>
  select_neighbors(std::vector<unsigned>& inds,
		   const std::vector& elems,
		   std::vector<unsigned>& buf,
		   unsigned next,
		   P p) {

    std::vector<unsigned> inds_to_remove;

    for (unsigned i = 0; i < inds.size(); i++) {
      unsigned u = inds[i];
      if (u != next && p(elems[u], elems[next])) {
    	buf.push_back(u);
    	inds_to_remove.push_back(u);
      }
    }

    return inds_to_remove;
  }

  template<typename I, typename P>
  std::vector<unsigned>
  dfs_by_neighbors(std::vector<unsigned>& inds, const std::vector& elems, P p) {
    std::vector<unsigned> comp;
    if (inds.size() == 0) {
      return comp;
    }

    std::vector<unsigned> buf;
    buf.push_back(inds.back());
    inds.pop_back();

    while (buf.size() > 0) {
      auto next = buf.back();
      comp.push_back(next);
      buf.pop_back();

      std::vector<unsigned> inds_to_remove = p(inds, elems, buf, next);
	//	select_neighbors(inds, elems, buf, next, p);

      subtract(inds, inds_to_remove);
    }

    return comp;
  }

  template<typename I, typename P>
  std::vector<unsigned>
  dfs_by(std::vector<unsigned>& inds, const std::vector& elems, P p) {
    auto neighbors = [p](std::vector<unsigned>& inds,
			 const std::vector& elems,
			 std::vector<unsigned>& buf,
			 unsigned next) {
      return select_neighbors(inds, elems, buf, next, p);
    };

    return dfs_by_neighbors(inds, elems, neighbors);
  }

  template<typename I, typename P>
  std::vector<std::vector<unsigned>>
  connected_components_by(const std::vector& elems, P p) {
    std::vector<std::vector<unsigned>> components;
    std::vector<unsigned> inds(elems.size());
    std::iota(begin(inds), end(inds), 0);

    while (inds.size() > 0) {
      components.push_back(dfs_by(inds, elems, p));
    }

    return components;
  }

  template<typename I, typename P>
  std::vector<std::vector>
  connected_components_by_elems(const std::vector& elems, P p) {
    auto ccs = connected_components_by(elems, p);
    std::vector<std::vector> res;
    for (auto cc : ccs) {
      std::vector cc_elems;
      for (auto i : cc) {
	cc_elems.push_back(elems[i]);
      }
      res.push_back(cc_elems);
    }
    return res;
  }

  template<typename I, typename F>
  std::vector
  greedy_chain(const I& init, const std::vector& elems, F f) {
    std::vector chain;
    chain.push_back(init);
    if (elems.size() == 0) { return chain; }
    std::vector<unsigned> used;
    bool found_next = true;
    while (found_next) {
      found_next = false;
      for (unsigned i = 0; i < elems.size(); i++) {
	if (!elem(i, used) && f(elems[i], chain.back())) {
	  chain.push_back(elems[i]);
	  used.push_back(i);
	  found_next = true;
	}
      }
    }
    return chain;
  }

  template<typename I>
  std::vector
  copy_not_indexes(const std::vector& elems,
		   std::vector<unsigned>& inds) {
    std::sort(begin(inds), end(inds));
    std::vector cp_elems;
    for (unsigned i = 0; i < elems.size(); i++) {
      if (!(std::binary_search(begin(inds), end(inds), i))) {
    	cp_elems.push_back(elems[i]);
      }
    }
    return cp_elems;
  }

  template<typename I>
  std::vector
  select_indexes(const std::vector& elems,
		 std::vector<unsigned>& inds) {
    std::sort(begin(inds), end(inds));
    std::vector cp_elems;
    for (unsigned i = 0; i < elems.size(); i++) {
      if (std::binary_search(begin(inds), end(inds), i)) {
    	cp_elems.push_back(elems[i]);
      }
    }
    return cp_elems;
  }

  // Set Operations

  // Start intersection
  template<typename A>
  std::set
  intersection(const std::set& l, const std::set& r) {
    std::set it;
    set_intersection(std::begin(l), std::end(l),
		     std::begin(r), std::end(r),
		     std::inserter(it, std::end(it)));
    return it;
  }

  template<typename A>
  std::deque
  intersection(const std::deque& l, const std::deque& r) {
    std::deque it;
    for (auto& e : l) {
      if (elem(e, r)) {
	it.push_back(e);
      }
    }
    return it;
  }

  template<typename A>
  std::vector
  intersection(const std::vector& l, const std::vector& r) {
    std::vector it;
    for (auto& e : l) {
      if (elem(e, r)) {
	it.push_back(e);
      }
    }
    return it;
  }

  template<typename A>
  std::unordered_set
  intersection(const std::unordered_set& l, const std::unordered_set& r) {
    std::unordered_set it;
    for (auto& e : l) {
      if (elem(e, r)) {
	it.insert(e);
      }
    }
    return it;
  }

  // End intersection

  // Start difference

  template<typename T>
  std::vector<T> difference(const std::vector<T>& a,
			    const std::vector<T>& b) {
    std::vector<T> diff = a;
    subtract(diff, b);

    return diff;
  }

  template<typename T>
  std::set<T> difference(const std::set<T>& a,
			 const std::set<T>& b) {
    std::set<T> diff;
    for (auto& e : a) {
      if (!elem(e, b)) {
	diff.insert(e);
      }
    }

    return diff;
  }

  template<typename T>
  std::unordered_set<T> difference(const std::unordered_set<T>& a,
				   const std::unordered_set<T>& b) {
    std::unordered_set<T> diff;
    for (auto& e : a) {
      if (!elem(e, b)) {
	diff.insert(e);
      }
    }

    return diff;
  }

  template<typename T>
  std::deque<T> difference(const std::deque<T>& a,
			   const std::deque<T>& b) {
    std::deque<T> diff;
    for (auto& e : a) {
      if (!elem(e, b)) {
	diff.push_back(e);
      }
    }

    return diff;
  }

  // End difference

  template<typename I>
  std::vector concat_all(const std::vector<std::vector>& vs) {
    std::vector all_vs;
    for (auto v : vs) {
      concat(all_vs, v);
    }
    return all_vs;
  }

  template<typename I>
  std::vector sort_unique(const std::vector& elems) {
    auto elems_cp = elems;
    sort(begin(elems_cp), end(elems_cp));
    auto it = unique(begin(elems_cp), end(elems_cp));
    elems_cp.resize(std::distance(begin(elems_cp), it));
    return elems_cp;
  }

  template<typename A, typename B>
  void map_insert(std::map<A, std::vector>& m, A a, B b) {
    if (m.find(a) == std::end(m)) {
      std::vector bs{b};
      m[a] = bs;
    } else {
      auto elems = m[a];
      elems.push_back(b);
      m[a] = elems;
    }
  }

  template<typename A, typename B>
  B map_find(const A& a, const std::unordered_map& m) {
    auto f = m.find(a);
    if (f == std::end(m)) {
      DBG_ASSERT(false);
    }

    return f->second;
  }

  template<typename A, typename B>
  B map_find(const A& a, const std::map& m) {
    auto f = m.find(a);
    if (f == std::end(m)) {
      DBG_ASSERT(false);
    }

    return f->second;
  }

  template<typename A, typename B>
  void map_insert(std::unordered_map<A, std::vector>& m, A a, B b) {
    if (m.find(a) == std::end(m)) {
      std::vector bs{b};
      m[a] = bs;
    } else {
      auto elems = m[a];
      elems.push_back(b);
      m[a] = elems;
    }
  }

  // TODO: Make this more efficient
  template<typename T, typename F>
  std::vector<T>
  partial_order_maxima(const std::vector<T>& elems, F f) {
    std::vector<T> maxima;
    for (unsigned i = 0; i < elems.size(); i++) {

	maxima.push_back(e);
      }
    }
    return maxima;
  }

  template<typename Elem, typename Orthogonal>
  Elem
  next_orthogonal_to_all(const std::vector<Elem>& to_check,
			 const std::vector<Elem>& to_return_from,
			 Orthogonal orthogonal) {
    assert(to_return_from.size() > 0);

    if (to_check.size() == 0) { return to_return_from.front(); }

    for (unsigned i = 0; i < to_return_from.size(); i++) {
      Elem q = to_return_from[i];
      if (all_of(begin(to_check), end(to_check),
		 [q, orthogonal](const Elem& r)
		 { return orthogonal(q, r); })) {
	return to_return_from[i];
      }
    }

    DBG_ASSERT(false);
  }

  // TODO: Move to utils/algorithm
  template<typename Elem, typename Orthogonal>
  std::vector<Elem>
  take_basis(const std::vector<Elem>& elems,
	     Orthogonal orthogonal,
	     const unsigned num_elems) {
    assert(elems.size() > num_elems);
    std::vector<Elem> basis;
    for (unsigned i = 0; i < num_elems; i++) {
      basis.push_back(next_orthogonal_to_all(basis, elems, orthogonal));
    }
    return basis;
  }

  template<typename T>
  std::vector<T*> ptrs(std::vector<T>& elems) {
    std::vector<T*> pointers;
    for (T& e : elems) {
      pointers.push_back(&e);
    }
    return pointers;
  }

  template<typename T>
  void reverse(T& t) {
    std::reverse(begin(t), end(t));
  }

  template<typename T, typename F>
  T min_e(const std::deque<T>& e, F f) {
    return *min_element(begin(e), end(e),
			[f](const T& l, const T& r) { return f(l) < f(r); });
  }

  template<typename T>
  T min_e(const std::deque<T>& e) {
    return *min_element(begin(e), end(e));
  }

  template<typename T, typename F>
  T min_e(const std::vector<T>& e, F f) {
    return *min_element(begin(e), end(e),
			[f](const T& l, const T& r) { return f(l) < f(r); });
  }

  template<typename T>
  T min_e(const std::vector<T>& e) {
    return *min_element(begin(e), end(e));
  }

  template<typename T, typename F>
  T min_e(const std::set<T>& e, F f) {
    return *min_element(begin(e), end(e),
			[f](const T& l, const T& r) { return f(l) < f(r); });
  }

  template<typename T>
  T min_e(const std::set<T>& e) {
    return *min_element(begin(e), end(e));
  }

  // template<typename T, unsigned long I>
  // T max_e(const std::array<T, I>& e) {
  //   return *max_element(e.begin(), e.end());
  // }

  template<typename T, typename F>
  T max_e(const std::vector<T>& e, F f) {
    return *max_element(begin(e), end(e),
			[f](const T& l, const T& r) { return f(l) < f(r); });
  }

  template<typename T>
  T max_e(const std::vector<T>& e) {
    return *max_element(begin(e), end(e));
  }

  template<typename T, typename F>
  T max_e(const std::deque<T>& e, F f) {
    return *max_element(begin(e), end(e),
			[f](const T& l, const T& r) { return f(l) < f(r); });
  }

  template<typename T>
  T max_e(const std::deque<T>& e) {
    return *max_element(begin(e), end(e));
  }

  template<typename T, typename F>
  T max_e(const std::set<T>& e, F f) {
    return *max_element(begin(e), end(e),
			[f](const T& l, const T& r) { return f(l) < f(r); });
  }

  template<typename T>
  T max_e(const std::set<T>& e) {
    return *max_element(begin(e), end(e));
  }

  template<typename T, typename F>
  T max_e(const std::unordered_set<T>& e, F f) {
    return *max_element(begin(e), end(e),
			[f](const T& l, const T& r) { return f(l) < f(r); });
  }

  template<typename T>
  T max_e(const std::unordered_set<T>& e) {
    return *max_element(begin(e), end(e));
  }

  template<typename T, typename F>
  T min_e(const std::unordered_set<T>& e, F f) {
    return *min_element(begin(e), end(e),
			[f](const T& l, const T& r) { return f(l) < f(r); });
  }

  template<typename T>
  T min_e(const std::unordered_set<T>& e) {
    return *min_element(begin(e), end(e));
  }

  template<typename T, typename F>
  T max_e(const std::list<T>& e, F f) {
    return *max_element(begin(e), end(e),
			[f](const T& l, const T& r) { return f(l) < f(r); });
  }

  template<typename T>
  T max_e(const std::list<T>& e) {
    return *max_element(begin(e), end(e));
  }

  template<typename T, typename F>
  T min_e(const std::list<T>& e, F f) {
    return *min_element(begin(e), end(e),
			[f](const T& l, const T& r) { return f(l) < f(r); });
  }

  template<typename T>
  T min_e(const std::list<T>& e) {
    return *min_element(begin(e), end(e));
  }

  template<typename T, typename F>
  T max_e(const std::forward_list<T>& e, F f) {
    return *max_element(begin(e), end(e),
			[f](const T& l, const T& r) { return f(l) < f(r); });
  }

  template<typename T>
  T max_e(const std::forward_list<T>& e) {
    return *max_element(begin(e), end(e));
  }

  template<typename T, typename F>
  T min_e(const std::forward_list<T>& e, F f) {
    return *min_element(begin(e), end(e),
			[f](const T& l, const T& r) { return f(l) < f(r); });
  }

  template<typename T>
  T min_e(const std::forward_list<T>& e) {
    return *min_element(begin(e), end(e));
  }

  template<typename T, typename F>
  void sort_lt(std::vector<T>& e, F f) {
    std::sort(begin(e), end(e), [f](const T& l, const T& r) { return f(l) < f(r); });
  }

  template<typename T, typename F>
  void sort_gt(std::vector<T>& e, F f) {
    std::sort(begin(e), end(e), [f](const T& l, const T& r) { return f(l) > f(r); });
  }

  template<typename A, typename B>
  bool contains_key(const A& a, const std::unordered_map<A, B>& m) {
    auto f = m.find(a);
    if (f == std::end(m)) {
      return false;
    }

    return true;
  }

  template<typename A, typename B>
  bool contains_key(const A& a, const std::map<A, B>& m) {
    auto f = m.find(a);
    if (f == std::end(m)) {
      return false;
    }

    return true;
  }

  template<typename T>
  bool elems_equal(const std::vector<T>& x,
		   const std::vector<T>& y) {
    if (x.size() != y.size()) { return false; }

    for (unsigned i = 0; i < x.size(); i++) {
      if (x[i] != y[i]) { return false; }
    }
    return true;
  }

  template<typename A, typename F>
  bool any_of(const A& container, F f) {
    return any_of(begin(container), end(container), f);
  }

  template<typename A, typename F>
  bool all_of(const A& container, F f) {
    return all_of(begin(container), end(container), f);
  }

  template<typename T, typename EqualityTest>
  bool elem_by(const T& e, const std::vector<T>& vals, EqualityTest eq) {

    for (auto& v : vals) {
      if (eq(e, v)) { return true; }
    }

    return false;
  }

  template<typename T>
  class maybe {

    T value;
    bool has_val;

  public:

    maybe(const T& value_) : value(value_), has_val(true) {}
    maybe() : has_val(false) {}

    bool has_value() const { return has_val; }

    T get_value() const {
      assert(has_value());
      return value;
    }
  };

}

#endif

      auto e = elems[i];
      bool gt_all = true;
      for (unsigned j = 0; j < elems.size(); j++) {
	auto a = elems[j];
	if (i != j && !f(a, e)) {


#pragma once

#ifdef COREIR_BACKEND

#include "coreir.h"

#include "microarchitecture.h"

namespace ahaHLS {

                  CoreIR::Namespace* const n);
}

#endif

  void emitCoreIR(const std::string& name,
                  MicroArchitecture& arch,


#pragma once

#include "scheduling.h"

namespace ahaHLS {

  class ControlFlowPosition {

    StateId state;
    bool inPipe;
    int pipeStage;

  public:
    llvm::Instruction* instr;

    ControlFlowPosition(const StateId state_,
                        const bool inPipe_,
                        const int pipeStage_,
                        llvm::Instruction* const instr_) :
      state(state_), inPipe(inPipe_), pipeStage(pipeStage_), instr(instr_) {
    }

    bool inPipeline() const {
      return inPipe;
    }

    StateId stateId() const {
      return state;
    }

    BasicBlock* getBB() const {
      return instr->getParent();
    }

    int pipelineStage() const {
      return pipeStage;
    }
  };

  std::ostream& operator<<(std::ostream& out, ControlFlowPosition& pos);

  ControlFlowPosition position(const StateId state, Instruction* const instr);

  ControlFlowPosition pipelinePosition(Instruction* const instr,
                                       const StateId state,
                                       const int stage);

  bool hasOutput(llvm::Instruction* instr);

  class Memory {
  public:
    int width;
    std::string name;
    int depth;
  };

  static inline
  Wire wire(const int width, const std::string& name) {
    return {false, width, name};
  }

  static inline
  Wire reg(const int width, const std::string& name) {
    return {true, width, name};
  }

  static inline
  Wire constWire(const int width, const int value) {
    return {width, value};
  }

  Port wireToOutputPort(const Wire w);
  Port wireToInputPort(const Wire w);

  static inline
  bool operator<(const Wire a, const Wire b) {
    return a.name < b.name;
  }

  std::ostream& operator<<(std::ostream& out, const Wire w);

  class ModuleInstance {
  public:
    std::string modName;
    std::map<std::string, std::string> params;
    std::string instName;
    std::map<std::string, std::string> portConnections;

    ModuleInstance(const std::string modName_,
                   const std::string instName_,
                   const std::map<std::string, std::string> portConnections_) :
      modName(modName_), params({}), instName(instName_), portConnections(portConnections_) {}

    ModuleInstance(const std::string modName_,
                   const std::map<std::string, std::string> params_,
                   const std::string instName_,
                   const std::map<std::string, std::string> portConnections_) :
      modName(modName_), params(params_), instName(instName_), portConnections(portConnections_) {}

  };

  static inline
  void print(std::ostream& out, int level, const ModuleInstance& b) {

    std::vector<std::string> portStrings;
    for (auto pt : b.portConnections) {
      portStrings.push_back("." + pt.first + "(" + pt.second + ")");
    }

    std::vector<std::string> paramStrs;
    for (auto pt : b.params) {
      paramStrs.push_back("." + pt.first + "(" + pt.second + ")");
    }

    out << tab(level) << b.modName;
    if (paramStrs.size() > 0) {
      out << " #(" << commaListString(paramStrs) << ")";
    }

    out << " ";
    out << b.instName << "(";
    out << commaListString(portStrings);
    out << ");" << std::endl;
  }

  class FunctionalUnit {
  public:

    ModuleSpec module;
    std::string instName;

    std::map<std::string, Wire> portWires;
    std::map<std::string, Wire> outWires;

    bool external;

    bool isExternal() const {
      return external;
    }

    std::string getModName() const {
      return module.name;
    }

    std::string outputWire(const std::string& name) const {
      if (!dbhc::contains_key(name, outWires)) {
        std::cout << "Error: No wire named " << name << std::endl;
        assert(false);
      }

      auto n = dbhc::map_find(name, outWires).name;
      return n;
    }

    Wire outputWire() const {
      assert(outWires.size() == 1);
      return (std::begin(outWires))->second;
    }

    std::string inputWire(const std::string& name) const {
      if (!dbhc::contains_key(name, portWires)) {
        std::cout << "Error: No wire named " << name << std::endl;
        assert(false);
      }

      auto n = dbhc::map_find(name, portWires).name;
      if (isExternal()) {
        return n + "_reg";
      }

      return n;
    }

    Wire input(const std::string& name) const {
      if (!dbhc::contains_key(name, portWires)) {
        std::cout << "Error: No wire named " << name << ", input wires in unit " <<  instName << std::endl;
        for (auto w : portWires) {
          std::cout << tab(1) << w.first << " -> " << w.second.valueString() << endl;
        }

        std::cout << "Module spec = " << module << std::endl;
        assert(false);
      }

      auto n = dbhc::map_find(name, portWires);
      if (isExternal()) {
        return wire(n.width, n.name + "_reg");
      }

      return n;
    }

    std::map<std::string, std::string> getParams() const {
      return module.params;
    }

    Wire onlyInput() const {
      assert(portWires.size() == 1);

      return (*begin(portWires)).second;
    }

    std::string onlyOutputVar() const {
      assert(outWires.size() == 1);

      return (*begin(outWires)).second.name;
    }
  };

  class InstructionBinding {
  public:
    FunctionalUnit unit;
    std::map<llvm::Value*, std::string> instrWires;

    InstructionBinding() {}
    InstructionBinding(const FunctionalUnit& unit_) : unit(unit_), instrWires({}) {}
    InstructionBinding(const FunctionalUnit& unit_,
                       const std::map<llvm::Value*, std::string>& wires_) :
      unit(unit_), instrWires(wires_) {}
  };

  class ElaboratedPipeline {
  public:
    Pipeline p;
    Wire inPipe;
    StateId stateId;

    ElaboratedPipeline(const Pipeline& p_) : p(p_) {}

    Wire inPipeWire() const {
      return wire(1, inPipe.name + "_out_data");
    }

    int II() const {
      return p.II();
    }

    int stateIndex(const StateId id) const {
      return id - p.startState();
    }

    int stageForState(const StateId id) const {
      return stateIndex(id);
    }

    int stateForStage(const int stageNo) const {
      return p.startState() + stageNo;
    }

  };

  bool isPipelineState(const StateId id,
                       const std::vector<ElaboratedPipeline>& pipelines);

  ElaboratedPipeline getPipeline(const StateId id,
                                 const std::vector<ElaboratedPipeline>& pipelines);

  FunctionalUnit functionalUnitForSpec(const std::string unitName,
                                       const ModuleSpec& mSpec);

  class RAM {

  public:

    std::string name;
    int width;
    int depth;

    RAM(const std::string& name_,
        const int width_,
        const int depth_) : name(name_), width(width_), depth(depth_) {}
  };

  class ControlState {
    Wire globalState;
    Wire lastBB;

    std::map<llvm::BasicBlock*, int> basicBlockNos;

  public:

    ControlState() {
      globalState = reg(32, "global_state");
      lastBB = reg(32, "last_BB_reg");
    }

    void setBasicBlockNo(BasicBlock* const bb, const int val) {
      basicBlockNos[bb] = val;
    }

    int getBasicBlockNo(BasicBlock* bb) const {
      return dbhc::map_find(bb, basicBlockNos);
    }

    Wire getGlobalState() const {
      return globalState;
    }

    Wire getLastBB() const {
      return lastBB;
    }
  };

  class UnitController {
  public:
    FunctionalUnit unit;
    std::map<StateId, std::vector<Instruction*> > instructions;
  };

  typedef std::vector<std::string> StallConds;
  typedef std::map<std::string, std::string> PortAssignments;

  // Represents all possible values that can be assigned to
  // a port, and the cycles at which they are assigned
  class PortValues {
  public:
    // This map should include
    //  1. States where the port is assigned
    //  2. States where it is an unused value
    bool isInsensitive;
    map<Wire, Wire> portVals;
    std::string defaultValue;
  };

  // Need to move to one map from ports to states and the values
  // they take in each state
  class PortController {
  public:
    UnitController unitController;
    PortAssignments statelessDefaults;

    map<string, PortValues> inputControllers;

    void setCond(const std::string& port, const Wire& condition, const Wire& value);

    FunctionalUnit& functionalUnit() {
      return unitController.unit;
    }

    void setAlways(const std::string& portName, const Wire value) {
      inputControllers[functionalUnit().inputWire(portName)].portVals[constWire(1, 1)] = value;
    }

    Wire onlyInput() const {
      const FunctionalUnit& unit = unitController.unit;
      return unit.onlyInput();
    }

    bool isExternal() const { return unitController.unit.isExternal(); }

    string defaultValue(const std::string& port) {
      return dbhc::map_find(port, statelessDefaults);
    }

    bool hasDefault(const std::string& port) {
      return dbhc::contains_key(port, statelessDefaults);
    }

  };

  class RegController {
  public:

    std::string resetValue;
    //std::map<string, string> values;
    std::map<Wire, Wire> values;
  };

  class WorldState {
  public:
    std::map<Instruction*, Wire> values;
  };

  class DataPath {
  public:
    std::map<StateId, WorldState> stateData;
    std::map<StateId, WorldState> stateDataInputs;
  };

  class MicroArchitecture {
  public:
    ControlState cs;

    STG stg;
    //std::map<llvm::Instruction*, FunctionalUnit> unitAssignment;
    std::map<llvm::Instruction*, InstructionBinding> unitAssignment;
    std::map<llvm::Value*, int> memoryMap;
    //std::map<llvm::Instruction*, Wire> names;
    std::vector<ElaboratedPipeline> pipelines;

    std::map<Wire, std::string> resetValues;
    HardwareConstraints hcs;

    std::map<std::string, RegController> regControllers;
    std::map<std::string, PortController> portControllers;
    std::vector<FunctionalUnit> functionalUnits;

    int uniqueNum;

    std::map<std::pair<llvm::BasicBlock*, llvm::BasicBlock*>, Wire> edgeTakenWires;
    std::map<StateId, Wire> atStateWires;
    std::map<StateId, Wire> lastBBWires;
    std::map<StateId, Wire> entryBBWires;
    std::map<StateId, Wire> lastStateWires;

    DataPath dp;

    Wire isActiveBlockVar(const StateId state, llvm::BasicBlock* const bb);
    std::string uniqueName(const std::string& prefix) {
      std::string name = prefix + "_" + std::to_string(uniqueNum);
      uniqueNum++;
      return name;
    }

    FunctionalUnit& makeUnit(std::string name, ModuleSpec& mSpec) {
      functionalUnits.push_back(functionalUnitForSpec(name, mSpec));
      return functionalUnits.back();
    }

    PortController& addPortController(FunctionalUnit& unit) {
      int earlierSize = portControllers.size();
      PortController c;
      c.unitController.unit = unit;
      portControllers[unit.instName] = c;

      assert(portControllers.size() == (earlierSize + 1));

      return portControllers[unit.instName];
    }

    PortController& portController(const std::string& name) {
      if (!dbhc::contains_key(name, portControllers)) {
        std::cout << "Error: No controller for " << name << std::endl;
        assert(dbhc::contains_key(name, portControllers));
      }
      return portControllers[name];
      // for (auto& c : portControllers) {
      //   if (c.unitController.unit.instName == name) {
      //     return c.second;
      //   }
      // }

      // cout << "Error: Could not find controller for " << name << endl;
      // assert(false);
    }

    void addController(const std::string& name, const int width) {
      RegController ctr;
      ctr.reg = reg(width, name);
      ctr.resetValue = "0";
      regControllers[name] = ctr;
    }

    RegController& getController(const std::string& name) {
      if (dbhc::contains_key(name, regControllers)) {
        return regControllers[name];
      } else {
        addController(name, 1);
        return regControllers[name];
      }
    }

    RegController& getController(const Wire& w) {
      auto name = w.name;
      if (dbhc::contains_key(name, regControllers)) {
        return regControllers[name];
      } else {
        addController(name, w.width);
        return regControllers[name];
      }
    }

    MicroArchitecture(const ControlState& cs_,
                      //const ArchOptions& archOptions_,
                      const STG& stg_,
                      //const std::map<llvm::Instruction*, FunctionalUnit>& unitAssignment_,
                      const std::map<llvm::Instruction*, InstructionBinding>& unitAssignment_,
                      const std::map<llvm::Value*, int>& memoryMap_,
                      const std::map<llvm::Instruction*, Wire>& names_,
                      const std::vector<ElaboratedPipeline>& pipelines_,
                      const HardwareConstraints& hcs_) :
      cs(cs_),
      //archOptions(archOptions_),
      stg(stg_),
      unitAssignment(unitAssignment_),
      memoryMap(memoryMap_),
      //names(names_),
      pipelines(pipelines_),
      hcs(hcs_),
      uniqueNum(0) {

      resetValues.insert({Wire(true, 32, "global_state"),
            std::to_string(cs.getBasicBlockNo(&(stg.getFunction()->getEntryBlock())))});
    }

    // bool hasGlobalStall() const {
    //   return cs.hasGlobalStall();
    // }

    ElaboratedPipeline getPipeline(const StateId state) const {
      assert(ahaHLS::isPipelineState(state, pipelines));
      return ahaHLS::getPipeline(state, pipelines);
    }

    int numFUsWithName(const std::string& name) const {
      int n = 0;
      std::set<std::string> alreadyAdded;
      for (auto ua : unitAssignment) {
        if (!dbhc::elem(ua.second.unit.instName, alreadyAdded) && (ua.second.unit.getModName() == name)) {
          n++;
        }
        alreadyAdded.insert(ua.second.unit.instName);
      }
      return n;
    }

    int numReadPorts() const {
      return numFUsWithName("load");
    }

    int numWritePorts() const {
      return numFUsWithName("store");
    }

    bool isPipelineState(const StateId id) const {
      return ahaHLS::isPipelineState(id, pipelines);
    }

  };

  Wire buildIncCounter(const Wire incrCond,
                       const int width,
                       MicroArchitecture& arch);

  MicroArchitecture
  buildMicroArchitecture(const STG& stg,
                         std::map<std::string, int>& memoryMap);

  MicroArchitecture
  buildMicroArchitecture(const STG& stg,
                         std::map<std::string, int>& memoryMap,
                         HardwareConstraints& hcs);

  MicroArchitecture
  buildMicroArchitecture(const STG& stg,
                         std::map<llvm::Value*, int>& memoryMap,
                         HardwareConstraints& hcs);

  MicroArchitecture
  buildMicroArchitecture(const STG& stg,
                         std::map<llvm::Value*, int>& memoryMap);

  // TODO: Move to separate memory analysis file, and eventually
  // to an LLVM dataflow pass
  std::map<llvm::Instruction*, llvm::Value*>
  memoryOpLocations(llvm::Function* f);

  std::string atState(const StateId state, MicroArchitecture& arch);
  std::string notAtState(const StateId state, MicroArchitecture& arch);

  llvm::Instruction* lastInstructionInState(const StateId state,
                                            MicroArchitecture& arch);

  std::string floatBits(const float f);

  ControlFlowPosition position(const StateId state,
                               Instruction* const instr,
                               MicroArchitecture& arch);

  bool stateless(FunctionalUnit& unit);

  Wire inAnyPipeline(MicroArchitecture& arch);

  Wire checkEqual(const int value, const Wire w, MicroArchitecture& arch);
  Wire checkEqual(const Wire valWire, const Wire w, MicroArchitecture& arch);

  PortController& addPortController(const std::string& name,
                                    const int width,
                                    MicroArchitecture& arch);

  Wire blockActiveInState(const StateId state,
                          BasicBlock* const blk,
                          MicroArchitecture& arch);

  bool jumpToSameState(BasicBlock* const predecessor,
                       BasicBlock* const successor,
                       MicroArchitecture& arch);

  Wire atStateWire(const StateId state, MicroArchitecture& arch);

  Wire checkNotWire(const Wire in, MicroArchitecture& arch);
  Wire checkAnd(const Wire in0, const Wire in1, MicroArchitecture& arch);

  std::string dataOutput(llvm::Instruction* instr0, const MicroArchitecture& arch);

  std::vector<Port>
  getPorts(MicroArchitecture& arch);

  std::string outputName(Value* val,
                         ControlFlowPosition& currentPosition,
                         MicroArchitecture& arch);

  Wire outputWire(Value* val,
                  ControlFlowPosition& currentPosition,
                  MicroArchitecture& arch);

  bool isInsensitive(const std::string& port,
                     PortController& portController);

  bool needsTempStorage(llvm::Instruction* const instr,
                        MicroArchitecture& arch);

  std::ostream& operator<<(std::ostream& out, const RegController& controller);
  std::ostream& operator<<(std::ostream& out, const FunctionalUnit& unit);

  void emitPipelineValidChainBlock(MicroArchitecture& arch);

  std::vector<ElaboratedPipeline>
  buildPipelines(llvm::Function* f, const STG& stg);

  void
  emitPipelineInitiationBlock(MicroArchitecture& arch);

  void emitPipelineResetBlock(MicroArchitecture& arch);

  void emitPipelineRegisterChains(MicroArchitecture& arch);

  Wire stateActiveReg(const StateId state, MicroArchitecture& arch);

  Wire nextBBReg(const StateId state, MicroArchitecture& arch);
  Wire lastBBReg(const StateId state, MicroArchitecture& arch);

  void convertRegisterControllersToPortControllers(MicroArchitecture& arch);
}