///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2026 Wizardry and Steamworks - License: MIT          //
///////////////////////////////////////////////////////////////////////////

#include <proto/exec.h>
#include <proto/dos.h>

#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>

#include <rexx/storage.h>
#include <rexx/rxslib.h>
#include <proto/rexxsyslib.h>
#include <clib/rexxsyslib_protos.h>

#define AMBIENT_PORT "AMBIENT"

/* Formal Amiga DOS Command-Line Template Definition. */
#define TEMPLATE "FILE/A,OUTPUT,OFFSET/N,COUNT/N,VERBOSE/S,MODE/K,NAME/A"

#define ARG_FILE     0
#define ARG_OUTPUT   1
#define ARG_OFFSET   2
#define ARG_COUNT    3
#define ARG_VERBOSE  4
#define ARG_MODE     5
#define ARG_NAME     6

#define NUM_ARGS     7

/* Structures */
typedef enum {
    MODE_BINARY = 0,
    MODE_EXTENSION,
    MODE_UNKNOWN
} MatchMode;

/* Protypes */
MatchMode ParseProcessingMode(const char *modeString);
static char *LoadTemplateBuffer(void);
static char *ProcessTemplateSubstitutions(char *template_buffer, const char *name, const char *mime_type, const char *matcher_line);
char *GenerateAmbientTemplate(const char *mime_type, const char *mime_name, const char *hex_sig);
char *GenerateAmbientBinaryTemplate(const char *name, const char *mime_type, const char *hex_sig);
char *GenerateAmbientExtensionTemplate(const char *name, const char *mime_type, const char *extension);
char *GetFileHexStr(const char *filePath, long offset, long byteCount);
char *GetMimeType(const char *filePath);

int main(void) {
    struct RDArgs *rdArgs = NULL;
    LONG argArray[NUM_ARGS] = {0};

    /* Extracted CLI options */
    char *filePath = NULL;
    char *mimeFilePath = NULL;
    long hexOffset = 0;
    long hexCount  = 4;  /* Default to 4 bytes */
    BOOL isVerbose = FALSE;

    /* Runtime processing variables */
    char *mimeType = NULL;
    char *templateResult = NULL;
    const char *mimeFileName = "aaa";
    MatchMode processingMode;

    /* Process command-line parameters  using proper Amiga templates */
    rdArgs = ReadArgs(TEMPLATE, argArray, NULL);
    if (!rdArgs) {
        PrintFault(IoErr(), "Magic2Morph");
        return 10;
    }

    /* Required parameters. */
    filePath = (char *)argArray[ARG_FILE];
    mimeFileName = (char *)argArray[ARG_NAME];

    /* Optional paramters. */
    if (argArray[ARG_OUTPUT]) {
		mimeFilePath = (char *)argArray[ARG_OUTPUT];
	}

    if (argArray[ARG_OFFSET]) {
		hexOffset = *(LONG *)argArray[ARG_OFFSET];
	}

    if (argArray[ARG_COUNT]) {
		hexCount = *(LONG *)argArray[ARG_COUNT];
	}

    if (argArray[ARG_VERBOSE]) {
		isVerbose = TRUE;
	}

    /* Retrieve MIME type from Ambient via AREXX. */
    mimeType = GetMimeType(filePath);
    if (mimeType == NULL) {
        fprintf(stderr, "Error: Failed to determine MIME type for '%s'.\n", filePath);
        FreeArgs(rdArgs);
        return 60;
    }

    /* Default to using the file extension. */
    processingMode = ParseProcessingMode((char *)argArray[ARG_MODE]);
    if (processingMode == MODE_UNKNOWN) {
        processingMode = MODE_EXTENSION;
    }

    /* Branch on the Magic2Morph processing mode. */
    switch (processingMode) {
        case MODE_BINARY: {
            char *hexSignature = NULL;

            if (isVerbose) {
                fprintf(stdout, "Processing Mode: BINARY\n");
            }

            /* Fetch hex signature block from the file bytes */
            hexSignature = GetFileHexStr(filePath, hexOffset, hexCount);

            if (hexSignature != NULL) {                                  
                templateResult = GenerateAmbientBinaryTemplate(mimeFileName, mimeType, hexSignature);
                FreeVec(hexSignature);
            } else {
                fprintf(stderr, "Error: Could not retrieve hex signature bytes.\n");
            }
            break;
        }

        case MODE_EXTENSION: {
            STRPTR fileNamePart;
            char *fileExtension = NULL;

            if (isVerbose) {
                fprintf(stdout, "Processing Mode: EXTENSION\n");
            }

            /* Isolate the actual file name from any volume or directory paths safely */
            fileNamePart = (STRPTR)FilePart((STRPTR)filePath);

            /* Locate the extension dot ONLY within the isolated filename */
            fileExtension = strrchr((char *)fileNamePart, '.');
            if (fileExtension != NULL) {
                fileExtension++; /* Step past the dot character itself */
            } else {
                fileExtension = ""; /* Fallback to empty string if no extension exists */
            }

            if (isVerbose) {
                fprintf(stdout, "Isolated Filename: %s\n", (char *)fileNamePart);
                fprintf(stdout, "Derived File Extension: .%s\n", fileExtension);
            }
                                                                         
            templateResult = GenerateAmbientExtensionTemplate(mimeFileName, mimeType, fileExtension);
            break;
        }

        default:
            break;
    }

    /* Write to console or the file. */
    if (templateResult != NULL) {
        if (mimeFilePath != NULL) {
            BPTR mimeFileHandle = Open((STRPTR)mimeFilePath, MODE_NEWFILE);

            if (mimeFileHandle) {
                Write(mimeFileHandle, templateResult, strlen(templateResult));
                Close(mimeFileHandle);
                if (isVerbose) {
                    printf("MIME template successfully written to: %s\n", mimeFilePath);
                }
            } else {
                PrintFault(IoErr(), "Error writing output file");
            }
        } else {
            /* Fallback to console output */
            fprintf(stdout, "%s", templateResult);
        }

        /* Clean up task-pooled string block allocation */
        FreeVecTaskPooled(templateResult);
    }

    /* Cleanup. */
    FreeVec(mimeType);
    FreeArgs(rdArgs);

    return 0;
}

