// Disable deprecated
#pragma warning(disable : 4995)

#include <assert.h>
#include <list>
#include <string>
#include <vector>
#include <fstream>
#include <algorithm>
#include "linkfile.h"

#ifdef LINUX_
#include <dirent.h>
#include <sys/stat.h>
#include <strings.h>
#include <cstring>
#endif

using namespace std;

// Defaults
#define KERNEL                      "kernel"
#define KERNEL_HEADER_FILE_NAME     "krnheader.h"

char KERNEL_COMPONENT_DIR[MAX_STRING_SIZE]		= "";
char KERNEL_HEADER_FILE[MAX_STRING_SIZE]		= "";
char KERNEL_TEMP_HEADER_FILE[MAX_STRING_SIZE]	= "";
char KERNEL_BINARY_FILE[MAX_STRING_SIZE]		= "";
char KERNEL_SEARCH_DIR[MAX_STRING_SIZE]		    = "";

char  KERNEL_HEADER_PREFIX[8]       = "";
char  KERNEL_HEADER_PREFIX_UPPER[8] = "";
char  KERNEL_HEADER_SENTRY[64]      = "";
char  KERNEL_IDR[16]                = "";

static const char *COPYRIGHT        =
    "/*\n"
    " * Copyright (c) 2019, Intel Corporation\n"
    " *\n"
    " * Permission is hereby granted, free of charge, to any person obtaining a\n"
    " * copy of this software and associated documentation files (the\n"
    " * 'Software'), to deal in the Software without restriction, including\n"
    " * without limitation the rights to use, copy, modify, merge, publish,\n"
    " * distribute, sublicense, and/or sell copies of the Software, and to\n"
    " * permit persons to whom the Software is furnished to do so, subject to\n"
    " * the following conditions:\n"
    " *\n"
    " * The above copyright notice and this permission notice shall be included\n"
    " * in all copies or substantial portions of the Software.\n"
    " *\n"
    " * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS\n"
    " * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n"
    " * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n"
    " * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n"
    " * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n"
    " * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n"
    " * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
    "*/\n"
    "\n"
    "////////////////////////////////////////////////////////////////////////////////\n"
    "// !!! WARNING - AUTO GENERATED FILE. DO NOT EDIT DIRECTLY. !!!\n"
    "// Generated by GenKrnBin.exe tool\n"
    "////////////////////////////////////////////////////////////////////////////////\n"
;

#define INDEX_OPEN_FORMAT_STRING         "#ifndef %s\n#define %s\n\n\0",              \
    KERNEL_HEADER_SENTRY,                                                                   \
    KERNEL_HEADER_SENTRY
#define INDEX_ENTRY_FORMAT_STRING(index) "#define %s_%s %d\n\0",                          \
    KERNEL_IDR, index, dwKernelCount
#define INDEX_CLOSE_FORMAT_STRING        "\n#endif // %s\0", KERNEL_HEADER_SENTRY
#define INDEX_ENTRY_FORMAT_INT(index,i)  "#define %s_%s %d\n\0",                          \
    KERNEL_IDR, index, i
#define KERNEL_NAME_DEFINE(index)        "#define %s_%s   \\\n",                          \
    KERNEL_IDR, index
#define KERNEL_NAME_ENTRY(index)         "    _" "T(\"%s\"),\\\n",                        \
    index
#define KERNEL_ID_ENTRY(index)           "    _" "T(\"%03d\"),\\\n",                      \
    index

#ifdef LINUX_
#define StrCmp strncasecmp
#else
#define StrCmp strnicmp
#endif

void ConcatenateKernelBinary(char *pKernelName, bool bVerbose);

// scratch global variables for writing files
char    g_Buffer[MAX_STRING_SIZE];
bool    g_bReturn;

// size_t is integral data type returned by the language operator sizeof and is defined in the <cstring> header file (among others) as an unsigned integral type
unsigned int   g_SizetOfTextToWrite;
unsigned int   g_dwNumBytesWritten;

// globals used in multiple functions
FILE          *g_hKernelBinary        = NULL;
unsigned int   g_dwTotalKernelCount   = 0;
unsigned int   g_dwCurrentKernelCount = 0;

