/*
 * Copyright (C) 2019-2021 Eugene 'Vindex' Stulin
 *
 * fragplayer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import amalthea.langlocal;
import std.array, std.file, std.path, std.process, std.range, std.string;

import fragpointer_module;

immutable app = "fragplayer";
immutable string shareDir;
immutable string helpDir;

immutable string invalidRange;

shared static this() {
    initLocalization(translations);
    invalidRange = "Invalid range of the fragment."._s;
    string currentApp = readLink("/proc/self/exe");
    shareDir = buildNormalizedPath(dirName(currentApp), "..", "share");
    helpDir = shareDir ~ "/help/";
}


void showHelp() {
    immutable lang = getSystemLanguage();
    string pathToHelpFile = helpDir ~ lang ~ "/" ~ app ~ "/help.txt";
    if (!exists(pathToHelpFile)) {
        pathToHelpFile = helpDir ~ "en_US/" ~ app ~ "/help.txt";
    }
    writeln(readText(pathToHelpFile));
}


void checkRangeByCondition(bool condition) {
    if (!condition) {
        throw new InvalidFragPointer(invalidRange);
    }
}


int main(string[] args) {
    if (args.length != 2) {
        stderr.writefln("%s %s",
            "Wrong using of fragplayer."._s,
            "See: fragplayer --help"._s
        );
        return 1;
    }
    
    if (args[1] == "--version") {
        fragpointer_module.showVersion();
        return 0;
    }
    
    string pathToFragmentPointer = args[1];
    try {
        runFragment(pathToFragmentPointer);
    } catch(InvalidFragPointer e) {
        stderr.writeln(e.msg);
        return 2;
    } catch(Exception e) {
        stderr.writeln(e.msg);
        return 3;
    }
    return 0;
}


void runFragment(string pathToFragmentPointer) {
    string content = readText(pathToFragmentPointer);
    content = fragPointerPreprocessing(content);
    string[] lines = content.split("\n").array;

    MediaType mediaType;
    string filepath;
    string start, finish;
    mediaType = lines[0].split("Type: ")[1].strip(" ").to!MediaType;
    filepath  = lines[1].split("Path: ")[1].strip(" ");
    if (!filepath.exists) {
        throw new FileException(filepath ~ ": " ~ "No such file."._s);
    }
    start = lines[2].split("Start: ")[1].strip(" ");
    finish = lines[3].split("Finish: ")[1].strip(" ");
    checkPointerValidity(mediaType, start);
    checkPointerValidity(mediaType, finish);

    final switch(mediaType) {
        case MediaType.video:
            vlcPlay(filepath, start, finish, MediaType.video);
            break;
        case MediaType.audio:
            vlcPlay(filepath, start, finish, MediaType.audio);
            break;
        case MediaType.text: 
            openTextFragment(filepath, start, finish);
            break;
    }
}


void openTextFragment(string filepath,
                      string start,
                      string finish) {
    size_t startLine, startColumn, finishLine, finishColumn;
    if (start.empty) {
        startLine = 1;
        startColumn = 1;
    } else {
        startLine    = start.split(',')[0].strip(" ").to!size_t;
        startColumn  = start.split(',')[1].strip(" ").to!size_t;
    }

    if (finish.empty) {
        filepath.lastLineAndLastColumn(finishLine, finishColumn);
    } else {
        finishLine   = finish.split(',')[0].strip(" ").to!size_t;
        finishColumn = finish.split(',')[1].strip(" ").to!size_t;
    }
    checkRangeByCondition(startLine <= finishLine);
    if (startLine == finishLine) {
        checkRangeByCondition(startColumn <= finishColumn);
    }

    dstring textFragment;
    auto f = File(filepath, "r");
    ulong currentLineNumber = 0;
    bool searchingOfBegin = true;
    foreach(l; f.byLine) {
        dstring line = l.to!dstring;
        currentLineNumber++;
        if (searchingOfBegin) {
            if (currentLineNumber != startLine) continue;
            searchingOfBegin = false;
            checkRangeByCondition(line.length >= startColumn);
            if (startLine == finishLine) {
                checkRangeByCondition(line.length >= finishColumn);
                textFragment ~= line[startColumn-1 .. finishColumn].dup ~ '\n';
                break;
            } else {
                textFragment ~= line[startColumn-1 .. $].dup ~ '\n';
            }
        } else { //searching of end
            if (currentLineNumber != finishLine) {
                textFragment ~= line.dup ~ '\n';
            } else {
                checkRangeByCondition(line.length >= finishColumn);
                textFragment ~= line[0 .. finishColumn].dup ~ '\n';
                break;
            }
        }
    }
    checkRangeByCondition(currentLineNumber == finishLine);
    string tempfile = "/tmp/fragplayer/"~baseName(filepath);
    mkdirRecurse("/tmp/fragplayer/");
    std.file.write(tempfile, textFragment.to!string);
    executeShell("ufo \"" ~ tempfile ~ "\"");
}


void lastLineAndLastColumn(string filepath,
                           out size_t lastLineNumber,
                           out size_t lastColumnNumber) {
    string[] content = readText(filepath).split("\n");
    lastLineNumber = content.length - 1;
    dstring dstringLastLine = content[lastLineNumber - 1].to!dstring;
    lastColumnNumber = dstringLastLine.length;
}


void vlcPlay(string filepath,
             string startTime,
             string finishTime,
             MediaType mediaType,
             bool cvlcMode = false) {
    static double convStringToSeconds(string time) {
        auto hms = time.split(":");
        double seconds = hms[0].to!ulong * 3600 + hms[1].to!ulong * 60;
        if (hms.length == 3) {
            seconds += hms[2].to!double;
        }
        return seconds;
    }
    double start, finish;
    string startOption, finishOption;
    if (!startTime.empty) {
        start = convStringToSeconds(startTime);
        startOption  = "--start-time " ~ start.to!string;
    }
    if (!finishTime.empty) {
        finish = convStringToSeconds(finishTime);
        finishOption = "--stop-time " ~ finish.to!string;
    }

    auto timeOpts = startOption ~ " " ~ finishOption;
    string playbackOpts;
    if (mediaType == MediaType.audio) {
        playbackOpts = "--play-and-exit";
    } else {
        playbackOpts = "--play-and-exit -f --disable-screensaver";
    }
    auto opts = playbackOpts ~ " " ~ timeOpts;
    string app = cvlcMode ? "cvlc" : "vlc";
    auto cmd = app ~ ` "`~filepath~`" ` ~ opts;
    debug writeln(cmd);
    executeShell(cmd);
}


immutable translations = [
    ["en_US", "ru_RU", "eo"],
    [
        "Wrong using of fragplayer.",
        "Неправильное использование fragplayer.",
        "Neregula uzado de fragplayer."
    ],
    [
        "See: fragplayer --help",
        "Смотрите: fragplayer --help",
        "Rigardu: fragplayer --help"
    ],
    [
        "Invalid range of the fragment.",
        "Некорректный диапазон фрагмента.",
        "Erara intervalo de la fragrmento."
    ],
    [
        "No such file.",
        "Нет такого файла.",
        "Ĉi tiu dosiero ne ekzistas."
    ],
];