/*
 * Interprets a string as a MatchMode
 * Returns the Magic2Morph mode
 */
MatchMode ParseProcessingMode(const char *modeString) {
    /* Fallback to default if NULL */
    if (modeString == NULL) {
        return MODE_UNKNOWN;
    }

    /* Fixed the extra parenthesis error */
    if (strncasecmp(modeString, "BINARY", strlen("BINARY")) == 0) {
        return MODE_BINARY;
    }

    if (strncasecmp(modeString, "EXTENSION", strlen("EXTENSION")) == 0) {
        return MODE_EXTENSION;
    }

    return MODE_UNKNOWN;
}

/* 
 * Internal helper function to locate, size, and read Magic2Morph.template into memory.
 * Returns an AllocVecTaskPooled null-terminated string that must be freed by the caller, or NULL on error. 
 */
/* 
 * Internal helper function to locate, size, and read Magic2Morph.template into memory.
 * Returns an AllocVecTaskPooled null-terminated string that must be freed by the caller, or NULL on error. 
 */
static char *LoadTemplateBuffer(void) {
    /* 4 paths now: Prodir (dynamic), current, ENV:, ENVARC: */
    const char *paths[] = { NULL, "", "ENV:", "ENVARC:" };
    const char *filename = "Magic2Morph.template";
    char prog_dir[256] = "";
    char template_path[256];
    BPTR file_in = (BPTR)NULL;
    char *buffer = NULL;
    LONG file_size = 0;
    int i;

    /* Get the full path of the running executable */
    char prog_path[256];
    if (GetProgramName(prog_path, sizeof(prog_path))) {
        /* Isolate the directory portion by stripping the file name */
        char *file_part = (char *)PathPart(prog_path);
        if (file_part) {
            size_t dir_len = file_part - prog_path;
            if (dir_len < sizeof(prog_dir)) {
                strncpy(prog_dir, prog_path, dir_len);
                prog_dir[dir_len] = '\0';
                paths[0] = prog_dir; /* Assign dynamically to the first slot */
            }
        }
    }

    /* Search and open the template file across the locations */
    for (i = 0; i < 4; i++) {
        /* Skip the first slot if we failed to resolve the program directory */
        if (i == 0 && paths[0] == NULL) {
            continue;
        }

        if (paths[i][0] == '\0') {
            snprintf(template_path, sizeof(template_path), "%s", filename);
        } else {
            snprintf(template_path, sizeof(template_path), "%s%s", paths[i], filename);
        }

        file_in = Open(template_path, MODE_OLDFILE);
        if (file_in) {
            break;
        }
    }

    if (!file_in) {
        fprintf(stderr, "Cannot open template file '%s' in program dir, current dir, ENV:, or ENVARC:\n", filename);
        return NULL;
    }

    /* Determine file size. */
    Seek(file_in, 0, OFFSET_END);
    file_size = Seek(file_in, 0, OFFSET_BEGINNING);

    if (file_size <= 0) {
        Close(file_in);
        return NULL;
    }

    /* Allocate memory for content. */
    buffer = (char *)AllocVecTaskPooled(file_size + 1);
    if (!buffer) {
        Close(file_in);
        return NULL;
    }

    /* Read template data. */
    if (Read(file_in, buffer, file_size) != file_size) {
        FreeVecTaskPooled(buffer);
        Close(file_in);
        return NULL;
    }
    buffer[file_size] = '\0';
    Close(file_in);

    return buffer;
}