// offset table
unsigned int  *g_pOffsetTable;
unsigned int   g_dwOffsetTableSize;
unsigned int   g_dwCurrentKernel;

#ifdef LINUX_
char strupr(char *str) 
{ 
    char *ptr = str;

    while (*ptr != '\0') 
    { 
        if (islower(*ptr)) 
        *ptr = toupper(*ptr); 
        ptr++; 
    }
}

#define _strupr_s strupr
#endif

int main(int argc, char *argv[])
{
    // list template class is based on a doubly-linked list
    list <string> KernelList; // creates an empty list for elements of type "string"
    list <string>::const_iterator iterator; // allows iterating over list <string>
    FILE *hKernelDirs, *hKernels, *hHeaderFile, *hTempHeaderFile;

    // TCHAR data type is char string used to represent a string of either the WCHAR or char type
    char SearchString[MAX_STRING_SIZE];
    char KernelNameNoExt[MAX_STRING_SIZE];
    char KernelNameFull[MAX_STRING_SIZE];
    char KernelBinName[MAX_STRING_SIZE];
    bool  bKernelNames, bVerbose;
    char* pcHeaderFile, pcTempHeaderFile;
    unsigned int dwKernelCount, dwHeaderSize, dwTempHeaderSize, dwBytesRead;

    bVerbose = false;
    if (argc < 3)
    {
        fprintf(stderr, "Usage: GenKrnBin.exe <kernel root dir> <component> [-verbose]\n");
        exit (-1);
    }

    int idx = 3;        // scan the command-line options from index 3
    while (idx < argc)
    {
        if (StrCmp(argv[idx], "-verbose", 8) == 0)
        {
            bVerbose = true;
        }
        ++idx;
    }

#ifdef LINUX_
    sprintf(KERNEL_COMPONENT_DIR,"%s", argv[1]);
    strncpy(KERNEL_HEADER_PREFIX, argv[2], sizeof(KERNEL_HEADER_PREFIX));
    strncpy(KERNEL_HEADER_PREFIX_UPPER, argv[2], sizeof(KERNEL_HEADER_PREFIX));
   _strupr_s(KERNEL_HEADER_PREFIX_UPPER);

    sprintf(KERNEL_HEADER_FILE, "%s/ig%skrn_g11_icllp.h", KERNEL_COMPONENT_DIR, KERNEL_HEADER_PREFIX);
    sprintf(KERNEL_TEMP_HEADER_FILE, "%s/%stemp%s", KERNEL_COMPONENT_DIR, KERNEL_HEADER_PREFIX, KERNEL_HEADER_FILE_NAME);
    sprintf(KERNEL_BINARY_FILE, "%s/ig%skrn_g11_icllp.bin", KERNEL_COMPONENT_DIR, KERNEL_HEADER_PREFIX);
    sprintf(KERNEL_SEARCH_DIR, "%s/g*", KERNEL_COMPONENT_DIR);
    sprintf(KERNEL_HEADER_SENTRY, "__%sKRNHEADER_H__", KERNEL_HEADER_PREFIX_UPPER);
    sprintf(KERNEL_IDR, "IDR_%s", KERNEL_HEADER_PREFIX_UPPER);
#else
    sprintf(KERNEL_COMPONENT_DIR,"%s", argv[1]);
    strncpy(KERNEL_HEADER_PREFIX, argv[2], sizeof(KERNEL_HEADER_PREFIX));
    strncpy(KERNEL_HEADER_PREFIX_UPPER, argv[2], sizeof(KERNEL_HEADER_PREFIX));
    _strupr_s(KERNEL_HEADER_PREFIX_UPPER);

    sprintf(KERNEL_HEADER_FILE, "%s\\ig%skrn_g11_icllp.h", KERNEL_COMPONENT_DIR, KERNEL_HEADER_PREFIX);
    sprintf(KERNEL_TEMP_HEADER_FILE, "%s\\%stemp%s", KERNEL_COMPONENT_DIR, KERNEL_HEADER_PREFIX, KERNEL_HEADER_FILE_NAME);
    sprintf(KERNEL_BINARY_FILE, "%s\\ig%skrn_g11_icllp.bin", KERNEL_COMPONENT_DIR, KERNEL_HEADER_PREFIX);
    sprintf(KERNEL_SEARCH_DIR, "%s\\g*", KERNEL_COMPONENT_DIR);
    sprintf(KERNEL_HEADER_SENTRY, "__%sKRNHEADER_H__", KERNEL_HEADER_PREFIX_UPPER);
    sprintf(KERNEL_IDR, "IDR_%s", KERNEL_HEADER_PREFIX_UPPER);
#endif
    bKernelNames = true;

    /////////////////////////////////////////////////
    // Generate a superset list of all the kernels //
    /////////////////////////////////////////////////


#ifdef LINUX_
    sprintf(SearchString, "%s", KERNEL_COMPONENT_DIR);
 
    struct dirent* ent = NULL;

    DIR *pDir=NULL;
    pDir=opendir(SearchString);
    int n;

    while (NULL != (ent=readdir(pDir)))
    {      
        n = strlen(ent->d_name);
        //printf("kernel name  = %s; ent->d_reclen = %d\n", ent->d_name, n);
        //printf("kernel offset= %s\n", ent->d_name + n - 4);

        if ((StrCmp(ent->d_name + n - 4, ".krn", 4) == 0) ||
            (StrCmp(ent->d_name + n - 4, ".hex", 4) == 0))
        {
            KernelList.push_back(string(ent->d_name));
            continue;
        }
        else
        {
            continue;
        }
    };

    closedir(pDir);
#endif

    // include LinkFile.txt
    KernelList.push_back(string("LinkFile.krn"));

    // remove duplicate entries from the list (create superset)
    KernelList.sort();

    // assumes list is sorted. removes subsequent consecutive equal values.
    KernelList.unique();

    ///////////////////////////////////////////////////////////////////
    // Create a temp header that defines the indicies into the table //
    ///////////////////////////////////////////////////////////////////

    // create the header file that will define the kernel IDs
    hHeaderFile = fopen(KERNEL_HEADER_FILE, "w");
    if (hHeaderFile == NULL)
    {
        fprintf(stderr, "Failed to open Temp Header File\n");
        exit (-1);
    }

    // print the copyright comment text to the file
    fwrite(COPYRIGHT, 1, strlen(COPYRIGHT), hHeaderFile);

    fprintf(hHeaderFile, INDEX_OPEN_FORMAT_STRING);
    fprintf(hHeaderFile, INDEX_ENTRY_FORMAT_INT("LINKFILE_VERSION", LINKFILE_VERSION));
    fprintf(hHeaderFile, INDEX_ENTRY_FORMAT_INT("LINKFILE_HEADER" , sizeof(LinkFileHeader)));
    fprintf(hHeaderFile, "\n");

    for (iterator = KernelList.begin(), dwKernelCount = 0;
        iterator != KernelList.end(); iterator++, dwKernelCount++)
    {
        strncpy(KernelNameNoExt, iterator->c_str(), MAX_STRING_SIZE);

        *(strrchr(KernelNameNoExt, '.')) = '\0';
        char *c;
 
        while ((c = strchr(KernelNameNoExt, '.')) != NULL) *c = '_';
        fprintf(hHeaderFile, INDEX_ENTRY_FORMAT_STRING(KernelNameNoExt));
    }

    fprintf(hHeaderFile, INDEX_ENTRY_FORMAT_STRING("TOTAL_NUM_KERNELS"));

    // kernel names are defined in the same sequence as they are loaded
    if (bKernelNames)
    {
        fprintf(hHeaderFile, "\n#if _DEBUG || _RELEASE_INTERNAL\n");
        fprintf(hHeaderFile, KERNEL_NAME_DEFINE("KERNEL_NAMES"));
        for (iterator = KernelList.begin(), dwKernelCount = 0;
        iterator != KernelList.end(); iterator++, dwKernelCount++)
        {
            strncpy(KernelNameNoExt, iterator->c_str(), MAX_STRING_SIZE);
            *(strrchr(KernelNameNoExt, '.')) = '\0';
            char *c;
            while ((c = strchr(KernelNameNoExt, '.')) != NULL) *c = '_';
            fprintf(hHeaderFile, KERNEL_NAME_ENTRY(KernelNameNoExt));
        }
        fprintf(hHeaderFile, "    _" "T(\"\")\n");
        fprintf(hHeaderFile, "#else // !_DEBUG\n");
        fprintf(hHeaderFile, KERNEL_NAME_DEFINE("KERNEL_NAMES"));
        for (int i = 0; i < dwKernelCount; i++)
        {
            fprintf(hHeaderFile, KERNEL_ID_ENTRY(i));
        }
        fprintf(hHeaderFile, "    _" "T(\"\")\n");
        fprintf(hHeaderFile, "#endif // _DEBUG\n");
    }

    fprintf(hHeaderFile, INDEX_CLOSE_FORMAT_STRING);
    fprintf(hHeaderFile, "\n");
    g_dwTotalKernelCount = dwKernelCount;
    fclose(hHeaderFile);

    ///////////////////////////////////////////////////////////////////
    // Create bin containing kernel table and actual kernel binaries //
    ///////////////////////////////////////////////////////////////////
    // allocate kernel offset table
    g_dwOffsetTableSize = (g_dwTotalKernelCount + 1) * sizeof(unsigned int);
    g_pOffsetTable      = (unsigned int *)malloc(g_dwOffsetTableSize);
    if (!g_pOffsetTable)
    {
        fprintf(stderr, "Failed to allocate offset table\n");
        exit (-1);
    }

    // do for every family specific folder
    // create the file that will be the concatenation of all kernel binaries
    sprintf(KernelBinName, "%s", KERNEL_BINARY_FILE);
    g_hKernelBinary = fopen(KernelBinName, "wb");
    if (g_hKernelBinary == NULL)
    {
        fprintf(stderr, "Failed to open Kernel Bin File\n");
        exit (-1);
    }

    // write empty offset table
    memset(g_pOffsetTable,  0, g_dwOffsetTableSize);
    fwrite( g_pOffsetTable, 1, g_dwOffsetTableSize, (FILE *)g_hKernelBinary);

    // create link information for the current family folder
#ifdef LINUX_
    sprintf(KernelNameFull, "%s", KERNEL_COMPONENT_DIR);
#else
    sprintf(KernelNameFull, "%s", KERNEL_COMPONENT_DIR);
#endif
	CreateLinkFile(KernelNameFull, KernelList);

    // for every kernel in the superset list
    g_dwCurrentKernel = 0;
    for (iterator = KernelList.begin(); iterator != KernelList.end(); iterator++, g_dwCurrentKernel++)
    {
        // check if this kernel exists for this platform
        // cFileName contains file name
#ifdef LINUX_
        sprintf(KernelNameFull, "%s/%s", KERNEL_COMPONENT_DIR, iterator->c_str());
#else
        sprintf(KernelNameFull, "%s\\%s", KERNEL_COMPONENT_DIR, iterator->c_str());
#endif
        // add kernel binary, update offset table
        ConcatenateKernelBinary(KernelNameFull, bVerbose);
    }

    // write offset table
    fseek(g_hKernelBinary, 0, SEEK_SET);
    fwrite(g_pOffsetTable, 1, g_dwOffsetTableSize, g_hKernelBinary);

    fclose(g_hKernelBinary);

#ifdef LINUX_
    sprintf(KernelNameFull, "%s", KERNEL_COMPONENT_DIR);
#else
    sprintf(KernelNameFull, "%s", KERNEL_COMPONENT_DIR);
#endif

	DeleteLinkFile(KernelNameFull);

    // free offset table
    if (g_pOffsetTable)
    {
        free(g_pOffsetTable);
    }

    return 0;
}

