/* This file is part of the the "fileformat" project.
 *
 * Copyright (C) 2019-2022, 2024-2025 Eugene 'Vindex' Stulin
 *
 * Distributed under the Boost Software License 1.0 or (at your option)
 * the GNU General Public License 3.0 or later.
 */

import std.conv : to;
import std.file : FileException;
import std.getopt : getopt, GetOptException;
import std.stdio : stderr, writeln;
import std.string : strip, empty;

import amalthea.libcore : RealizeException;
import amalthea.fileformats :
    getFileFormat, contentTypeGuess, addNewPathToFindFileFormats;
import amalthea.fs : exists, isSymlink, isRegularFile;
static import amalthea.fs;
import amalthea.langlocal : _s, initLocalization;
import amalthea.sys : readAppHelpText;

immutable app = "fileformat";
immutable appVersion = import("version").strip;
immutable string[][] translations = mixin(import("translations.dtxt"));


int main(string[] args) {
    initLocalization(translations);
    bool fHelp, fVersion;
    bool enableMime;
    string formatDir;
    try {
        args.getopt(
            "help",    &fHelp,
            "version", &fVersion,
            "m|mime",  &enableMime,
            "formats", &formatDir
        );
    } catch(GetOptException e) {
        stderr.writeln(e.msg);
        wrongUsage("Wrong use of fileformat."._s);
        return 1;
    }
    if (args.length == 1) {  // help, version or wrong usage
        if (fHelp) {
            writeln(readAppHelpText(app));
        } else if (fVersion) {
            writeln(appVersion);
        } else {
            wrongUsage("Wrong use of fileformat."._s);
            return 1;
        }
        return 0;
    }
    if (!formatDir.empty) {
        try {
            addNewPathToFindFileFormats(formatDir);
        } catch (FileException e) {
            wrongUsage(formatDir ~ ": directory not found."._s);
            return 1;
        }
    }
    try {
        auto files = args[1 .. $];
        showFormat(files, enableMime);
    } catch(WrongUsage e) {
        wrongUsage(e.msg._s);
        return 1;
    } catch(Exception e) {
        stderr.writeln(e.msg._s);
        return 2;
    }
    return 0;
}


class WrongUsage : Exception { mixin RealizeException; }


void wrongUsage(string msg) {
    stderr.writeln(msg);
    stderr.writeln("See: fileformat --help"._s);
}


/*******************************************************************************
 * Returns english field names as keys and aligned field names as values.
 */
string[string] getAlignedColumnFields(bool enableMime) {
    string[] fields = [
        "File: "._s, "Format: "._s, "Description: "._s, "Group: "._s
    ];
    if (enableMime) {
        fields ~= ["MIME type: "._s, "MIME description: "._s];
    }
    alignLeft(fields);
    string[string] fieldTitles = [
        "File: ":        fields[0],
        "Format: ":      fields[1],
        "Description: ": fields[2],
        "Group: ":       fields[3]
    ];
    if (enableMime) {
        fieldTitles["MIME type: "]        = fields[4];
        fieldTitles["MIME description: "] = fields[5];
    }
    return fieldTitles;
}


/*******************************************************************************
 * Prints format information about each file.
 *
 * Params:
 *   files       = File list.
 *   chosenModes = List of types of required information.
 */
void showFormat(string[] files, bool enableMime) {
    auto ft = getAlignedColumnFields(enableMime);
    foreach(i, f; files) {
        if (i > 0) {
            writeln;
        }
        if (!f.exists) {
            stderr.writeln("Error: "._s ~ f ~ ": file not found."._s);
            continue;
        } else if (f.isSymlink) {
            f = amalthea.fs.readLinkRecurse(f);
        }
        if (!f.isRegularFile) {
            stderr.writeln("Error: "._s ~ f ~ ": file is not regular."._s);
            continue;
        }
        writeln(ft["File: "], f);
        auto format = getFileFormat(f);
        if (format.group.empty) {
            format.group = "Other"._s;
        }
        writeln(ft["Format: "], format.format);
        writeln(ft["Description: "], format.description);
        writeln(ft["Group: "], format.group);
        if (enableMime) {
            auto mimeAndDescr = contentTypeGuess(f);
            writeln(ft["MIME type: "], mimeAndDescr[0]);
            writeln(ft["MIME description: "], mimeAndDescr[1]);
        }
    }
}


/// Enumeration for left and right alignment.
enum StringAlignment {
    left,
    right
}


/*******************************************************************************
 * Adds spaces in the end or in the begin of lines.
 * Params:
 *     lines     = Strings whose lenghts will expand to the length
 *                 of the maximum string.
 *     alignment = Direction of aligment of strings as fields.
 */
void alignLines(ref string[] lines, StringAlignment alignment) {
    size_t maxLength = 0;
    foreach(l; lines) {
        size_t nsymbols = l.to!dstring.length;
        if (nsymbols > maxLength) {
            maxLength = nsymbols;
        }
    }
    foreach(ref l; lines) {
        size_t nsymbols = l.to!dstring.length;
        char[] spaces = new char[maxLength - nsymbols];
        foreach(ref el; spaces) {
            el = ' ';
        }
        final switch(alignment) {
            case StringAlignment.left: l ~= spaces.idup; break;
            case StringAlignment.right: l = spaces.idup ~ l;
        }
    }
}


/*******************************************************************************
 * Adds spaces in the end of lines.
 * Params:
 *     lines = Strings whose lenghts will expand to the length
 *             of the maximum string.
 */
void alignLeft(ref string[] lines) {
    alignLines(lines, StringAlignment.left);
}