/* 
 * Internal core engine to execute line-by-line substitutions on a loaded template buffer.
 * Frees the source template_buffer internally before returning the finalized string block. 
 * Returns the substituted string.
 */
static char *ProcessTemplateSubstitutions(char *template_buffer, const char *name, const char *mime_type, const char *matcher_line) {
    char *result = NULL;
    size_t extra_space;

    if (!template_buffer) {
		return NULL;
	}

    /* Calculate dynamic bounds safely. */
    extra_space = strlen(name) + strlen(mime_type) + strlen(matcher_line) + 512;
    result = (char *)AllocVecTaskPooled(strlen(template_buffer) + extra_space);
    if (!result) {
        FreeVecTaskPooled(template_buffer);
        return NULL;
    }

    result[0] = '\0';
    char *line = strtok(template_buffer, "\n");
    while (line != NULL) {
        char processed_line[1024];
        strncpy(processed_line, line, sizeof(processed_line) - 1);
        processed_line[sizeof(processed_line) - 1] = '\0';

        char *pos;
        /* Substitute AMBIENT_MIME_NAME */
        while ((pos = strstr(processed_line, "AMBIENT_MIME_NAME")) != NULL) {
            char temp[1024];
            *pos = '\0';
            snprintf(temp, sizeof(temp), "%s%s%s", processed_line, name, pos + strlen("AMBIENT_MIME_NAME"));
            strcpy(processed_line, temp);
        }

        /* Substitute AMBIENT_MIME_TYPE */
        while ((pos = strstr(processed_line, "AMBIENT_MIME_TYPE")) != NULL) {
            char temp[1024];
            *pos = '\0';
            snprintf(temp, sizeof(temp), "%s%s%s", processed_line, mime_type, pos + strlen("AMBIENT_MIME_TYPE"));
            strcpy(processed_line, temp);
        }

        /* Substitute AMBIENT_FILE_MATCHER */
        while ((pos = strstr(processed_line, "AMBIENT_FILE_MATCHER")) != NULL) {
            char temp[1024];
            *pos = '\0';
            snprintf(temp, sizeof(temp), "%s%s%s", processed_line, matcher_line, pos + strlen("AMBIENT_FILE_MATCHER"));
            strcpy(processed_line, temp);
        }

        strcat(result, processed_line);
        strcat(result, "\n");
        line = strtok(NULL, "\n");
    }

    FreeVecTaskPooled(template_buffer);
    return result;
}

/* 
 * Binary template generator.
 * Returns an Ambient MIME file using binary matching.
 */
char *GenerateAmbientBinaryTemplate(const char *name, const char *mime_type, const char *hex_sig) {
    char matcher_line[1024];
    char *template_buffer;

    if (hex_sig == NULL || hex_sig[0] == '\0') {
        matcher_line[0] = '\0';
    } else {
        snprintf(matcher_line, sizeof(matcher_line), "Hex 0 %s", hex_sig);
    }

    template_buffer = LoadTemplateBuffer();
    return ProcessTemplateSubstitutions(template_buffer, name, mime_type, matcher_line);
}

/* 
 * File extension template generator.
 * Returns an Ambient MIME file using file extension matching.
 */
char *GenerateAmbientExtensionTemplate(const char *name, const char *mime_type, const char *extension) {
    char matcher_line[1024];
    char *template_buffer;

    if (extension == NULL || extension[0] == '\0') {
        matcher_line[0] = '\0';
    } else {
        snprintf(matcher_line, sizeof(matcher_line), "Extension %s", extension);
    }

    template_buffer = LoadTemplateBuffer();
    return ProcessTemplateSubstitutions(template_buffer, name, mime_type, matcher_line);
}

/* 
 * Helper function to read and format file bytes into a hexadecimal string.
 * Returns an AllocVec'd string which the caller must free using FreeVec(). 
 */
