while (tok)
    {
        tokens->push_back(tok);
        tok = strtok(NULL, ",");
    }

#include "Base.h"

#include "EncoderArguments.h"
#include "StringUtil.h"

#ifdef WIN32
    #define PATH_MAX    _MAX_PATH
    #define realpath(A,B)    _fullpath(B,A,PATH_MAX)
#endif

// The encoder version number should be incremented when a feature is added to the encoder.
// The encoder version is not the same as the GPB version.
#define ENCODER_VERSION "3.0.0"
#define HEIGHTMAP_SIZE_MAX 2049

namespace gplayencoder
{

static EncoderArguments* __instance;

extern int __logVerbosity = 1;

EncoderArguments::EncoderArguments(size_t argc, const char** argv) :
    _normalMap(false),
    _parseError(false),
    _fontPreview(false),
    _fontFormat(Font::BITMAP),
    _textOutput(false),
    _optimizeAnimations(false),
    _animationGrouping(ANIMATIONGROUP_PROMPT),
    _outputMaterial(false),
    _generateTextureGutter(false)
{
    __instance = this;

    memset(_heightmapResolution, 0, sizeof(int) * 2);

    if (argc > 1)
    {
        // read the options
        std::vector<std::string> arguments;
        for (size_t i = 1; i < argc; ++i)
        {
            arguments.push_back(argv[i]);
        }
        size_t index = 0;
        for (size_t i = 0; i < arguments.size(); ++i)
        {
            if (arguments[i][0] == '-')
            {
                readOption(arguments, &i);
                index = i + 1;
            }
        }
        if (arguments.size() - index == 2)
        {
            setInputfilePath(arguments[index]);
            setOutputfilePath(arguments[index + 1]);
        }
        else if (arguments.size() - index == 1)
        {
            setInputfilePath(arguments[index]);
        }
    }
    else
    {
        _parseError = true;
    }
}

EncoderArguments::~EncoderArguments(void)
{
}

EncoderArguments* EncoderArguments::getInstance()
{
    return __instance;
}

const std::string& EncoderArguments::getFilePath() const
{
    return _filePath;
}

const std::string EncoderArguments::getFileDirPath() const
{
    int pos = _filePath.find_last_of('/');
    return (pos == -1 ? _filePath : _filePath.substr(0, pos));
}

const char* EncoderArguments::getFilePathPointer() const
{
    return _filePath.c_str();
}

const std::string EncoderArguments::getFileName() const
{
    int pos = _filePath.find_last_of('/');
    return (pos == -1 ? _filePath : _filePath.substr(pos + 1));
}

std::string EncoderArguments::getOutputDirPath() const
{
    if (_fileOutputPath.size() > 0)
    {
        int pos = _fileOutputPath.find_last_of('/');
        return (pos == -1 ? _fileOutputPath : _fileOutputPath.substr(0, pos));
    }
    else
    {
        int pos = _filePath.find_last_of('/');
        return (pos == -1 ? _filePath : _filePath.substr(0, pos));
    }
}

std::string EncoderArguments::getOutputFileExtension() const
{
    switch (getFileFormat())
    {
    case FILEFORMAT_TMX:
        return ".scene";
    case FILEFORMAT_PNG:
    case FILEFORMAT_RAW:
        if (_normalMap)
            return ".png";

    default:
        return ".gpb";
    }
}

std::string EncoderArguments::getOutputFilePath() const
{
    if (_fileOutputPath.size() > 0)
    {
        // Output file explicitly set
        return _fileOutputPath;
    }
    else
    {
        // Generate an output file path
        int pos = _filePath.find_last_of('.');
        std::string outputFilePath(pos > 0 ? _filePath.substr(0, pos) : _filePath);

        // Modify the original file name if the output extension can be the same as the input
        if (_normalMap)
        {
            outputFilePath.append("_normalmap");
        }

        outputFilePath.append(getOutputFileExtension());
        return outputFilePath;
    }
}

const std::vector<std::string>& EncoderArguments::getGroupAnimationNodeId() const
{
    return _groupAnimationNodeId;
}

const std::vector<std::string>& EncoderArguments::getGroupAnimationAnimationId() const
{
    return _groupAnimationAnimationId;
}

bool EncoderArguments::containsGroupNodeId(const std::string& nodeId) const
{
    return find(_groupAnimationNodeId.begin(), _groupAnimationNodeId.end(), nodeId) != _groupAnimationNodeId.end();
}

const std::string EncoderArguments::getAnimationId(const std::string& nodeId) const
{
    for (size_t i = 0, size = _groupAnimationNodeId.size(); i < size; ++i)
    {
        if (_groupAnimationNodeId[i].compare(nodeId) == 0)
        {
            return _groupAnimationAnimationId[i];
        }
    }
    return "";
}

EncoderArguments::AnimationGroupOption EncoderArguments::getAnimationGrouping() const
{
    return _animationGrouping;
}

const std::vector<EncoderArguments::HeightmapOption>& EncoderArguments::getHeightmapOptions() const
{
    return _heightmaps;
}

unsigned int EncoderArguments::tangentBinormalIdCount() const
{
    return _tangentBinormalId.size();
}

bool EncoderArguments::isGenerateTangentBinormalId(const std::string& id) const
{
    return _tangentBinormalId.find(id) != _tangentBinormalId.end();
}

bool EncoderArguments::normalMapGeneration() const
{
    return _normalMap;
}

void EncoderArguments::getHeightmapResolution(int* x, int* y) const
{
    *x = _heightmapResolution[0];
    *y = _heightmapResolution[1];
}

const Vector3& EncoderArguments::getHeightmapWorldSize() const
{
    return _heightmapWorldSize;
}

bool EncoderArguments::parseErrorOccured() const
{
    return _parseError;
}

bool EncoderArguments::fileExists() const
{
    if (_filePath.length() > 0)
    {
        struct stat buf;
        if (stat(_filePath.c_str(), &buf) != -1)
        {
            return true;
        }
    }
    return false;
}

void splitString(const char* str, std::vector<std::string>* tokens)
{
    // Split node id list into tokens
    unsigned int length = strlen(str);
    char* temp = new char[length + 1];
    strcpy(temp, str);
    char* tok = strtok(temp, ",");

    delete[] temp;
}

void EncoderArguments::printUsage() const
{
    LOG(1, "Usage: gameplay-encoder [options] <input filepath> <output filepath>\n\n" \
    "Encoder version: " ENCODER_VERSION "\n\n" \
    "Supported file extensions:\n" \
    "  .fbx\t(FBX scenes)\n" \
    "  .ttf\t(TrueType fonts)\n" \
    "\n" \
    "General options:\n" \
    "  -v <verbosity>\tVerbosity level (0-4).\n" \
    "\n" \
    "FBX file options:\n" \
    "  -i <id>\tFilter by node ID.\n" \
    "  -t\t\tWrite text/xml.\n" \
    "  -g:auto\tAutomatically group animation channels into a new animation.\n" \
    "  -g:none\tDo not prompt to group animations.\n" \
    "  -g <node id> <animation id>\n" \
        "\t\tGroup all animation channels targeting the nodes into a \n" \
        "\t\tnew animation.\n" \
    "  -m\t\tOutput material file for scene.\n" \
    "  -tb <node id>\n" \
        "\t\tGenerates tangents and binormals for the given node.\n" \
    "  -oa\n" \
        "\t\tOptimizes animations by analyzing animation channel data and\n" \
        "\t\tremoving any channels that contain default/identity values\n" \
        "\t\tand removing any duplicate contiguous keyframes, which are \n" \
        "\t\tcommon when exporting baked animation data.\n" \
    "  -h <size> \"<node ids>\" <filename>\n" \
        "\t\tGenerates a single heightmap image using meshes from the \n" \
        "\t\tspecified nodes. \n" \
        "\t\t<size> is two comma-separated numbers in the format \"X,Y\", \n" \
        "\t\tindicating the dimensions of the produced heightmap image.\n" \
        "\t\t<node ids> should be in quotes with a space between each id.\n" \
        "\t\tFilename is the name of the image (PNG) to be saved.\n" \
        "\t\tMultiple -h arguments can be supplied to generate more than one \n" \
        "\t\theightmap. For 24-bit packed height data use -hp instead of -h.\n" \
    "\n" \
    "TMX file options:\n" \
    "  -tg\tEnable texture gutter's around tiles. This will modify any referenced\n" \
    "  \ttile sets to add a 1px border around it to prevent seams.\n"
    "  -tg:none\tDo not priduce a texture gutter.\n"
    "\n" \
    "Normal map options:\n" \
        "  -n\t\tGenerate normal map (requires input file of type PNG or RAW)\n" \
        "  -s\t\tSize/resolution of the input heightmap image (required for RAW files)\n" \
        "  -w <size>\tSpecifies the size of an input terrain heightmap file in world\n" \
        "\t\tunits, along the X, Y and Z axes. <size> should be three \n" \
        "\t\tcomma-separated numbers in the format \"X,Y,Z\".  \n" \
        "\t\tThe Y value represents the maximum possible height value of a  \n" \
        "\t\tfull intensity heightmap pixel.\n" \
        "\n" \
        "  \t\tNormal map generation can be used to create object-space normal maps from \n" \
        "  \t\theightmap images. Heightmaps must be in either PNG format (where the \n" \
        "  \t\tintensity of each pixel represents a height value), or in RAW format \n" \
        "  \t\t(8 or 16-bit), which is a common headerless format supported by most \n" \
        "  \t\tterrain generation tools.\n" \
    "\n" \
    "TTF file options:\n" \
    "  -s <sizes>\tComma-separated list of font sizes (in pixels).\n" \
    "  -p\t\tOutput font preview.\n" \
    "  -f\t\tFormat of font. -f:b (BITMAP), -f:d (DISTANCE_FIELD).\n" \
    "\n");
    exit(8);
}

bool EncoderArguments::fontPreviewEnabled() const
{
    return _fontPreview;
}

Font::FontFormat EncoderArguments::getFontFormat() const
{
    return _fontFormat;
}

bool EncoderArguments::textOutputEnabled() const
{
    return _textOutput;
}

bool EncoderArguments::optimizeAnimationsEnabled() const
{
    return _optimizeAnimations;
}

bool EncoderArguments::outputMaterialEnabled() const
{
    return _outputMaterial;
}

bool EncoderArguments::generateTextureGutter() const
{
    return _generateTextureGutter;
}

const char* EncoderArguments::getNodeId() const
{
    if (_nodeId.length() == 0)
    {
        return NULL;
    }
    return _nodeId.c_str();
}

std::vector<unsigned int> EncoderArguments::getFontSizes() const
{
    return _fontSizes;
}

EncoderArguments::FileFormat EncoderArguments::getFileFormat() const
{
    if (_filePath.length() < 5)
    {
        return FILEFORMAT_UNKNOWN;
    }
    // Extract the extension
    std::string ext = "";
    size_t pos = _filePath.find_last_of(".");
    if (pos != std::string::npos)
    {
        ext = _filePath.substr(pos + 1);
    }
    for (size_t i = 0; i < ext.size(); ++i)
        ext[i] = (char)tolower(ext[i]);

    // Match every supported extension with its format constant
    if (ext.compare("fbx") == 0)
    {
        return FILEFORMAT_FBX;
    }
    if (ext.compare("tmx") == 0)
    {
        return FILEFORMAT_TMX;
    }
    if (ext.compare("ttf") == 0)
    {
        return FILEFORMAT_TTF;
    }
    if (ext.compare("otf") == 0)
    {
        return FILEFORMAT_OTF;
    }
    if (ext.compare("gpb") == 0)
    {
        return FILEFORMAT_GPB;
    }
    if (ext.compare("png") == 0)
    {
        return FILEFORMAT_PNG;
    }
    if (ext.compare("raw") == 0)
    {
        return FILEFORMAT_RAW;
    }

    return FILEFORMAT_UNKNOWN;
}

void EncoderArguments::readOption(const std::vector<std::string>& options, size_t* index)
{
    const std::string& str = options[*index];
    if (str.length() == 0 && str[0] != '-')
    {
        return;
    }
    switch (str[1])
    {
    case 'f':
        if (str.compare("-f:b") == 0)
        {
            _fontFormat = Font::BITMAP;
        }
       else  if (str.compare("-f:d") == 0)
        {
            _fontFormat = Font::DISTANCE_FIELD;
        }
        break;
    case 'g':
        if (str.compare("-groupAnimations:auto") == 0 || str.compare("-g:auto") == 0)
        {
            _animationGrouping = ANIMATIONGROUP_AUTO;
        }
        else if (str.compare("-groupAnimations:off") == 0 || str.compare("-g:off") == 0)
        {
            _animationGrouping = ANIMATIONGROUP_OFF;
        }
        else if (str.compare("-groupAnimations") == 0 || str.compare("-g") == 0)
        {
            // read two strings, make sure not to go out of bounds
            if ((*index + 2) >= options.size())
            {
                LOG(1, "Error: -g requires 2 arguments.\n");
                _parseError = true;
                return;
            }
            (*index)++;
            _groupAnimationNodeId.push_back(options[*index]);
            (*index)++;
            _groupAnimationAnimationId.push_back(options[*index]);
        }
        break;
    case 'i':
        // Node ID
        (*index)++;
        if (*index < options.size())
        {
            _nodeId.assign(options[*index]);
        }
        else
        {
            LOG(1, "Error: missing arguemnt for -%c.\n", str[1]);
            _parseError = true;
            return;
        }
        break;
    case 'o':
        // Optimization flag
        if (str == "-oa")
        {
            // Optimize animations
            _optimizeAnimations = true;
        }
        break;
    case 'h':
        {
            bool isHighPrecision = str.compare("-hp") == 0;
            if (str.compare("-heightmap") == 0 || str.compare("-h") == 0 || isHighPrecision)
            {
                (*index)++;
                if (*index < (options.size() + 2))
                {
                    _heightmaps.resize(_heightmaps.size() + 1);
                    HeightmapOption& heightmap = _heightmaps.back();

                    heightmap.isHighPrecision = isHighPrecision;

                    // Read heightmap size
                    std::vector<std::string> parts;
                    splitString(options[*index].c_str(), &parts);
                    if (parts.size() != 2)
                    {
                        LOG(1, "Error: invalid size argument for -h|-heightmap.\n");
                        _parseError = true;
                        return;
                    }
                    heightmap.width = atoi(parts[0].c_str());
                    heightmap.height = atoi(parts[1].c_str());

                    // Put some artificial bounds on heightmap dimensions
                    if (heightmap.width <= 0 || heightmap.height <= 0 || heightmap.width > HEIGHTMAP_SIZE_MAX || heightmap.height > HEIGHTMAP_SIZE_MAX)
                    {
                        LOG(1, "Error: size argument for -h|-heightmap must be between (1,1) and (%d,%d).\n", (int)HEIGHTMAP_SIZE_MAX, (int)HEIGHTMAP_SIZE_MAX);
                        _parseError = true;
                        return;
                    }

                    // Split node id list into tokens
                    (*index)++;
                    splitString(options[*index].c_str(), &heightmap.nodeIds);

                    // Store output filename
                    (*index)++;
                    heightmap.filename = options[*index];
                    if (heightmap.filename.empty())
                    {
                        LOG(1, "Error: missing filename argument for -h|-heightmap.\n");
                        _parseError = true;
                        return;
                    }

                    // Ensure the output filename has a .png extention
                    if (heightmap.filename.length() > 5)
                    {
                        const char* ext = heightmap.filename.c_str() + (heightmap.filename.length() - 4);
                        if (ext[0] != '.' || tolower(ext[1]) != 'p' || tolower(ext[2]) != 'n' || tolower(ext[3]) != 'g')
                            heightmap.filename += ".png";
                    }
                    else
                        heightmap.filename += ".png";
                }
                else
                {
                    LOG(1, "Error: missing argument for -h|-heightmap.\n");
                    _parseError = true;
                    return;
                }
            }
        }
        break;
    case 'm':
        if (str.compare("-m") == 0)
        {
            // generate a material file
            _outputMaterial = true;
        }
        break;
    case 'n':
        _normalMap = true;
        break;
    case 'w':
        {
            // Read world size
            (*index)++;
            if (*index >= options.size())
            {
                LOG(1, "Error: missing world size argument for -w.\n");
                _parseError = true;
                return;
            }
            std::vector<std::string> parts;
            splitString(options[*index].c_str(), &parts);
            if (parts.size() != 3)
            {
                LOG(1, "Error: invalid world size argument for -w.\n");
                _parseError = true;
                return;
            }
            _heightmapWorldSize.x = (float)atof(parts[0].c_str());
            _heightmapWorldSize.y = (float)atof(parts[1].c_str());
            _heightmapWorldSize.z = (float)atof(parts[2].c_str());
            if (_heightmapWorldSize.x == 0 || _heightmapWorldSize.y == 0 || _heightmapWorldSize.z == 0)
            {
                LOG(1, "Error: invalid world size argument for -w.\n");
                _parseError = true;
                return;
            }
        }
        break;
    case 'p':
        _fontPreview = true;
        break;
    case 's':
        if (_normalMap)
        {
            (*index)++;
            if (*index >= options.size())
            {
                LOG(1, "Error: missing argument for -s.\n");
                _parseError = true;
                return;
            }
            // Heightmap size
            std::vector<std::string> parts;
            splitString(options[*index].c_str(), &parts);
            if (parts.size() != 2 ||
                (_heightmapResolution[0] = atoi(parts[0].c_str())) <= 0 ||
                (_heightmapResolution[1] = atoi(parts[1].c_str())) <= 0)
            {
                LOG(1, "Error: invalid argument for -s.\n");
                _parseError = true;
                return;
            }
        }
        else
        {
            // Font Sizes
            const char* sizes = NULL;
            if (str.length() > 2)
            {
                char n = str[2];
                if (n > '0' && n <= '9')
                {
                    sizes = str.c_str() + 2;
                }
            }
            else
            {
                (*index)++;
                if (*index < options.size())
                {
                    sizes = options[*index].c_str();
                }
            }

            if (sizes == NULL)
            {
                LOG(1, "Error: invalid format for argument: -s");
                _parseError = true;
                return;
            }

            // Parse comma-separated list of font sizes
            char* ptr = const_cast<char*>(sizes);
            std::string sizeStr;
            while (ptr)
            {
                char* end = strchr(ptr, ',');
                if (end)
                {
                    sizeStr = std::string(ptr, end - ptr);
                    ptr = end + 1;
                }
                else
                {
                    sizeStr = ptr;
                    ptr = NULL;
                }
                if (sizeStr.length() > 0)
                {
                    int size = atoi(sizeStr.c_str());
                    if (size <= 0)
                    {
                        LOG(1, "Error: invalid font size provided: %s", sizeStr.c_str());
                        _parseError = true;
                        return;
                    }
                    _fontSizes.push_back((unsigned int)size);
                }
            }
        }
        break;
    case 't':
        if (str.compare("-t") == 0)
        {
            _textOutput = true;
        }
        else if (str.compare("-tb") == 0)
        {
            if ((*index + 1) >= options.size())
            {
                LOG(1, "Error: -tb requires 1 argument.\n");
                _parseError = true;
                return;
            }
            (*index)++;
            std::string nodeId = options[*index];
            if (nodeId.length() > 0)
            {
                _tangentBinormalId.insert(nodeId);
            }
        }
        else if (str.compare("-textureGutter:none") == 0 || str.compare("-tg:none") == 0)
        {
            _generateTextureGutter = false;
        }
        else if (str.compare("-textureGutter") == 0 || str.compare("-tg") == 0)
        {
            _generateTextureGutter = true;
        }
        break;
    case 'v':
        (*index)++;
        if (*index < options.size())
        {
            __logVerbosity = atoi(options[*index].c_str());
            if (__logVerbosity < 0)
                __logVerbosity = 0;
            else if (__logVerbosity > 4)
                __logVerbosity = 4;
        }
        break;
    default:
        break;
    }
}

void EncoderArguments::setInputfilePath(const std::string& inputPath)
{
    _filePath.assign(getRealPath(inputPath));
}

void EncoderArguments::setOutputfilePath(const std::string& outputPath)
{
    std::string ext = getOutputFileExtension();

    if (outputPath.size() > 0 && outputPath[0] != '\0')
    {
        std::string realPath = getRealPath(outputPath);
        if (endsWith(realPath.c_str(), ext.c_str()))
        {
            _fileOutputPath.assign(realPath);
        }
        else if (endsWith(outputPath.c_str(), "/"))
        {
            std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(_filePath));

            _fileOutputPath.assign(outputPath);
            _fileOutputPath.append(filenameNoExt);
            _fileOutputPath.append(ext);
        }
        else
        {
            std::string filenameNoExt = getFilenameNoExt(getFilenameFromFilePath(realPath));
            int pos = realPath.find_last_of("/");
            if (pos)
            {
                _fileOutputPath = realPath.substr(0, pos);
                _fileOutputPath.append("/");
                _fileOutputPath.append(filenameNoExt);
                _fileOutputPath.append(ext);
            }
        }
    }
}