void ConcatenateKernelBinary(char *pKernelName, bool bVerbose)
{
    FILE *hKernel;
    char pBuffer[MAX_STRING_SIZE];
    unsigned int dwBytesRead, dwBytesWritten, dwBinaryPos, dwFileSize;
    int   iLength;

    // update offset table
    dwBinaryPos = ftell(g_hKernelBinary) - g_dwOffsetTableSize;
    g_pOffsetTable[g_dwCurrentKernel] = dwBinaryPos;

    // the function opens an existing file and returns a handle that can be used to access the object
    // open .krn/.hex file
    hKernel = fopen(pKernelName, "r");

    if (hKernel == NULL)
    {
		fclose(hKernel);
        g_pOffsetTable[g_dwCurrentKernel + 1] = dwBinaryPos;
        return;
    }
    else
    {
        fseek(hKernel, 0, SEEK_END);
        dwFileSize = ftell(hKernel);
        fseek(hKernel, 0, SEEK_SET);
    }

    // print kernel name processed
    if (bVerbose)
    {
        fprintf(stderr, "%s\n", pKernelName);
    }

	// .hex file
    iLength = strlen(pKernelName);
    if (StrCmp(&pKernelName[iLength-4], ".hex", 4) == 0)
    {
        int    iLine = 1;
        char   pBinBuffer[MAX_STRING_SIZE];
        int    iBinLeft = MAX_STRING_SIZE;
        unsigned int * pBinCurrent = (unsigned int *)pBinBuffer;
        char  *pBuffCurrent = pBuffer;
        int    iBuffLeft  = 0;

        dwBytesRead = 0;
        do
        {
            // shift and read hex data
            if (iBuffLeft < 35)
            {
                // move incomplete line to the beginning of the buffer
                if (iBuffLeft)
                {
                    memcpy(pBuffer, pBuffCurrent, iBuffLeft);
                }

                // fill buffer (leave room for 0 at the end of the string)
                pBuffCurrent = pBuffer;
                if (dwBytesRead = fread(pBuffCurrent + iBuffLeft, 1, sizeof(pBuffer) - iBuffLeft - 1, hKernel))
                {
                    // add data
                    iBuffLeft += dwBytesRead;

                    // terminate string with 0 to avoid parsing issues
                    pBuffCurrent[iBuffLeft] = 0;

                    // no data to read - EOF
                    if (dwBytesRead == 0)
                    {
                        iBuffLeft = -1;
                    }
                }
                else
                {
                    iBuffLeft = -1;
                }
            }

            // process hex data one line at a time (35 chars => "xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx")
            while (iBuffLeft >= 35 && iBinLeft > 0)
            {
                // skip CR/LF/TAB (control characters)
                while (iBuffLeft > 35 && *pBuffCurrent < 32)
                {
                    pBuffCurrent++;
                    iBuffLeft--;
                }

                // convert hex to bin (4 DW = 16 B = 128 bits/line)
                if ( 4 != sscanf(pBuffCurrent, "%08x %08x %08x %08x", pBinCurrent, pBinCurrent+1, pBinCurrent+2, pBinCurrent+3) )
                {
                    fprintf(stderr, "Fail to process file %s at line %d\n", pKernelName, iLine);
                    exit(-1);
                }

                // line counter
                iLine++;

                // increment hex buffer
                pBuffCurrent += 35;
                iBuffLeft    -= 35;

                // increment bin buffer
                pBinCurrent += 4;
                iBinLeft    -= 4 * sizeof(unsigned int);

            }

            if (iBinLeft == 0 || iBuffLeft < 0)
            {
                // get actual size of binary buffer
                dwBytesRead = sizeof(pBinBuffer) - iBinLeft;

                dwBinaryPos = ftell(g_hKernelBinary);
                fwrite(pBinBuffer, 1, dwBytesRead, g_hKernelBinary);

                if (iBuffLeft >= 0)
                {
                    pBinCurrent = (unsigned int *)pBinBuffer;
                    iBinLeft    = sizeof(pBinBuffer);
                }
            }
        } while (dwBytesRead > 0 && iBuffLeft >= 0);
    }
    else
    {
        if (fread(pBuffer, 1, sizeof(pBuffer), hKernel))
        {
            dwBinaryPos = ftell(g_hKernelBinary);
            fwrite(pBuffer, 1, dwFileSize, g_hKernelBinary);
        }
    }

    dwBinaryPos = ftell(g_hKernelBinary) - g_dwOffsetTableSize;
    g_pOffsetTable[g_dwCurrentKernel+1] = dwBinaryPos;

	// close kernel file
	if (hKernel != NULL) fclose(hKernel);
}
