/***************************************************************************
 *   crwx-ng                                                               *
 *   Copyright (C) 2007-2010,2012 Vadim Lopatin <coolreader.org@gmail.com> *
 *   Copyright (C) 2020,2023 Aleksey Chernov <valexlin@gmail.com>          *
 *                                                                         *
 *   This program 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 2        *
 *   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, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,            *
 *   MA 02110-1301, USA.                                                   *
 ***************************************************************************/

#include "readerapp.h"

#include <wx/image.h>
#include <wx/settings.h>
#include <wx/cmdline.h>

#include <crengine.h>

#include <stdio.h>

#include "rescont.h"
#include "utils.h"
#include "readerframe.h"
#include "config.h"

namespace resources
{
#include "resources/icons_res.h"
}

// Maximum cache directory size
#define DOC_CACHE_SIZE 128 * 1024 * 1024 // 128Mb

// forward declaration
#if (USE_FREETYPE == 1)
bool getDirectoryFonts(const lString32Collection& pathList, const lString32Collection& ext, lString32Collection& fonts,
                       bool absPath);
#endif

IMPLEMENT_APP(ReaderApp)

static const wxCmdLineEntryDesc cmdLineDesc[] = {
    { wxCMD_LINE_SWITCH, "h", "help", "Show this help message and exit", wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
    { wxCMD_LINE_SWITCH, "v", "version", "Print the version of the program and exit" },
    { wxCMD_LINE_OPTION, "d", "loglevel", "Specify log level", wxCMD_LINE_VAL_STRING },
    { wxCMD_LINE_OPTION, "l", "logfile", "Specify log file", wxCMD_LINE_VAL_STRING },
    { wxCMD_LINE_PARAM, NULL, NULL, "input file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
    { wxCMD_LINE_NONE }
};

bool ReaderApp::OnInit() {
#ifdef _DEBUG
    CRLog::log_level loglevel = CRLog::LL_TRACE;
    lString8 logfile("stdout");
#else
    CRLog::log_level loglevel = CRLog::LL_ERROR;
    lString8 logfile("stderr");
#endif
    wxString fnameToOpen;

    // Parse command line
    wxCmdLineParser parser(cmdLineDesc, argc, argv);
    parser.SetLogo("crwx-ng " VERSION);
    int parseRet = parser.Parse(true);
    if (-1 == parseRet) {
        // help was given
        return false;
    } else if (parseRet > 0) {
        // Parse error
        // ignore this
    } else {
        // Ok
        if (parser.Found("version")) {
            printf("crwx-ng %s\n", VERSION);
            return false;
        }
        wxString value;
        if (parser.Found("loglevel", &value)) {
            if (0 == value.CmpNoCase("trace"))
                loglevel = CRLog::LL_TRACE;
            else if (0 == value.CmpNoCase("debug"))
                loglevel = CRLog::LL_DEBUG;
            else if (0 == value.CmpNoCase("info"))
                loglevel = CRLog::LL_DEBUG;
            else if (0 == value.CmpNoCase("warn"))
                loglevel = CRLog::LL_WARN;
            else if (0 == value.CmpNoCase("error"))
                loglevel = CRLog::LL_ERROR;
            else if (0 == value.CmpNoCase("fatal"))
                loglevel = CRLog::LL_FATAL;
            else {
                fprintf(stderr, "Invalid log level: %s!\n", value.mb_str().data());
                fprintf(stderr, "Available levels:\n");
                fprintf(stderr, "  TRACE\n");
                fprintf(stderr, "  DEBUG\n");
                fprintf(stderr, "  INFO\n");
                fprintf(stderr, "  WARN\n");
                fprintf(stderr, "  ERROR\n");
                fprintf(stderr, "  FATAL\n");
                return false;
            }
        }
        if (parser.Found("logfile", &value)) {
            if (value.IsEmpty()) {
                fprintf(stderr, "You can't specify empty string as log file!\n");
                return false;
            }
            logfile = UnicodeToUtf8(wx2cr(value));
        }
        if (parser.GetParamCount() > 0)
            fnameToOpen = parser.GetParam(0);
    }

    // Init log subsystem
    if ("stdout" == logfile)
        CRLog::setStdoutLogger();
    else if ("stderr" == logfile)
        CRLog::setStderrLogger();
    else
        CRLog::setFileLogger(logfile.c_str());
    CRLog::setLogLevel(loglevel);

    lString32 configDir = getConfigDir();
    lString32 engineDataDir = getEngineDataDir();
    lString32 dataDir = getMainDataDir();

    // Create a config directory if it doesn't already exist
    if (!LVDirectoryExists(configDir))
        LVCreateDirectory(configDir);

    // Init embedded resources (icons)
    wxImage::AddHandler(new wxPNGHandler);
    if (!ResourceContainer::instance().OpenFromMemory(resources::icons_zip_data, resources::icons_zip_data_size)) {
        CRLog::error("Failed to open resource icons_zip_data!");
        return false;
    }
#if 0
    // Test resource availability
    wxBitmap icon = ResourceContainer::instance().GetBitmap(U"icons/16x16/help.png");
    if ( icon.IsOk() ) {
        printf( "Image opened: %dx%d:%d\n", icon.GetWidth(), icon.GetHeight(), icon.GetDepth() );
    }
#endif

    // crengine-ng: init fonts
    lString32Collection fontDirs;
    lString32 homefonts = configDir + cs32("fonts");
    fontDirs.add(homefonts);
    InitFontManager(lString8::empty_str);
    lString32Collection fontExt;
#if USE_FREETYPE == 1
    fontExt.add(cs32(".ttf"));
    fontExt.add(cs32(".ttc"));
    fontExt.add(cs32(".otf"));
    fontExt.add(cs32(".pfa"));
    fontExt.add(cs32(".pfb"));
#else
    fontExt.add(cs32(".lbf"));
#endif
    lString32 fontsDataDir = dataDir + cs32("fonts");
    LVAppendPathDelimiter(fontsDataDir);
    fontDirs.add(fontsDataDir);

#if USE_FREETYPE == 1
#if !defined(USE_FONTCONFIG) || (USE_FONTCONFIG == 0)
#if defined(_LINUX)
    lString32 shareFonts = cs32("/usr/share/fonts/");
    LVContainerRef dir = LVOpenDirectory(shareFonts);
    if (!dir.isNull()) {
        for (int i = 0; i < dir->GetObjectCount(); i++) {
            const LVContainerItemInfo* item = dir->GetObjectInfo(i);
            if (NULL != item) {
                if (item->IsContainer()) {
                    lString32 name = item->GetName();
                    if ((cs32(".") == name) || (cs32("..") == name))
                        continue;
                    lString32 path = shareFonts + name;
                    LVAppendPathDelimiter(path);
                    fontDirs.add(path);
                }
            }
        }
    }
#elif MACOS == 1
    fontDirs.add(cs32("/Library/Fonts"));
    fontDirs.add(cs32("/System/Library/Fonts"));
    fontDirs.add(cs32("/System/Library/Fonts/Supplemental"));
    lString32 home = wx2cr(wxGetHomeDir());
    fontDirs.add(home + cs32("/Library/Fonts"));
#elif defined(_WIN32)
    // TODO: test this
    wchar_t wd_buf[MAX_PATH];
    wd_buf[0] = 0;
    ::GetWindowsDirectoryW(wd_buf, MAX_PATH - 1);
    lString16 sysFontDir16 = lString16(wd_buf) + U"\\fonts\\";
    lString32 sysFontDir = Utf16ToUnicode(sysFontDir16);
    fontDirs.add(sysFontDir);
#endif // _WIN32
#endif // !USE_FONTCONFIG
#endif // USE_FREETYPE==1
    lString32Collection fonts;
    getDirectoryFonts(fontDirs, fontExt, fonts, true);
    // Load fonts from files
    CRLog::debug("%d font files found", fonts.length());
    for (int fi = 0; fi < fonts.length(); fi++) {
        lString8 fn = UnicodeToLocal(fonts[fi]);
        CRLog::trace("loading font: %s", fn.c_str());
        if (!fontMan->RegisterFont(fn)) {
            CRLog::trace("    failed\n");
        }
    }
    CRLog::trace("%d fonts loaded.", fontMan->GetFontCount());
    if (fontMan->GetFontCount() < 1) {
        //error
#if (USE_FREETYPE == 1)
        CRLog::fatal("Fatal Error: Cannot open font file(s) .ttf \nCannot work without font");
#else
        CRLog::fatal(
                "Fatal Error: Cannot open font file(s) font#.lbf \nCannot work without font\nUse FontConv utility to generate .lbf fonts from TTF");
#endif
        return false;
    }

    // crengine-ng: init engine cache dir
    ldomDocCache::init(getEngineCacheDir(), (lvsize_t)DOC_CACHE_SIZE);

    // crengine-ng: init hyphenation manager
    lString32 mainHyphDir = engineDataDir + cs32("hyph");
    LVAppendPathDelimiter(mainHyphDir);
    lString32 userHyphDir = configDir + cs32("hyph");
    LVAppendPathDelimiter(userHyphDir);
    HyphMan::initDictionaries(mainHyphDir, true);
    HyphMan::initDictionaries(userHyphDir, false);

    if (!fnameToOpen.IsEmpty())
        CRLog::info("File to open: %s", LCSTR(wx2cr(fnameToOpen)));

#ifdef _WIN32
    //::GetSystemMetrics()
    RECT wrc;
    SystemParametersInfo(SPI_GETWORKAREA, 0, &wrc, 0);
    int x = wrc.left;
    int y = wrc.top;
    int cx = wrc.right - wrc.left;
    int cy = wrc.bottom - wrc.top;
#else
    int x = 20;
    int y = 40;
    int cx = wxSystemSettings::GetMetric(wxSYS_SCREEN_X);
    int cy = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y) - 40;
#endif
    int scale_x = cx * 256 / 620;
    int scale_y = cy * 256 / 830;
    int scale = scale_x < scale_y ? scale_x : scale_y;
    cx = 610 * scale / 256;
    cy = 830 * scale / 256;
    ReaderFrame* frame = new ReaderFrame(wxT("crwx-ng " wxT(VERSION)), fnameToOpen, wxPoint(x, y), wxSize(cx, cy));
    frame->Show(true);
    SetTopWindow(frame);
    return true;
}

int ReaderApp::OnExit() {
    ShutdownFontManager();
    HyphMan::uninit();
#if LDOM_USE_OWN_MEM_MAN == 1
    //ldomFreeStorage();
#endif
    return 0;
}

#if (USE_FREETYPE == 1)
bool getDirectoryFonts(const lString32Collection& pathList, const lString32Collection& ext, lString32Collection& fonts,
                       bool absPath) {
    int foundCount = 0;
    lString32 path;
    for (int di = 0; di < pathList.length(); di++) {
        path = pathList[di];
        LVContainerRef dir = LVOpenDirectory(path.c_str());
        if (!dir.isNull()) {
            CRLog::trace("Checking directory %s", UnicodeToUtf8(path).c_str());
            for (int i = 0; i < dir->GetObjectCount(); i++) {
                const LVContainerItemInfo* item = dir->GetObjectInfo(i);
                lString32 fileName = item->GetName();
                lString8 fn = UnicodeToLocal(fileName);
                //printf(" test(%s) ", fn.c_str() );
                if (!item->IsContainer()) {
                    bool found = false;
                    lString32 lc = fileName;
                    lc.lowercase();
                    for (int j = 0; j < ext.length(); j++) {
                        if (lc.endsWith(ext[j])) {
                            found = true;
                            break;
                        }
                    }
                    if (!found)
                        continue;
                    lString32 fn;
                    if (absPath) {
                        fn = path;
                        if (!fn.empty() && fn[fn.length() - 1] != PATH_SEPARATOR_CHAR)
                            fn << PATH_SEPARATOR_CHAR;
                    }
                    fn << fileName;
                    foundCount++;
                    fonts.add(fn);
                }
            }
        }
    }
    return foundCount > 0;
}
#endif