char *GetFileHexStr(const char *filePath, long offset, long byteCount) {
    BPTR fileHandle;
    unsigned char *readBuffer;
    char *hexString = NULL;
    long bytesRead = 0;
    long i;

    if (byteCount <= 0) {
        return NULL;
    }

    /* Open the target file using MorphOS/AmigaOS DOS layer */
    fileHandle = Open((STRPTR)filePath, MODE_OLDFILE);
    if (!fileHandle) {
        return NULL;
    }

    /* Allocate temporary public buffer memory for file data */
    readBuffer = (unsigned char *)AllocVec(byteCount, MEMF_PUBLIC | MEMF_CLEAR);
    if (!readBuffer) {
        Close(fileHandle);
        return NULL;
    }

    /* Seek to the specified offset if requested */
    if (offset > 0) {
        Seek(fileHandle, offset, OFFSET_BEGINNING);
    }

    /* Read the bytes */
    bytesRead = Read(fileHandle, readBuffer, byteCount);

    if (bytesRead > 0) {
        /* Calculate exact string size: 2 chars per byte, spaces between them, plus null terminator */
        /* Formula: (bytesRead * 2) for hex digits + (bytesRead - 1) for spaces + 1 for '\0' */
        long stringLength = (bytesRead * 3);

        /* Allocate the return string buffer */
        hexString = (char *)AllocVec(stringLength, MEMF_PUBLIC | MEMF_CLEAR);

        if (hexString != NULL) {
            char *dest = hexString;

            for (i = 0; i < bytesRead; i++) {
                /* Format byte into string and advance pointer */
                sprintf(dest, "%02X", readBuffer[i]);
                dest += 2;

                /* Add space separator between bytes, but not after the last one */
                if (i + 1 < bytesRead) {
                    *dest++ = ' ';
                }
            }
            *dest = '\0'; /* Ensure explicit termination */
        }
    }

    /* Clean up resources */
    FreeVec(readBuffer);
    Close(fileHandle);

    return hexString;
}

/* 
 * Helper function to communicate with Ambient via ARexx and retrieve a file's MIME type.
 * Returns an AllocVec'd string which the caller must free using FreeVec(), or NULL on error. 
 */
char *GetMimeType(const char *filePath) {
    struct Library *RexxSysBase = NULL;
    struct MsgPort *ambientPort = NULL;
    struct MsgPort *replyPort = NULL;
    struct RexxMsg *rxMsg = NULL;
    char cmdBuffer[1024];
    char *mimeType = NULL;

    RexxSysBase = OpenLibrary("rexxsyslib.library", 0);
    if (!RexxSysBase) {
        return NULL;
    }

    Forbid();
    ambientPort = FindPort(AMBIENT_PORT);
    Permit();

    if (!ambientPort) {
        CloseLibrary(RexxSysBase);
        return NULL;
    }

    replyPort = CreateMsgPort();
    if (!replyPort) {
        CloseLibrary(RexxSysBase);
        return NULL;
    }

    rxMsg = (struct RexxMsg *)AllocVec(sizeof(struct RexxMsg), MEMF_PUBLIC | MEMF_CLEAR);
    if (!rxMsg) {
        DeleteMsgPort(replyPort);
        CloseLibrary(RexxSysBase);
        return NULL;
    }

    snprintf(cmdBuffer, sizeof(cmdBuffer), "GETMIMETYPE \"%s\"", filePath);

    rxMsg->rm_Node.mn_Node.ln_Type = NT_MESSAGE;
    rxMsg->rm_Node.mn_Length       = sizeof(struct RexxMsg);
    rxMsg->rm_Node.mn_ReplyPort    = replyPort;
    rxMsg->rm_Action               = RXCOMM | RXFF_RESULT;
    rxMsg->rm_Args[0]              = (STRPTR)cmdBuffer;

    PutMsg(ambientPort, (struct Message *)rxMsg);
    WaitPort(replyPort);
    GetMsg(replyPort);

    if (rxMsg->rm_Result1 == 0 && rxMsg->rm_Result2 != 0) {
        ULONG mimeTypeLength = strlen((char *)rxMsg->rm_Result2);

        mimeType = (char *)AllocVec(mimeTypeLength + 1, MEMF_PUBLIC | MEMF_CLEAR);
        if (mimeType != NULL) {
            strncpy(mimeType, (char *)rxMsg->rm_Result2, mimeTypeLength);
            mimeType[mimeTypeLength] = '\0';
        }

        DeleteArgstring((STRPTR)rxMsg->rm_Result2);
    }

    FreeVec(rxMsg);
    DeleteMsgPort(replyPort);
    CloseLibrary(RexxSysBase);

    return mimeType;
}