std::string EncoderArguments::getRealPath(const std::string& filepath)
{
    char path[PATH_MAX + 1]; /* not sure about the "+ 1" */
    realpath(filepath.c_str(), path);
    replace_char(path, '\\', '/');
    return std::string(path);
}

void EncoderArguments::replace_char(char* str, char oldChar, char newChar)
{
    for (; *str != '\0'; ++str)
    {
        if (*str == oldChar)
        {
            *str = newChar;
        }
    }
}

std::string concat(const std::string& a, const char* b)
{
    std::string str(a);
    str.append(b);
    return str;
}

void unittestsEncoderArguments()
{
    std::string dir = EncoderArguments::getRealPath(".");
    std::string exePath = EncoderArguments::getRealPath(".");
    exePath.append("/gameplay-encoder.exe");
    const char* exe = exePath.c_str();
    {
        const char* argv[] = {exe, "-g", "root", "movements", "C:\\Git\\gaming\\GamePlay\\gameplay-encoder\\res\\duck.fbx"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getAnimationId("root"), ("movements")));
        assert(equals(args.getGroupAnimationNodeId()[0], ("root")));
        assert(equals(args.getOutputFilePath(), "C:/Git/gaming/GamePlay/gameplay-encoder/res/duck.gpb"));
    }
    {
        // Test with only input file name (relative)
        const char* argv[] = {exe, "input.fbx"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/input.gpb")));
        equals(args.getOutputDirPath(), dir);
    }
    {
        // Test specifying a relative output path
        const char* argv[] = {exe, "input.fbx", "output.gpb"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
    }
    {
        // Test specifying a relative output path
        const char* argv[] = {exe, "input.fbx", "output.gpb"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
    }
    {
        // Test specifying a relative output path to a directory
        const char* argv[] = {exe, "input.fbx", "stuff/output.gpb"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/stuff/output.gpb")));
    }
    {
        // Test parsing some arguments
        const char* argv[] = {exe, "test.fbx", "-t", "input.fbx", "output.gpb"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
        assert(args.textOutputEnabled());
    }
    {
        // Test output file with no file extension
        const char* argv[] = {exe, "input.fbx", "output"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
    }
    {
        // Test output file with wrong file extension
        const char* argv[] = {exe, "input.fbx", "output.fbx"};
        EncoderArguments args(sizeof(argv) / sizeof(char*), (const char**)argv);
        assert(equals(args.getFilePath(), concat(dir, "/input.fbx")));
        assert(equals(args.getOutputFilePath(), concat(dir, "/output.gpb")));
    }
}

}


dgVector dgCollisionLumpedMassParticles::CalculateContactNormalAndPenetration(const dgVector& worldPosition) const
{
	dgVector contactNormal(dgFloat32(0.0f), dgFloat32(1.0f), dgFloat32(0.0f), dgFloat32(0.0f));
	dgVector otherPoint(worldPosition);
	otherPoint.m_y = dgFloat32(0.0f);
	dgVector point(worldPosition - contactNormal.Scale (m_particleRadius));
	contactNormal.m_w = contactNormal.DotProduct(otherPoint - point).GetScalar();

	return contactNormal;
}

/*
*
*/

#include "dgPhysicsStdafx.h"
#include "dgWorld.h"
#include "dgContact.h"
#include "dgCollision.h"
#include "dgMeshEffect.h"
#include "dgDynamicBody.h"
#include "dgCollisionLumpedMassParticles.h"

#define DG_MINIMIM_ZERO_SPEED			dgFloat32 (1.0e-3f)
#define DG_MINIMIM_PARTCLE_RADIUS		dgFloat32 (1.0f/16.0f)
#define DG_MINIMIM_ZERO_SURFACE			(DG_MINIMIM_PARTCLE_RADIUS * dgFloat32 (0.25f))

dgCollisionLumpedMassParticles::dgCollisionLumpedMassParticles(dgWorld* const world, dgCollisionID collisionID)
	:dgCollisionConvex(world->GetAllocator(), 0, collisionID)
	,m_posit(world->GetAllocator())
	,m_veloc(world->GetAllocator())
	,m_accel(world->GetAllocator())
	,m_externalAccel(world->GetAllocator())
	,m_mass(world->GetAllocator())
	,m_invMass(world->GetAllocator())
	,m_body(NULL)
	,m_totalMass(dgFloat32(1.0f))
	,m_particleRadius(DG_MINIMIM_PARTCLE_RADIUS)
	,m_particlesCount(0)
{
	m_rtti |= dgCollisionLumpedMass_RTTI;
}

dgCollisionLumpedMassParticles::dgCollisionLumpedMassParticles (const dgCollisionLumpedMassParticles& source)
	:dgCollisionConvex(source)
	,m_posit(source.m_posit, source.m_particlesCount)
	,m_veloc(source.m_veloc, source.m_particlesCount)
	,m_accel(source.m_accel, source.m_particlesCount)
	,m_externalAccel(source.m_externalAccel, source.m_particlesCount)
	,m_mass(source.m_mass, source.m_particlesCount)
	,m_invMass(source.m_invMass, source.m_particlesCount)
	,m_body(NULL)
	,m_totalMass(source.m_totalMass)
	,m_particleRadius(source.m_particleRadius)
	,m_particlesCount(source.m_particlesCount)
{
	m_rtti |= dgCollisionLumpedMass_RTTI;
}

dgCollisionLumpedMassParticles::dgCollisionLumpedMassParticles (dgWorld* const world, dgDeserialize deserialization, void* const userData, dgInt32 revisionNumber)
	:dgCollisionConvex(world, deserialization, userData, revisionNumber)
	,m_posit(world->GetAllocator())
	,m_veloc(world->GetAllocator())
	,m_accel(world->GetAllocator())
	,m_externalAccel(world->GetAllocator())
	,m_mass(world->GetAllocator())
	,m_invMass(world->GetAllocator())
	,m_body(NULL)
	,m_totalMass(dgFloat32(1.0f))
	,m_particleRadius (DG_MINIMIM_PARTCLE_RADIUS)
	,m_particlesCount(0)
{
	m_rtti |= dgCollisionLumpedMass_RTTI;
	dgAssert (0);
}

dgCollisionLumpedMassParticles::~dgCollisionLumpedMassParticles(void)
{
}

void dgCollisionLumpedMassParticles::FinalizeBuild()
{
	m_veloc.Resize(m_particlesCount);
	m_accel.Resize(m_particlesCount);
	m_externalAccel.Resize(m_particlesCount);

	dgVector com(dgFloat32(0.0f));
	dgVector* const posit = &m_posit[0];

	dgVector minp(dgFloat32 (1.0e10f));
	dgVector maxp(dgFloat32 (-1.0e10f));
	for (dgInt32 i = 0; i < m_particlesCount; i++) {
		minp = minp.GetMin(posit[i]);
		maxp = maxp.GetMax(posit[i]);
		m_accel[i] = dgVector::m_zero;
		m_veloc[i] = dgVector::m_zero;
		m_externalAccel[i] = dgVector::m_zero;
	}

	// for now use a fix size box
	m_boxSize = dgVector::m_half * (maxp - minp);
	m_boxOrigin = dgVector::m_half * (maxp + minp);
}

void dgCollisionLumpedMassParticles::DebugCollision (const dgMatrix& matrix, dgCollision::OnDebugCollisionMeshCallback callback, void* const userData) const
{
	dgAssert (0);
}

void dgCollisionLumpedMassParticles::SetOwnerAndMassPraperties (dgDynamicBody* const body)
{
	m_body = body;

	dgMatrix matrix (body->GetMatrix());
	dgVector position (matrix.m_posit);
	matrix.m_posit = dgVector::m_wOne;

	dgVector* const posit = &m_posit[0];
	const dgFloat32* const mass = &m_mass[0];

	dgVector xMassSum(dgFloat32(0.0f));
	dgFloat32 massSum = dgFloat32 (0.0f);
	dgMatrix scaledTranform (body->m_collision->GetScaledTransform(matrix));
	for (dgInt32 i = 0; i < m_particlesCount; i++) {
		massSum += mass[i];
		posit[i] = scaledTranform.TransformVector(posit[i]) & dgVector::m_triplexMask;
		xMassSum += posit[i].Scale (mass[i]);
	}
	m_totalMass = massSum;
	dgFloat32 invMass = dgFloat32(1.0f) / massSum;

	body->m_collision->SetScale(dgVector (dgFloat32 (1.0f)));
	body->m_collision->SetLocalMatrix (dgGetIdentityMatrix());
	matrix.m_posit = position;
	body->m_matrix = matrix;
	body->m_localCentreOfMass = xMassSum * invMass;

	body->m_mass = dgVector(dgFloat32(1.0f), dgFloat32(1.0f), dgFloat32(1.0f), m_totalMass);
	body->m_invMass = dgVector(dgFloat32 (1.0f), dgFloat32 (1.0f), dgFloat32 (1.0f), invMass);

}

int dgCollisionLumpedMassParticles::GetCount() const
{
	return m_particlesCount;
}

dgInt32 dgCollisionLumpedMassParticles::GetStrideInByte() const
{
	return sizeof (dgVector);
}

const dgVector* dgCollisionLumpedMassParticles::GetVelocity() const
{
	return &m_veloc[0];
}

const dgVector* dgCollisionLumpedMassParticles::GetPositions() const
{
	return &m_posit[0];
}

const dgVector* dgCollisionLumpedMassParticles::GetAcceleration() const
{
	return &m_accel[0];
}

dgInt32 dgCollisionLumpedMassParticles::CalculateSignature() const
{
	dgAssert (0);
	return 0;
}

void dgCollisionLumpedMassParticles::SetCollisionBBox(const dgVector& p0, const dgVector& p1)
{
	dgAssert (0);
}

void dgCollisionLumpedMassParticles::Serialize(dgSerialize callback, void* const userData) const
{
	dgAssert (0);
}

void dgCollisionLumpedMassParticles::RegisterCollision(const dgBody* const otherBody)
{
}

void dgCollisionLumpedMassParticles::CalcAABB(const dgMatrix& matrix, dgVector& p0, dgVector& p1) const
{
	dgVector origin(matrix.TransformVector(m_boxOrigin));
	dgVector size(matrix.m_front.Abs().Scale(m_boxSize.m_x) + matrix.m_up.Abs().Scale(m_boxSize.m_y) + matrix.m_right.Abs().Scale(m_boxSize.m_z));
	p0 = (origin - size) & dgVector::m_triplexMask;
	p1 = (origin + size) & dgVector::m_triplexMask;
}

dgFloat32 dgCollisionLumpedMassParticles::RayCast(const dgVector& localP0, const dgVector& localP1, dgFloat32 maxT, dgContactPoint& contactOut, const dgBody* const body, void* const userData, OnRayPrecastAction preFilter) const
{
	// for now brute force ray cast
	dgVector p (m_posit[0]);
	dgFloat32 distance2 = dgFloat32 (0.1f);
	for (dgInt32 i = 0; i < m_particlesCount; i ++) {
		dgVector posit (dgPointToRayDistance (m_posit[i], localP0, localP1));
		dgVector step (posit - m_posit[i]);
		dgAssert (step.m_w == dgFloat32 (0.0f));
		dgFloat32 dist2 = step.DotProduct(step).GetScalar();
		if (dist2 < distance2) {
			distance2 = dist2;
			p = m_posit[i];
		}
	}

	dgFloat32 dist = dgFloat32(1.0e10f);
	if (distance2 < dgFloat32(0.1f)) {
		contactOut.m_point = p;
		contactOut.m_normal = dgVector(dgFloat32(0.0f), dgFloat32(1.0f), dgFloat32(0.0f), dgFloat32(0.0f));
		dgVector num(p - localP0);
		dgVector den(localP1 - localP0);
		dgAssert (den.m_w == dgFloat32 (0.0f));
		dist = num.DotProduct(den).GetScalar() / den.DotProduct(den).GetScalar();
	}
	return dist;
}

dgMatrix dgCollisionLumpedMassParticles::CalculateInertiaAndCenterOfMass(const dgMatrix& m_alignMatrix, const dgVector& localScale, const dgMatrix& matrix) const
{
dgAssert (0);
	dgVector com(dgFloat32(0.0f));
	for (dgInt32 i = 0; i < m_particlesCount; i++) {
		com = matrix.RotateVector(m_posit[i] * localScale);
	}
	dgVector den(dgFloat32(1.0f / m_particlesCount));
	dgMatrix inertia(dgGetIdentityMatrix());
	inertia.m_posit = com * den;
	inertia.m_posit.m_w = dgFloat32(1.0f);
	return inertia;
}

/*
dgFloat32 dgCollisionLumpedMassParticles::CalculaleContactPenetration(const dgVector& point, const dgVector& normal) const
{
	dgVector otherPoint (point);
	otherPoint.m_y = dgFloat32 (0.0f);
	dgFloat32 penetration = normal.DotProduct(point - otherPoint).GetScalar();
	return penetration;
}
*/

void dgCollisionLumpedMassParticles::HandleCollision(dgFloat32 timestep, dgVector* const normalDir, dgVector* const normalAccel, dgFloat32* const frictionCoefficient)
{
	const dgMatrix& matrix = m_body->GetCollision()->GetGlobalMatrix();
	dgVector origin(matrix.m_posit);

	dgFloat32 coeficientOfFriction = dgFloat32(0.6f);
	dgFloat32 coeficientOfPenetration = dgFloat32(0.1f);
coeficientOfPenetration *= 1.0f;

	dgVector timestepV(timestep);
	dgFloat32 invTimeStep = dgFloat32 (1.0f) / timestep;
	dgVector* const veloc = &m_veloc[0];
	const dgVector* const accel = &m_accel[0];
	const dgVector* const posit = &m_posit[0];
	const dgVector* const extAccel = &m_externalAccel[0];

static int xxx;
xxx ++;

	for (dgInt32 i = 0; i < m_particlesCount; i++) {
		dgVector normal(dgVector::m_zero);
		dgVector accel1(dgVector::m_zero);
		dgVector tangent0(dgVector::m_zero);
		dgVector tangent1(dgVector::m_zero);

		dgVector contactPosition(origin + posit[i]);
		dgVector contactNormal(CalculateContactNormalAndPenetration (contactPosition));

		dgFloat32 frictionCoef = dgFloat32(0.0f);
		if (contactNormal.m_w > dgFloat32 (0.0f)) {
			dgVector projectedVelocity(veloc[i] + timestepV * (accel[i] + extAccel[i]));
			dgAssert (projectedVelocity.m_w == dgFloat32 (0.0f));
			dgFloat32 projectedNormalSpeed = contactNormal.DotProduct(projectedVelocity).GetScalar();
			if (projectedNormalSpeed < DG_MINIMIM_ZERO_SPEED) {
				if (contactNormal.m_w > DG_MINIMIM_ZERO_SURFACE) {
					normal = contactNormal & dgVector::m_triplexMask;
					dgFloat32 penetration = dgClamp(contactNormal.m_w - DG_MINIMIM_ZERO_SURFACE, dgFloat32(0.0f), dgFloat32(0.125f));
					dgFloat32 s = dgFloat32 (2.0f) * invTimeStep * penetration;
					dgFloat32 a = -s * invTimeStep;
					veloc[i] += normal.Scale (s - veloc[i].DotProduct(normal).GetScalar());
					accel1 = normal.Scale(a);
					frictionCoef = coeficientOfFriction;
				}
			}
		}

		normalDir[i] = normal;
		normalAccel[i] = accel1;
		frictionCoefficient[i] = frictionCoef;
	}
}


Schema* LogicalProject::GetOutputSchema() {
  Schema* schema = GetSchema(plan_context_->attribute_list_);
  return schema;
}

#include <atomic>

/*
 *
 *
 * /CLAIMS/logical_operator/project.cpp
 *
 *  Created on: Sep 22, 2015
 *      Author: casa, ChenLingyun
 *       Email: example@nnthink.com
 *
 * Description:
 * it's a function which takes an input (e.g. a database row) and
 * produces an output (e.g. one of the columns from the row, or
 * perhaps some calculation based on multiple columns).
 */
#define GLOG_NO_ABBREVIATED_SEVERITIES
#include <vector>
#include <map>
#include <string>
#include "../common/expression/expr_column.h"
#include "../logical_operator/logical_project.h"
#include "../logical_operator/logical_operator.h"
#include "../common/ids.h"
#include "../common/data_type.h"
#include "../common/Expression/initquery.h"
#include "../common/log/logging.h"
#include "../common/expression/expr_node.h"
#include "../physical_operator/physical_project.h"

using claims::common::ExprColumn;
using claims::common::ExprNode;
using claims::common::LogicInitCnxt;
using claims::physical_operator::PhysicalProject;
namespace claims {
namespace logical_operator {
LogicalProject::LogicalProject(LogicalOperator* child,
                               vector<QNode*> expression_tree)
    : LogicalOperator(kLogicalProject),
      child_(child),
      expression_tree_(expression_tree),
      plan_context_(NULL) {}
LogicalProject::LogicalProject(LogicalOperator* child,
                               vector<ExprNode*> expr_list)
    : LogicalOperator(kLogicalProject),
      child_(child),
      expr_list_(expr_list),
      plan_context_(NULL) {}
LogicalProject::~LogicalProject() {
  if (NULL != plan_context_) {
    delete plan_context_;
    plan_context_ = NULL;
  }
  if (NULL != child_) {
    delete child_;
    child_ = NULL;
  }
}
// construct a PlanContext from child
PlanContext LogicalProject::GetPlanContext() {
  lock_->acquire();
  PlanContext ret;
  // get the PlanContext of child
  const PlanContext child_plan_context = child_->GetPlanContext();
  /**
   * set commnication_cost and partitioner of the PlanContext to be returned
   * from PlanContext of child
   */
  ret.commu_cost_ = child_plan_context.commu_cost_;
  ret.plan_partitioner_ = child_plan_context.plan_partitioner_;
  std::vector<Attribute> ret_attrs;
  // construct an input schema from attribute list of child
  Schema* input_schema = GetSchema(child_plan_context.attribute_list_);
// get the index of attributes in child PlanContext
/**
 * if the expression type is compare,then the new column will be boolean
 * type,
 * else will be it's actual type according to the variable
 */

#ifdef NEWCONDI
  for (int i = 0; i < expression_tree_.size(); ++i) {
    if (expression_tree_[i]->type == t_qexpr_cmp) {
      InitExprAtLogicalPlan(expression_tree_[i], t_boolean, column_id_,
                            input_schema);
    } else {
      InitExprAtLogicalPlan(expression_tree_[i],
                            expression_tree_[i]->actual_type, column_id_,
                            input_schema);
    }
  }
  // clean the attribute list of PlanContext to be returned
  ret_attrs.clear();

  /**
   * if the return type is a String value,then we have to calculate the length
   * of the string
   * else just construct a column having the same type as the return type
   */
  for (int i = 0; i < expression_tree_.size(); ++i) {
    column_type* column = NULL;
    if (t_string == expression_tree_[i]->return_type_ ||
        t_decimal == expression_tree_[i]->return_type_) {
      column = new column_type(expression_tree_[i]->return_type_,
                               expression_tree_[i]->length);
    } else {
      column = new column_type(expression_tree_[i]->return_type_);
    }
    // set TableID
    const unsigned kTableID = INTERMEIDATE_TABLEID;
    // construct attribute
    Attribute attr_alais(kTableID, i, expression_tree_[i]->alias, column->type,
                         column->size);
    // construct an attribute list
    ret_attrs.push_back(attr_alais);
  }
#else
  ret_attrs.clear();
  LogicInitCnxt licnxt;
  licnxt.schema0_ = input_schema;
  int mid_table_id = 0;
  if (plan_context_ == NULL) {
    mid_table_id = MIDINADE_TABLE_ID++;
  } else {
    mid_table_id = plan_context_->attribute_list_[0].table_id_;
    DELETE_PTR(plan_context_);
  }
  GetColumnToId(child_plan_context.attribute_list_, licnxt.column_id0_);
  for (int i = 0; i < expr_list_.size(); ++i) {
    licnxt.return_type_ = expr_list_[i]->actual_type_;
    expr_list_[i]->InitExprAtLogicalPlan(licnxt);
    ret_attrs.push_back(expr_list_[i]->ExprNodeToAttr(i, mid_table_id));

    // update partition key
    if (t_qcolcumns == expr_list_[i]->expr_node_type_) {
      ExprColumn* column = reinterpret_cast<ExprColumn*>(expr_list_[i]);
      if (ret.plan_partitioner_.get_partition_key().attrName ==
          column->table_name_ + "." + column->column_name_) {
        ret.plan_partitioner_.set_partition_key(ret_attrs[i]);
      }
    }
  }

#endif
  // set the attribute list of the PlanContext to be returned
  ret.attribute_list_ = ret_attrs;
  plan_context_ = new PlanContext();
  // set the PlanContext to be returned
  *plan_context_ = ret;
  lock_->release();
  return ret;
}

// get PlanContext and child physical plan from child ,
PhysicalOperatorBase* LogicalProject::GetPhysicalPlan(
    const unsigned& block_size) {
  GetPlanContext();
  const PlanContext child_plan_context = child_->GetPlanContext();
  PhysicalOperatorBase* child = child_->GetPhysicalPlan(block_size);
  PhysicalProject::State state;

  // assign some attributes to the state_
  state.block_size_ = block_size;
  state.child_ = child;
  state.schema_input_ = GetSchema(child_plan_context.attribute_list_);
  state.schema_output_ = GetOutputSchema();
  state.expr_tree_ = expression_tree_;
  state.expr_list_ = expr_list_;
  return new PhysicalProject(state);
}

// construct a schema from attribute list of PlanContext

// Print the whole logical operation tree
void LogicalProject::Print(int level) const {
  cout << setw(level * kTabSize) << " "
       << "Project:" << endl;
  LOG(INFO) << "Project:\n" << endl;
#ifdef NEWCONDI
  for (int i = 0; i < expression_tree_.size(); i++) {
    printf("%s\n", expression_tree_[i]->alias.c_str());
    LOG(INFO) << expression_tree_[i]->alias.c_str() << endl;
  }
#else
  GetPlanContext();
  cout << setw(level * kTabSize) << " "
       << "[Partition info: "
       << plan_context_->plan_partitioner_.get_partition_key().attrName
       << " table_id= "
       << plan_context_->plan_partitioner_.get_partition_key().table_id_
       << " column_id= "
       << plan_context_->plan_partitioner_.get_partition_key().index << " ]"
       << endl;
  ++level;
  for (int i = 0; i < expr_list_.size(); ++i) {
    cout << setw(level * kTabSize) << " " << expr_list_[i]->alias_ << endl;
  }
  --level;
#endif
  child_->Print(level);
}
void LogicalProject::PruneProj(set<string>& above_attrs) {
  for (int i = 0, size = expr_list_.size(); i < size; ++i) {
    expr_list_[i]->GetUniqueAttr(above_attrs);
  }
  child_->PruneProj(above_attrs);
}

}  // namespace logical_operator
}  // namespace claims


Module::~Module()
{
}

bool
Module::get_status()const
{
	return status_;
}

bool
Module::start()
{
	if(!get_status())
		status_=start_vfunc();
	return get_status();
}

bool
Module::stop()
{
	if(get_status() && count()<=1 && stop_vfunc())
	{
		status_=false;
		return true;
	}
	return false;
}


inline void fftScale(int rndMode, int shift, T_accRef<float>& accum) {
    accum.real = (accum.real + (float)(1 << (shift - 1))) / (float)(1 << (shift));
}

/*
 */
#include <math.h>
#include <float.h>
#include <stdio.h>
#include <stdlib.h>
#include "fft_ifft_dit_1ch_ref.hpp"
#include "fft_ifft_dit_twiddle_lut.h"
#include "fir_ref_utils.hpp"

namespace xf {
namespace dsp {
namespace aie {
namespace fft {
namespace dit_1ch {
/*
  FFT/iFFT DIT single channel reference model
*/
static constexpr unsigned int kMaxPointSize = 4096;

// Utility functions for FFT/iFFT single channel reference model

template <typename TT_TWIDDLE>
TT_TWIDDLE* fnGetTwiddleMasterBase(){};
template <>
cint16* fnGetTwiddleMasterBase<cint16>() {
    return (cint16*)twiddle_master_cint16;
};
template <>
cfloat* fnGetTwiddleMasterBase<cfloat>() {
    return (cfloat*)twiddle_master_cfloat;
};

// function to query type -used to avoid template specializing the whole class.
template <typename T_D>
inline constexpr bool is_cfloat() {
    return false;
};
template <>
inline constexpr bool is_cfloat<cfloat>() {
    return true;
};

// Function has to be fully specialized, which is excessive, so integer template parameters are used simply as
// parameters (function arguments) to reduce duplication
template <typename TT_TWIDDLE>
TT_TWIDDLE get_twiddle(int i, unsigned int TP_POINT_SIZE, unsigned int TP_FFT_NIFFT) {
    return 0; // never used
}
template <>
cint16 get_twiddle<cint16>(int i, unsigned int TP_POINT_SIZE, unsigned int TP_FFT_NIFFT) {
    int step = kMaxPointSize / TP_POINT_SIZE;
    cint16 raw_twiddle = twiddle_master_cint16[i * step];
    if (TP_FFT_NIFFT == 0) {
        raw_twiddle.imag = -raw_twiddle.imag;
    }
    return raw_twiddle;
}
template <>
cint32 get_twiddle<cint32>(int i, unsigned int TP_POINT_SIZE, unsigned int TP_FFT_NIFFT) {
    int step = kMaxPointSize / TP_POINT_SIZE;
    cint32 raw_twiddle = twiddle_master_cint32[i * step];
    if (TP_FFT_NIFFT == 0) {
        raw_twiddle.imag = -raw_twiddle.imag;
    }
    return raw_twiddle;
}
template <>
cfloat get_twiddle<cfloat>(int i, unsigned int TP_POINT_SIZE, unsigned int TP_FFT_NIFFT) {
    int step = kMaxPointSize / TP_POINT_SIZE;
    cfloat raw_twiddle = twiddle_master_cfloat[i * step];
    if (TP_FFT_NIFFT == 0) {
        raw_twiddle.imag = -raw_twiddle.imag;
    }
    return raw_twiddle;
}

// converts the input type to the type used for internal data processing
template <typename TT_DATA>
inline T_int_data<TT_DATA> castInput(TT_DATA sampleIn) {
    T_int_data<TT_DATA> retVal; // default to mute warnings
    return retVal;
}
template <>
inline T_int_data<int16> castInput<int16>(int16 sampleIn) {
    T_int_data<int16> retVal;
    retVal.real = sampleIn;
    retVal.imag = 0;
    return retVal;
}
template <>
inline T_int_data<cint16> castInput<cint16>(cint16 sampleIn) {
    T_int_data<cint16> retVal;
    retVal.real = sampleIn.real;
    retVal.imag = sampleIn.imag;
    return retVal;
}
template <>
inline T_int_data<int32> castInput<int32>(int32 sampleIn) {
    T_int_data<int32> retVal;
    retVal.real = sampleIn;
    retVal.imag = 0;
    return retVal;
}
template <>
inline T_int_data<cint32> castInput<cint32>(cint32 sampleIn) {
    T_int_data<cint32> retVal;
    retVal.real = sampleIn.real;
    retVal.imag = sampleIn.imag;
    return retVal;
}
template <>
inline T_int_data<float> castInput<float>(float sampleIn) {
    T_int_data<float> retVal;
    retVal.real = sampleIn;
    retVal.imag = 0;
    return retVal;
}
template <>
inline T_int_data<cfloat> castInput<cfloat>(cfloat sampleIn) {
    T_int_data<cfloat> retVal;
    retVal.real = sampleIn.real;
    retVal.imag = sampleIn.imag;
    return retVal;
}

// Derivation of base type of a complex type.
template <typename TT_TWIDDLE = cint16>
struct T_base_type_struct {
    using T_base_type = int16;
};
template <>
struct T_base_type_struct<cint32> {
    using T_base_type = int32;
};
template <>
struct T_base_type_struct<cfloat> {
    using T_base_type = float;
};

// fnMaxPos Maximum positive value of a twiddle component. This is arcane because twiddle is a complex type, but the
template <typename TT_TWIDDLE = cint16>
typename T_base_type_struct<TT_TWIDDLE>::T_base_type fnMaxPos() {
    return C_PMAX16;
}
template <>
T_base_type_struct<cint32>::T_base_type fnMaxPos<cint32>() {
    return C_PMAX32;
}
template <>
T_base_type_struct<cfloat>::T_base_type fnMaxPos<cfloat>() {
    return 3.4028234664e+32;
}

// fnMaxNeg Maximum negative value of a twiddle component. This is arcane because twiddle is a complex type, but the
template <typename TT_TWIDDLE = cint16>
typename T_base_type_struct<TT_TWIDDLE>::T_base_type fnMaxNeg() {
    return C_NMAX16;
}
template <>
T_base_type_struct<cint32>::T_base_type fnMaxNeg<cint32>() {
    return C_NMAX32;
}
template <>
T_base_type_struct<cfloat>::T_base_type fnMaxNeg<cfloat>() {
    return -3.4028234664e+32;
}

// converts the input type to the type used for internal data processing
template <typename TT_DATA>
inline TT_DATA castOutput(T_int_data<TT_DATA> sampleIn, const unsigned shift) {
    TT_DATA retVal; // default to mute warnings
    return retVal;
}
template <>
inline int16 castOutput<int16>(T_int_data<int16> sampleIn, const unsigned shift) {
    int16 retVal;
    // rounding is performed in the radix stages
    retVal = sampleIn.real;
    if (retVal >= C_PMAX16) {
        retVal = C_PMAX16;
    } else if (retVal < C_NMAX16) {
        retVal = C_NMAX16;
    }
    return retVal;
}
template <>
inline cint16 castOutput<cint16>(T_int_data<cint16> sampleIn, const unsigned shift) {
    cint16 retVal;
    // rounding is performed in the radix stages
    retVal.real = sampleIn.real;
    retVal.imag = sampleIn.imag;
    if (sampleIn.real >= C_PMAX16) {
        retVal.real = C_PMAX16;
    } else if (sampleIn.real < C_NMAX16) {
        retVal.real = C_NMAX16;
    }
    if (sampleIn.imag >= C_PMAX16) {
        retVal.imag = C_PMAX16;
    } else if (sampleIn.imag < C_NMAX16) {
        retVal.imag = C_NMAX16;
    }
    return retVal;
}
template <>
inline int32 castOutput<int32>(T_int_data<int32> sampleIn, const unsigned shift) {
    int32 retVal;
    // rounding is performed in the radix stages
    retVal = sampleIn.real;
    if (retVal >= C_PMAX32) {
        retVal = C_PMAX32;
    } else if (retVal < C_NMAX32) {
        retVal = C_NMAX32;
    }
    return retVal;
}
template <>
inline cint32 castOutput<cint32>(T_int_data<cint32> sampleIn, const unsigned shift) {
    cint32 retVal;
    // rounding is performed in the radix stages
    retVal.real = sampleIn.real;
    retVal.imag = sampleIn.imag;
    if (retVal.real >= C_PMAX32) {
        retVal.real = C_PMAX32;
    } else if (retVal.real < C_NMAX32) {
        retVal.real = C_NMAX32;
    }
    if (retVal.imag >= C_PMAX32) {
        retVal.imag = C_PMAX32;
    } else if (retVal.imag < C_NMAX32) {
        retVal.imag = C_NMAX32;
    }
    return retVal;
}
template <>
inline float castOutput<float>(T_int_data<float> sampleIn, const unsigned shift) {
    float retVal;
    // rounding is performed in the radix stages
    retVal = sampleIn.real;
    return retVal;
}
template <>
inline cfloat castOutput<cfloat>(T_int_data<cfloat> sampleIn, const unsigned shift) {
    cfloat retVal;
    // rounding is performed in the radix stages
    retVal.real = sampleIn.real;
    retVal.imag = sampleIn.imag;
    return retVal;
}

template <typename TT_DATA, typename TT_TWIDDLE>
inline void btflynonbitacc(TT_TWIDDLE twiddle,
                           T_int_data<TT_DATA> A,
                           T_int_data<TT_DATA> B,
                           T_int_data<TT_DATA>& outA,
                           T_int_data<TT_DATA>& outB) {
    return;
}
template <>
inline void btflynonbitacc<cint16, cint16>(
    cint16 twiddle, T_int_data<cint16> A, T_int_data<cint16> B, T_int_data<cint16>& outA, T_int_data<cint16>& outB) {
    T_int_data<cint16> rotB, Ao, Bo;
    rotB.real = (int32)(((int64)twiddle.real * B.real - (int64)twiddle.imag * B.imag + 16384) >> 15);
    rotB.imag = (int32)(((int64)twiddle.real * B.imag + (int64)twiddle.imag * B.real + 16384) >> 15);
#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
    printf("rotB = (%6d, %6d), ", rotB.real, rotB.imag);
#endif //_DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_

    Ao.real = A.real + rotB.real;
    Ao.imag = A.imag + rotB.imag;
    Bo.real = A.real - rotB.real;
    Bo.imag = A.imag - rotB.imag;
    outA = Ao;
    outB = Bo;
}

//----------------log2 point size
template <unsigned int TP_POINT_SIZE>
inline constexpr unsigned int getPointSizePower() {
    switch (TP_POINT_SIZE) {
        case 16:
            return 4;
            break;
        case 32:
            return 5;
            break;
        case 64:
            return 6;
            break;
        case 128:
            return 7;
            break;
        case 256:
            return 8;
            break;
        case 512:
            return 9;
            break;
        case 1024:
            return 10;
            break;
        case 2048:
            return 11;
            break;
        case 4096:
            return 12;
            break;
        case 8192:
            return 13;
            break;
        case 16384:
            return 14;
            break;
        case 32768:
            return 15;
            break;
        case 65536:
            return 16;
            break;
        default:
            printf("Error in pointSizePower\n");
            return 0;
            break;
    }
}

unsigned int bitRev(unsigned int len, unsigned int val) {
    unsigned int retVal = 0;
    unsigned int ip = val;
    for (int i = 0; i < len; i++) {
        retVal = retVal << 1;
        if (ip % 2 == 1) {
            retVal++;
        }
        ip >>= 1;
    }
    return retVal;
}
inline void fftScale(int rndMode, int shift, T_accRef<int16>& accum) {
    // cint64_t ret;
    accum.real = rounding(rndMode, shift, accum.real);
};

inline void fftScale(int rndMode, int shift, T_accRef<int32>& accum) {
    // cint64_t ret;
    accum.real = rounding(rndMode, shift, accum.real);
};

inline void fftScale(int rndMode, int shift, T_accRef<cint32>& accum) {
    // cint64_t ret;
    accum.real = rounding(rndMode, shift, accum.real);
    accum.imag = rounding(rndMode, shift, accum.imag);
};
inline void fftScale(int rndMode, int shift, T_accRef<cint16>& accum) {
    // cint64_t ret;
    accum.real = rounding(rndMode, shift, accum.real);
    accum.imag = rounding(rndMode, shift, accum.imag);
};

// Rounding and shift - do apply to float in the FFT

;
inline void fftScale(int rndMode, int shift, T_accRef<cfloat>& accum) {
    accum.real = (accum.real + (float)(1 << (shift - 1))) / (float)(1 << (shift));
    accum.imag = (accum.imag + (float)(1 << (shift - 1))) / (float)(1 << (shift));
};

// Templatized complex multiply, returning uniform cint64.
// inv is actually compile-time constant, but passed here as a run-time because it reduces code verbosity.
template <typename T_D, typename T_TW>
cint64 cmpy(T_D d, T_TW tw, bool inv) {
    return {0, 0};
};
template <>
cint64 cmpy<cint16, cint16>(cint16 d, cint16 tw, bool inv) {
    cint64 retVal;
    if (inv == true) {
        retVal.real = (int64)d.real * (int64)tw.real + (int64)d.imag * (int64)tw.imag;
        retVal.imag = (int64)d.imag * (int64)tw.real - (int64)d.real * (int64)tw.imag;
    } else {
        retVal.real = (int64)d.real * (int64)tw.real - (int64)d.imag * (int64)tw.imag;
        retVal.imag = (int64)d.imag * (int64)tw.real + (int64)d.real * (int64)tw.imag;
    }
    return retVal;
}
template <>
cint64 cmpy<cint32, cint16>(cint32 d, cint16 tw, bool inv) {
    cint64 retVal;
    if (inv == true) {
        retVal.real = (int64)d.real * (int64)tw.real + (int64)d.imag * (int64)tw.imag;
        retVal.imag = (int64)d.imag * (int64)tw.real - (int64)d.real * (int64)tw.imag;
    } else {
        retVal.real = (int64)d.real * (int64)tw.real - (int64)d.imag * (int64)tw.imag;
        retVal.imag = (int64)d.imag * (int64)tw.real + (int64)d.real * (int64)tw.imag;
    }
    return retVal;
}

// templatized butterfly function.
template <typename TI_D, typename T_TW>
void btfly(T_int_data<TI_D>& q0,
           T_int_data<TI_D>& q1,
           T_int_data<TI_D> d0,
           T_int_data<TI_D> d1,
           T_TW tw,
           bool inv,
           unsigned int shift) {
    printf("wrong\n");
}; // assumes downshift by weight of twiddle type, e.g. 15 for cint16

template <>
void btfly<cfloat, cfloat>(T_int_data<cfloat>& q0,
                           T_int_data<cfloat>& q1,
                           T_int_data<cfloat> d0,
                           T_int_data<cfloat> d1,
                           cfloat tw,
                           bool inv,
                           unsigned int shift) {
    cfloat d1up;
    cfloat d1rot;
    d1rot.real = d0.real * tw.real - d0.imag * tw.imag;
    d1rot.imag = d0.imag * tw.real + d0.real * tw.imag;
#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
    printf("32 d1rot = (%f, %f)\n", d1rot.real, d1rot.imag);
#endif // _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
    q0.real = d0.real + d1rot.real;
    q0.imag = d0.imag + d1rot.imag;
    q1.real = d0.real - d1rot.real;
    q1.imag = d0.imag - d1rot.imag;
};

template <>
void btfly<cint32, cint16>(T_int_data<cint32>& q0,
                           T_int_data<cint32>& q1,
                           T_int_data<cint32> d0,
                           T_int_data<cint32> d1,
                           cint16 tw,
                           bool inv,
                           unsigned int shift) {
    cint32 d1up;
    cint32 d1rot;
    cint64 d1rot64;
    const int64 kRoundConst = ((int64)1 << (shift - 1));
    d1up.real = (int32)d1.real;
    d1up.imag = (int32)d1.imag;
    const unsigned int shft = 15; // from cint16 for twiddle
    d1rot64 = cmpy<cint32, cint16>(d1up, tw, inv);
#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
    d1rot.real = (int32)(d1rot64.real);
    d1rot.imag = (int32)(d1rot64.imag);
#endif // _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
    q0.real = (int32)((((int64)d0.real << shft) + d1rot64.real + kRoundConst) >> shift);
    q0.imag = (int32)((((int64)d0.imag << shft) + d1rot64.imag + kRoundConst) >> shift);
    q1.real = (int32)((((int64)d0.real << shft) - d1rot64.real + kRoundConst) >> shift);
    q1.imag = (int32)((((int64)d0.imag << shft) - d1rot64.imag + kRoundConst) >> shift);
};

template <>
void btfly<cint16, cint16>(T_int_data<cint16>& q0,
                           T_int_data<cint16>& q1,
                           T_int_data<cint16> d0,
                           T_int_data<cint16> d1,
                           cint16 tw,
                           bool inv,
                           unsigned int shift) // the upshift variant
{
    cint32 d0up;
    cint32 d1up;
    cint64 d1rot64;
    cint32 d1rot;
    const int64 kRoundConst = ((int64)1 << (shift - 1));
    const unsigned int shft =
        15; // from cint16 for twiddle.Not to be confused with shift which can include scaling factor
    d0up.real = (int32)d0.real;
    d0up.imag = (int32)d0.imag;
    d1up.real = (int32)d1.real;
    d1up.imag = (int32)d1.imag;
    d1rot64 = cmpy<cint32, cint16>(d1up, tw, inv);
#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
    d1rot.real = (int32)(d1rot64.real);
    d1rot.imag = (int32)(d1rot64.imag);
#endif // _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
    q0.real = (int32)((((int64)d0up.real << shft) + d1rot64.real + kRoundConst) >> shift);
    q0.imag = (int32)((((int64)d0up.imag << shft) + d1rot64.imag + kRoundConst) >> shift);
    q1.real = (int32)((((int64)d0up.real << shft) - d1rot64.real + kRoundConst) >> shift);
    q1.imag = (int32)((((int64)d0up.imag << shft) - d1rot64.imag + kRoundConst) >> shift);
};

// templatized Radix2 stage
// First stage in DIT has trivial twiddles (1,0), but this is the one twiddle which isn't exact, so cannot be treated as
template <typename TT_DATA,
          typename TT_TWIDDLE, // type of coefficients           (e.g. int16, cint32)
          unsigned int TP_POINT_SIZE,
          unsigned int TP_FFT_NIFFT,
          unsigned int TP_SHIFT,
          unsigned int TP_DYN_PT_SIZE,
          unsigned int TP_WINDOW_VSIZE>
void fft_ifft_dit_1ch_ref<TT_DATA, TT_TWIDDLE, TP_POINT_SIZE, TP_FFT_NIFFT, TP_SHIFT, TP_DYN_PT_SIZE, TP_WINDOW_VSIZE>::
    r2StageInt(
        T_int_data<TT_DATA>* samplesA, T_int_data<TT_DATA>* samplesB, TT_TWIDDLE* twiddles, int pptSize, bool inv) {
#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
    printf("Entering r2StageInt\n");
#endif //_DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
    int ptSize = TP_DYN_PT_SIZE == 0 ? TP_POINT_SIZE : pptSize;
    T_int_data<TT_DATA> sam1, sam2, sam2rot;
    int64 sum;
    TT_TWIDDLE tw;
    const unsigned int shift = 15;
    const unsigned int round_const = (1 << (shift - 1));
    for (int op = 0; op < (ptSize >> 1); op++) {
        tw.real = twiddles[0].real;
        tw.imag = inv ? -twiddles[0].imag : twiddles[0].imag;
        sam1.real = (int64)samplesA[2 * op].real << shift;
        sam1.imag = (int64)samplesA[2 * op].imag << shift;
        sam2 = samplesA[2 * op + 1];
        if (inv) {
            sam2rot.real = (int64)sam2.real * tw.real + (int64)sam2.imag * tw.imag;
            sam2rot.imag = (int64)sam2.imag * tw.real - (int64)sam2.real * tw.imag;
        } else {
            sam2rot.real = (int64)sam2.real * tw.real - (int64)sam2.imag * tw.imag;
            sam2rot.imag = (int64)sam2.real * tw.imag + (int64)sam2.imag * tw.real;
        }
        sum = (int64)sam1.real + (int64)sam2rot.real + (int64)round_const;
        samplesB[2 * op].real = (int32)(sum >> shift);
        samplesB[2 * op].imag = (int32)(((int64)sam1.imag + (int64)sam2rot.imag + (int64)round_const) >> shift);
        samplesB[2 * op + 1].real = (int32)(((int64)sam1.real - (int64)sam2rot.real + (int64)round_const) >> shift);
        samplesB[2 * op + 1].imag = (int32)(((int64)sam1.imag - (int64)sam2rot.imag + (int64)round_const) >> shift);
    }
}

template <typename TT_DATA,
          typename TT_TWIDDLE, // type of coefficients           (e.g. int16, cint32)
          unsigned int TP_POINT_SIZE,
          unsigned int TP_FFT_NIFFT,
          unsigned int TP_SHIFT,
          unsigned int TP_DYN_PT_SIZE,
          unsigned int TP_WINDOW_VSIZE>
void fft_ifft_dit_1ch_ref<TT_DATA, TT_TWIDDLE, TP_POINT_SIZE, TP_FFT_NIFFT, TP_SHIFT, TP_DYN_PT_SIZE, TP_WINDOW_VSIZE>::
    r2StageFloat(T_int_data<TT_DATA>* samplesA,
                 T_int_data<TT_DATA>* samplesB,
                 TT_TWIDDLE* twiddles,
                 unsigned int rank,
                 int pptSize,
                 bool inv) {
    int ptSize = TP_DYN_PT_SIZE == 0 ? TP_POINT_SIZE : pptSize;
    constexpr unsigned int kRadix = 2;
    unsigned int opLowMask = (1 << rank) - 1;
    unsigned int opHiMask = ptSize - 1 - opLowMask;
    T_int_data<cfloat> sam1, sam2, sam2rot;
    unsigned int inIndex[kRadix];
    cfloat tw;
    unsigned int temp1, temp2, twIndex;
    for (int op = 0; op < (ptSize >> 1); op++) {
        for (int i = 0; i < 2; i++) {
            inIndex[i] = ((op & opHiMask) << 1) + (i << rank) + (op & opLowMask);
        }
        temp1 = inIndex[0] << (kMaxLogPtSize - 1 - rank);
        temp2 = temp1 & ((1 << (kMaxLogPtSize - 1)) - 1);
        twIndex = temp2;
        tw.real = twiddles[twIndex].real;
        tw.imag = inv ? -twiddles[twIndex].imag : twiddles[twIndex].imag;
        sam1.real = samplesA[inIndex[0]].real;
        sam1.imag = samplesA[inIndex[0]].imag;
        sam2.real = samplesA[inIndex[1]].real;
        sam2.imag = samplesA[inIndex[1]].imag;
        samplesB[inIndex[0]].real = +sam1.real + (-sam2.imag * tw.imag + sam2.real * tw.real); // sam2rot.real;
        samplesB[inIndex[0]].imag = +sam1.imag + (+sam2.imag * tw.real + sam2.real * tw.imag); // sam2rot.imag;
        samplesB[inIndex[1]].real = +sam1.real + (+sam2.imag * tw.imag - sam2.real * tw.real); // sam2rot.real;
        samplesB[inIndex[1]].imag = +sam1.imag + (-sam2.imag * tw.real - sam2.real * tw.imag); // sam2rot.imag;
#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
        printf("index 0 = %d, index 1 = %d, twIndex = %d, tw = (%f, %f)", inIndex[0], inIndex[1], twIndex, tw.real,
               tw.imag);
#endif //_DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
    }
}

template <typename TT_DATA,
          typename TT_TWIDDLE, // type of coefficients           (e.g. int16, cint32)
          unsigned int TP_POINT_SIZE,
          unsigned int TP_FFT_NIFFT,
          unsigned int TP_SHIFT,
          unsigned int TP_DYN_PT_SIZE,
          unsigned int TP_WINDOW_VSIZE>
void fft_ifft_dit_1ch_ref<TT_DATA, TT_TWIDDLE, TP_POINT_SIZE, TP_FFT_NIFFT, TP_SHIFT, TP_DYN_PT_SIZE, TP_WINDOW_VSIZE>::
    r4StageInt(T_int_data<TT_DATA>* samplesIn,
               TT_TWIDDLE* twiddles1,
               TT_TWIDDLE* twiddles2,
               unsigned int n,
               unsigned int r,
               unsigned int shift,
               unsigned int rank,
               T_int_data<TT_DATA>* samplesOut,
               int pptSize,
               bool inv) {
    int ptSize = TP_DYN_PT_SIZE == 0 ? TP_POINT_SIZE : pptSize;
    constexpr unsigned int kMaxPointSize = 4096;
    constexpr unsigned int kRadix = 4;    // actually spoofed by 4 radix2 operations.
    constexpr unsigned int stdShift = 15; // derived from cint16's binary point position.
    T_int_data<TT_DATA> sam0, sam1, sam2, sam3;
    TT_TWIDDLE tw[kRadix];
    unsigned int inIndex[kRadix];
    unsigned int twIndex[kRadix];
    unsigned int temp1, temp2;
    cint32 sam2raw, sam3raw;
    cint64 sam2rot, sam3rot;
    cint64 a0, a1, a2, a3, o0, o1, o2, o3;
    T_int_data<TT_DATA> y0, y1, y2, y3;
    T_int_data<TT_DATA> yd0, yd1, yd2, yd3;
    T_int_data<TT_DATA> z0, z1, z2, z3;
    cint64 y1rot, y3rot;
    unsigned int opLowMask = (1 << rank) - 1;
    unsigned int opHiMask = ptSize - 1 - opLowMask;
    const unsigned int round_const = (1 << (shift - 1));

#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
#endif //_DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_

    for (int op = 0; op < (ptSize >> 2); op++) {
        for (int i = 0; i < 4; i++) {
            inIndex[i] = ((op & opHiMask) << 2) + (i << rank) + (op & opLowMask);
        }
        temp1 = inIndex[0] << (kMaxLogPtSize - 1 - rank);
        temp2 = temp1 & ((1 << (kMaxLogPtSize - 1)) - 1);
        twIndex[0] = temp2;

        temp1 = inIndex[1] << (kMaxLogPtSize - 1 - rank);
        temp2 = temp1 & ((1 << (kMaxLogPtSize - 1)) - 1);
        twIndex[1] = temp2;

        temp1 = inIndex[2] << (kMaxLogPtSize - 1 - (rank + 1));
        temp2 = temp1 & ((1 << (kMaxLogPtSize - 1)) - 1);
        twIndex[2] = temp2;

        temp1 = inIndex[3] << (kMaxLogPtSize - 1 - (rank + 1));
        temp2 = temp1 & ((1 << (kMaxLogPtSize - 1)) - 1);
        twIndex[3] = temp2;
        for (int i = 0; i < 4; i++) {
            tw[i].real = twiddles1[twIndex[i]].real;
            tw[i].imag = twiddles1[twIndex[i]].imag;
        }
// for second rank butterflies, minus j intrinsic is used, but sometimes due to saturation, table entries in different
// quadrants are not exactly
// the same as each other rotated by j, so it is necessary to mimic the UUT behaviour here
#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
#endif //_DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
        if (twIndex[2] >= kMaxPointSize >> 2) {
            twIndex[2] -= (kMaxPointSize >> 2);
            tw[2].real = twiddles2[twIndex[2]].imag;
            tw[2].imag = -twiddles2[twIndex[2]].real;
        }
        if (twIndex[3] >= kMaxPointSize >> 2) {
            twIndex[3] -= (kMaxPointSize >> 2);
            tw[3].real = twiddles2[twIndex[3]].imag;
            tw[3].imag = -twiddles2[twIndex[3]].real;
        }

#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
#endif //_DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_

        sam0 = samplesIn[inIndex[0]];
        sam1 = samplesIn[inIndex[1]];
        sam2 = samplesIn[inIndex[2]];
        sam3 = samplesIn[inIndex[3]];
#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
#endif //_DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
#endif //_DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
        btfly<TT_DATA, TT_TWIDDLE>(y0, y1, sam0, sam1, tw[0], inv, stdShift);
        btfly<TT_DATA, TT_TWIDDLE>(y2, y3, sam2, sam3, tw[1], inv, stdShift);
#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
//    printf("End of first R2 y0  = (%d, %d), y1  = (%d, %d), y2  = (%d, %d), y3  =
#endif //_DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
        btfly<TT_DATA, TT_TWIDDLE>(z0, z2, y0, y2, tw[2], inv, shift);
        btfly<TT_DATA, TT_TWIDDLE>(z1, z3, y1, y3, tw[3], inv, shift);
#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
// printf("End of second R2 z0  = (%d, %d), z1  = (%d, %d), z2  = (%d, %d), z3  =
#endif //_DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
        samplesOut[inIndex[0]] = z0;
        samplesOut[inIndex[1]] = z1;
        samplesOut[inIndex[2]] = z2;
        samplesOut[inIndex[3]] = z3;
    }
}

// Bit-accurate REF FFT DIT function
template <typename TT_DATA,
          typename TT_TWIDDLE, // type of coefficients           (e.g. int16, cint32)
          unsigned int TP_POINT_SIZE,
          unsigned int TP_FFT_NIFFT,
          unsigned int TP_SHIFT,
          unsigned int TP_DYN_PT_SIZE,
          unsigned int TP_WINDOW_VSIZE>
void fft_ifft_dit_1ch_ref<TT_DATA, TT_TWIDDLE, TP_POINT_SIZE, TP_FFT_NIFFT, TP_SHIFT, TP_DYN_PT_SIZE, TP_WINDOW_VSIZE>::
    fft(input_window<TT_DATA>* inWindow, output_window<TT_DATA>* outWindow) {
    constexpr unsigned int kMaxPtSizePwr = 12;                   // largest is 4096 = 1<<12;
    constexpr unsigned int kMinPtSizePwr = 4;                    // largest is 16 = 1<<4;
    constexpr unsigned int kHeaderSize = 32 / (sizeof(TT_DATA)); // dynamic point size header size in samples
    constexpr unsigned int kPtSizePwr = fnGetPointSizePower<TP_POINT_SIZE>();
    constexpr unsigned int kScaleFactor = kPtSizePwr - 1; // 1 is for initial rotation factor of 1/sqrt(2).
    constexpr unsigned int kSampleRanks = kRanks + 1;

    constexpr unsigned int kR2Stages =
        std::is_same<TT_DATA, cfloat>::value ? kPtSizePwr : (kPtSizePwr % 2 == 1 ? 1 : 0); // There is one radix 2 stage
                                                                                // if we have an odd power of
                                                                                // cfloat all stages are R2.
    constexpr unsigned int kR4Stages = std::is_same<TT_DATA, cfloat>::value ? 0 : kPtSizePwr / 2;
    constexpr unsigned int shift = 15; // unsigned weight (binary point position) of TT_TWIDDLE
    unsigned int stageShift = 0;

    TT_DATA sampleIn;
    T_int_data<TT_DATA> rotB; // Sample B after rotation.
    TT_TWIDDLE twiddle;
    TT_TWIDDLE twiddles[1 << (kMaxPtSizePwr - 1)];
    T_int_data<TT_DATA> chess_storage(% chess_alignof(cint32)) samplesStoreA[TP_POINT_SIZE];
    T_int_data<TT_DATA> chess_storage(% chess_alignof(cint32)) samplesStoreB[TP_POINT_SIZE];
    T_int_data<TT_DATA> inSampleA, inSampleB, outSampleA, outSampleB;
    unsigned int posLoMask, posHiMask;
    unsigned int twiddleMask, twiddleIndex, twiddlePos;
    unsigned int posLo, posHi;
    unsigned int posA, posB;
    unsigned int rank = 0;
    TT_DATA* headerPtr;
    TT_DATA header;
    int16 ptSizePwr =
        kPtSizePwr;      // default to static point size value. May be overwritten if dynamic point size selected.
    TT_DATA dummyttdata; // used to consume blank data in header.
    unsigned int r2Stages =
        kR2Stages; // default to static point size value. May be overwritten if dynamic point size selected.
    unsigned int r4Stages =
        kR4Stages; // default to static point size value. May be overwritten if dynamic point size selected.
    unsigned int ptSize =
        TP_POINT_SIZE; // default to static point size value. May be overwritten if dynamic point size selected.
    bool inv = TP_FFT_NIFFT == 1 ? false : true; // may be overwritten if dyn_pt_size is set
    TT_DATA headerOut;

    T_accRef<T_int_data<TT_DATA> > accum;
    T_int_data<TT_DATA> *samplesA, *samplesB, *tempPtr;
    const TT_TWIDDLE* twiddle_master = fnGetTwiddleMasterBase<TT_TWIDDLE>();

    if
        constexpr(TP_DYN_PT_SIZE == 1) {
            headerPtr = (TT_DATA*)inWindow->ptr;
            header = *headerPtr++; // saved for later when output to outWindow
            window_writeincr(outWindow, header);
            inv = header.real == 0 ? true : false;
            header = *headerPtr++; // saved for later when output to outWindow
            ptSizePwr = (int32)header.real;
            window_writeincr(outWindow, header);
            dummyttdata = nullElem<TT_DATA>();
            for (int i = 2; i < kHeaderSize - 1; i++) {
                window_writeincr(outWindow, dummyttdata);
            }
            window_writeincr(outWindow, dummyttdata); // Status word. 0 indicated all ok.
            window_incr(inWindow, kHeaderSize);
            // override values set for constant point size with values derived from the header in a dynamic point size
            // frame
            r2Stages = std::is_same<TT_DATA, cfloat>::value
                           ? ptSizePwr
                           : (ptSizePwr % 2 == 1 ? 1 : 0); // There is one radix 2 stage if we have an odd power of 2
                                                           // point size, but for cfloat all stages are R2.
            r4Stages = std::is_same<TT_DATA, cfloat>::value ? 0 : ptSizePwr / 2;
            ptSize = ((unsigned int)1) << ptSizePwr;

            // write out header
        }
    for (int opIndex = 0; opIndex < TP_WINDOW_VSIZE / TP_POINT_SIZE; opIndex++) {
        rank = 0;
        if ((ptSizePwr >= kMinPtSizePwr) && (ptSizePwr <= kMaxPtSizePwr)) {
            // read samples in
            for (unsigned int i = 0; i < ptSize; i++) {
                window_readincr(inWindow, sampleIn);
                samplesStoreA[bitRev(ptSizePwr, i)] = castInput<TT_DATA>(sampleIn);
            }
            window_incr(inWindow, TP_POINT_SIZE - ptSize);
            for (unsigned int i = 0; i < (1 << (kMaxPtSizePwr - 1)); i++) {
                twiddles[i] = twiddle_master[i];
            }

            samplesA = samplesStoreA;
            samplesB = samplesStoreB;

            for (unsigned int r2StageCnt = 0; r2StageCnt < r2Stages; r2StageCnt++) {
                if
                    constexpr(is_cfloat<TT_DATA>()) {
                        r2StageFloat(samplesA, samplesB, twiddles, r2StageCnt, ptSize, inv);
                    }
                else {
                    r2StageInt(samplesA, samplesB, twiddles, ptSize,
                               inv); // only called for the first stage so stage is implied
                }
                // Now watch carefully. The pea is under the cup labelled samplesB (the output).
                tempPtr = samplesA;
                samplesA = samplesB;
                samplesB = tempPtr;
                rank++;
                // The pea is now under the cup labelled samplesA. The next rank's input or the actual output
            }

            for (int r4StageCnt = 0; r4StageCnt < r4Stages; r4StageCnt++) {
                if (r4StageCnt == r4Stages - 1) {
                    stageShift = shift + TP_SHIFT;
                } else {
                    stageShift = shift;
                }
                r4StageInt(samplesA, twiddles, twiddles, ptSize, ptSize >> 2, stageShift, rank, samplesB, ptSize,
                           inv); //<TT_DATA,TT_TWIDDLE,TP_POINT_SIZE,TP_FFT_NIFFT>, but not required because this is a
                                 //member function.

                // Now watch carefully. The pea is under the cup labelled samplesB (the output).
                tempPtr = samplesA;
                samplesA = samplesB;
                samplesB = tempPtr;
                rank += 2;
                // The pea is now under the cup labelled samplesA. The next rank's input or the actual output
            }

            // Write samples out (natural order)
            TT_DATA outSample;
            for (unsigned int i = 0; i < ptSize; i++) {
                outSample = castOutput<TT_DATA>(samplesA[i], 0);
                window_writeincr(outWindow, outSample);
            }
            for (int i = ptSize; i < TP_POINT_SIZE; i++) {
                window_writeincr(outWindow, nullElem<TT_DATA>());
            }

        } else {                                 // ptSizePwr is out of range
            window_writeincr(outWindow, header); // pass input definition header to output
            dummyttdata = nullElem<TT_DATA>();
            for (int i = sizeof(TT_DATA) / 2; i < kHeaderSize - 1; i++) {
                window_writeincr(outWindow, dummyttdata);
            }
            dummyttdata.real = std::is_same<TT_DATA, cfloat>::value, 1.0, 1;
            window_writeincr(outWindow, dummyttdata); // error flag out
            dummyttdata = nullElem<TT_DATA>();
            // write out blank frame
            for (int i = 0; i < ptSize * sizeof(TT_DATA) / sizeof(int32); i++) {
                window_writeincr(outWindow, dummyttdata);
            }

            rank += 2;
            // The pea is now under the cup labelled samplesA. The next rank's input or the actual output
        }
    }
};

// Non Bit-accurate REF FFT DIT function
template <typename TT_DATA,
          typename TT_TWIDDLE, // type of coefficients           (e.g. int16, cint32)
          unsigned int TP_POINT_SIZE,
          unsigned int TP_FFT_NIFFT,
          unsigned int TP_SHIFT,
          unsigned int TP_DYN_PT_SIZE,
          unsigned int TP_WINDOW_VSIZE>
void fft_ifft_dit_1ch_ref<TT_DATA, TT_TWIDDLE, TP_POINT_SIZE, TP_FFT_NIFFT, TP_SHIFT, TP_DYN_PT_SIZE, TP_WINDOW_VSIZE>::
    nonBitAccfft(input_window<TT_DATA>* inWindow, output_window<TT_DATA>* outWindow) {
#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
    printf("Ref model params:\n");
    printf("TP_POINT_SIZE = %d\n", TP_POINT_SIZE);
    printf("TP_FFT_NIFFT = %d\n", TP_FFT_NIFFT);
#endif //_DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
    constexpr unsigned int kPtSizePwr = fnGetPointSizePower<TP_POINT_SIZE>();
    constexpr unsigned int kScaleFactor = TP_SHIFT; // was kPtSizePwr -1; //1 is for initial rotation factor of
                                                    // 1/sqrt(2), but with TP_SHIFT this is user-config
    constexpr unsigned int kSampleRanks = kRanks + 1;

#ifdef _DSPLIB_FFT_IFFT_DIT_1CH_REF_DEBUG_
    printf("kranks = %d, kPtSizePwr = %d, kScaleFactor = %d\n", kRanks, kPtSizePwr, kScaleFactor);
#endif

    TT_DATA sampleIn;
    T_int_data<TT_DATA> rotB; // Sample B after rotation.
    TT_TWIDDLE twiddle;
    TT_TWIDDLE twiddles[TP_POINT_SIZE / 2];
    T_int_data<TT_DATA> chess_storage(% chess_alignof(cint16)) samplesStoreA[TP_POINT_SIZE];
    T_int_data<TT_DATA> chess_storage(% chess_alignof(cint16)) samplesStoreB[TP_POINT_SIZE];
    T_int_data<TT_DATA> inSampleA, inSampleB, outSampleA, outSampleB;
    unsigned int posLoMask, posHiMask;
    unsigned int twiddleMask, twiddleIndex, twiddlePos;
    unsigned int posLo, posHi;
    unsigned int posA, posB;
    T_accRef<T_int_data<TT_DATA> > accum;
    T_int_data<TT_DATA> *samplesA, *samplesB, *tempPtr;

    // Form twiddle table for this point size;
    for (int i = 0; i < TP_POINT_SIZE / 2; i++) {
        twiddles[i] = get_twiddle<TT_TWIDDLE>(i, TP_POINT_SIZE, TP_FFT_NIFFT);
    }

    // A window may contain multiple FFT data sets. This dilutes the overheads
    for (int iter = 0; iter < TP_WINDOW_VSIZE / TP_POINT_SIZE; iter++) {
        // read samples in
        for (unsigned int i = 0; i < TP_POINT_SIZE; i++) {
            window_readincr(inWindow, sampleIn);
            samplesStoreA[i] = castInput<TT_DATA>(sampleIn);
        }

        samplesA = samplesStoreA;
        samplesB = samplesStoreB;

        // Perform FFT
        for (unsigned int rank = 0; rank < kRanks; rank++) {
            posLoMask = (1 << (kRanks - 1 - rank)) - 1;      // e.g. 000111
            posHiMask = (1 << (kRanks - 1)) - 1 - posLoMask; // e.g. 111000
            for (unsigned int op = 0; op < TP_POINT_SIZE / 2; op++) {
                posLo = op & posLoMask;
                posHi = op & posHiMask;
                posA = (posHi << 1) + 0 + posLo;
                posB = (posHi << 1) + (1 << (kRanks - 1 - rank)) + posLo;
                if (posA < TP_POINT_SIZE && posB < TP_POINT_SIZE) {
                    inSampleA = samplesA[posA];
                    inSampleB = samplesA[posB];
                }
                twiddleMask = TP_POINT_SIZE / 2 - 1;
                twiddleIndex = bitRev(kPtSizePwr - 1, op) << (kRanks - rank - 1);
                twiddlePos = twiddleIndex & twiddleMask; // << (kRanks-rank-1);
                twiddle = twiddles[twiddlePos];
                btflynonbitacc<TT_DATA, TT_TWIDDLE>(twiddle, inSampleA, inSampleB, outSampleA, outSampleB);
                if (posA < TP_POINT_SIZE && posB < TP_POINT_SIZE) {
                    samplesB[posA] = outSampleA;
                    samplesB[posB] = outSampleB;
                }
            }
            // Now watch carefully. The pea is under the cup labelled samplesB (the output).
            tempPtr = samplesA;
            samplesA = samplesB;
            samplesB = tempPtr;
            // The pea is now under the cup labelled samplesA. The next rank's input or the actual output
        }
        // Write samples out (natural order)
        for (unsigned int i = 0; i < TP_POINT_SIZE; i++) {
            window_writeincr((output_window<TT_DATA>*)outWindow,
                             castOutput<TT_DATA>(samplesA[bitRev(kPtSizePwr, i)], kScaleFactor));
        }
    }
};
}
}
}
}
}