/* Copyright 2022 the SumatraPDF project authors (see AUTHORS file).
   License: GPLv3 */

#include "utils/BaseUtil.h"
#include "utils/WinUtil.h"

#include "wingui/UIModels.h"

#include "Settings.h"
#include "AppSettings.h"
#include "DisplayMode.h"
#include "DocController.h"
#include "EngineBase.h"
#include "EngineAll.h"
#include "DisplayModel.h"
#include "GlobalPrefs.h"
#include "Annotation.h"
#include "SumatraConfig.h"
#include "SumatraPDF.h"
#include "MainWindow.h"
#include "WindowTab.h"
#include "Commands.h"
#include "ExternalViewers.h"
#include "FileHistory.h"
#include "Installer.h"
#include "TextToSpeech.h"
#include "CommandAvailability.h"

// clang-format off

static UINT_PTR gNoDocWhitelist[] = {
    CmdOpenFile,
    CmdExit,
    CmdNewWindow,
    CmdContributeTranslation,
    CmdOptions,
    CmdAdvancedOptions,
    CmdAdvancedSettings,
    CmdChangeLanguage,
    CmdCheckUpdate,
    CmdHelpOpenManual,
    CmdHelpOpenManualOnWebsite,
    CmdHelpOpenKeyboardShortcuts,
    CmdHelpVisitWebsite,
    CmdHelpAbout,
    CmdDebugDownloadSymbols,
    CmdDebugShowNotif,
    CmdDebugStartStressTest,
    CmdDebugTestApp,
    CmdDebugTogglePredictiveRender,
    CmdDebugToggleRtl,
    CmdChangeScrollbar,
    CmdToggleAntiAlias,
    CmdToggleSmoothScroll,
    CmdToggleScrollbarInSinglePage,
    CmdToggleLazyLoading,
    CmdToggleEscToExit,
    CmdToggleFullscreen,
    CmdToggleMenuBar,
    CmdToggleToolbar,
    CmdToggleUseTabs,
    CmdToggleTabsMru,
    CmdToggleTips,
    CmdToggleFrequentlyRead,
    CmdToggleChmUI,
    CmdToggleReuseInstance,
    CmdToggleInverseSearch,
    CmdToggleLinks,
    CmdToggleWindowsPreviewer,
    CmdToggleWindowsSearchFilter,
    CmdInvertColors,
    CmdFavoriteToggle,
    CmdShowLog,
    CmdClearHistory,
    CmdReopenLastClosedFile,
    CmdSelectNextTheme,
    CmdListPrinters,
    CmdDebugCrashMe,
    CmdDebugCorruptMemory,
    CmdScreenshot,
    CmdPasteClipboardImage,
    CmdTabGroupRestore,
    CmdSetScreenshotHotkey,
    0,
};

static UINT_PTR gDisableIfNoSelection[] = {
    CmdCopySelection,
    CmdTranslateSelectionWithDeepL,
    CmdTranslateSelectionWithGoogle,
    CmdSearchSelectionWithWikipedia,
    CmdSearchSelectionWithGoogleScholar,
    CmdSearchSelectionWithBing,
    CmdSearchSelectionWithGoogle,
    CmdCreateAnnotHighlight,
    CmdCreateAnnotSquiggly,
    CmdCreateAnnotStrikeOut,
    CmdCreateAnnotUnderline,
    0,
};

static UINT_PTR removeIfNoInternetPerms[] = {
    CmdCheckUpdate,
    CmdTranslateSelectionWithGoogle,
    CmdTranslateSelectionWithDeepL,
    CmdSearchSelectionWithGoogle,
    CmdSearchSelectionWithBing,
    CmdSearchSelectionWithWikipedia,
    CmdSearchSelectionWithGoogleScholar,
    CmdHelpVisitWebsite,
    CmdHelpOpenManualOnWebsite,
    CmdHelpOpenKeyboardShortcuts,
    CmdContributeTranslation,
    0,
};

static UINT_PTR removeIfNoFullscreenPerms[] = {
    CmdTogglePresentationMode,
    CmdToggleFullscreen,
    0,
};

static UINT_PTR removeIfNoPrefsPerms[] = {
    CmdOptions,
    CmdAdvancedOptions,
    CmdPinSelectedDocument,
    CmdForgetSelectedDocument,
    CmdFavoriteAdd,
    CmdFavoriteDel,
    CmdFavoriteToggle,
    0,
};

static UINT_PTR removeIfNoCopyPerms[] = {
    CmdTranslateSelectionWithGoogle,
    CmdTranslateSelectionWithDeepL,
    CmdSearchSelectionWithGoogle,
    CmdSearchSelectionWithBing,
    CmdSearchSelectionWithWikipedia,
    CmdSearchSelectionWithGoogleScholar,
    CmdSelectAll,
    CmdCopySelection,
    CmdCopyLinkTarget,
    CmdCopyComment,
    CmdCopyImage,
    0,
};

static UINT_PTR removeIfNoDiskAccessPerm[] = {
    CmdNewWindow,
    CmdOpenFile,
    CmdOpenNextFileInFolder,
    CmdOpenPrevFileInFolder,
    CmdClose,
    CmdShowInFolder,
    CmdSaveAs,
    CmdRenameFile,
    CmdDeleteFile,
    CmdSendByEmail,
    CmdContributeTranslation,
    CmdAdvancedOptions,
    CmdAdvancedSettings,
    CmdFavoriteAdd,
    CmdFavoriteDel,
    CmdFavoriteToggle,
    CmdOpenSelectedDocument,
    CmdPinSelectedDocument,
    CmdForgetSelectedDocument,
    CmdInvokeInverseSearch,
    CmdPasteClipboardImage,
    CmdCreateShortcutToFile,
    CmdSaveEmbeddedFile,
    CmdShowLog,
    0,
};

static UINT_PTR removeIfAnnotsNotSupported[] = {
    CmdSaveAnnotations,
    CmdSaveAnnotationsNewFile,
    CmdEditAnnotations,
    CmdDeleteAnnotation,
    CmdShowAnnotations,
    CmdHideAnnotations,
    CmdToggleShowAnnotations,
    0,
};

static UINT_PTR removeIfChm[] = {
    CmdSinglePageView,
    CmdFacingView,
    CmdBookView,
    CmdToggleContinuousView,
    CmdRotateLeft,
    CmdRotateRight,
    CmdTogglePresentationMode,
    CmdZoomFitPage,
    CmdZoomActualSize,
    CmdZoomFitWidth,
    CmdZoomFitContent,
    CmdZoomShrinkToFit,
    CmdZoom6400,
    CmdZoom3200,
    CmdZoom1600,
    CmdZoom800,
    CmdZoom12_5,
    CmdZoom8_33,
    CmdInvokeInverseSearch,
    0,
};

static i32 gBlacklistCommandsFromPalette[] = {
    CmdNone,
    CmdOpenWithKnownExternalViewerFirst,
    CmdOpenWithKnownExternalViewerLast,
    CmdCommandPalette,
    CmdNextTabSmart,
    CmdPrevTabSmart,
    CmdSetTheme,
    CmdOpenSelectedDocument,
    CmdPinSelectedDocument,
    CmdForgetSelectedDocument,
    CmdExpandAll,
    CmdCollapseAll,
    CmdMoveFrameFocus,
    CmdFavoriteDel,
    CmdPresentationWhiteBackground,
    CmdPresentationBlackBackground,
    CmdSaveEmbeddedFile,
    CmdOpenEmbeddedPDF,
    CmdSaveAttachment,
    CmdOpenAttachment,
    CmdCreateShortcutToFile,
    0,
};

static i32 gCommandsDebugOnly[] = {
    CmdDebugCorruptMemory,
    CmdDebugCrashMe,
    CmdDebugDownloadSymbols,
    CmdDebugTestApp,
    CmdDebugShowNotif,
    CmdDebugStartStressTest,
    0,
};

// clang-format on

static bool CmdIdInList(int cmdId, UINT_PTR* ids) {
    for (int i = 0; ids[i]; i++) {
        if ((int)ids[i] == cmdId) {
            return true;
        }
    }
    return false;
}

static bool CmdIdInI32List(int cmdId, i32* ids) {
    while (*ids) {
        if (cmdId == *ids) {
            return true;
        }
        ids++;
    }
    return false;
}

static CommandVisibility MapForSurface(CommandVisibility v, CommandSurface surface) {
    if (surface == CommandSurface::Palette && v == CommandVisibility::Disable) {
        return CommandVisibility::Hide;
    }
    return v;
}

bool CmdWorksWithoutDocument(int cmdId) {
    return CmdIdInList(cmdId, gNoDocWhitelist);
}

static void PopulateTabCloseFlags(AppCommandCtx& ctx) {
    if (!ctx.win) {
        return;
    }
    int nTabs = ctx.win->TabCount();
    ctx.nTabs = nTabs;
    WindowTab* currTab = ctx.tab;
    int tabIdx = ctx.win->GetTabIdx(currTab);
    ctx.canCloseTabsToRight = tabIdx < (nTabs - 1);
    ctx.canCloseTabsToLeft = false;
    int nFirstDocTab = 0;
    for (int i = 0; i < nTabs; i++) {
        WindowTab* t = ctx.win->GetTab(i);
        if (t->IsAboutTab()) {
            nFirstDocTab = 1;
            continue;
        }
        ctx.hasDocTabs = true;
        if (t == currTab) {
            if (i > nFirstDocTab) {
                ctx.canCloseTabsToLeft = true;
            }
            continue;
        }
        ctx.canCloseOtherTabs = true;
    }
}

AppCommandCtx NewAppCommandCtx(MainWindow* win, Point cursorPos) {
    AppCommandCtx ctx;
    ctx.win = win;
    ctx.cursorPos = cursorPos;
    if (!win) {
        return ctx;
    }

    ctx.tab = win->CurrentTab();
    ctx.isDocLoaded = win->IsDocLoaded();
    ctx.filePath = ctx.tab ? ctx.tab->filePath : nullptr;
    ctx.allowToggleMenuBar = true;

    if (ctx.tab) {
        ctx.isChm = ctx.tab->AsChm();
        EngineBase* engine = ctx.tab->GetEngine();
        if (engine && engine->kind == kindEngineComicBooks) {
            ctx.isCbx = true;
        }
        if (engine && engine->IsImageCollection()) {
            ctx.isImageCollection = true;
        }
        ctx.engineKind = ctx.tab->GetEngineType();
        ctx.canSendEmail = CanSendAsEmailAttachment(ctx.tab);
        ctx.isPdf = CouldBePDFDoc(ctx.tab);
        if (ctx.isPdf && engine) {
            ctx.isPdfEncrypted = EngineMupdfIsEncrypted(engine);
        }
        ctx.canContinueReadAloud = CanContinueReadAloud(ctx.tab);
    }

    ctx.hasSelection = ctx.isDocLoaded && ctx.tab && win->showSelection && ctx.tab->selectionOnPage;

    if (ctx.isDocLoaded && win->ctrl) {
        ctx.isSinglePage = IsSingle(win->ctrl->GetDisplayMode());
        ctx.pageCount = win->ctrl->PageCount();
        ctx.hasToc = win->ctrl->HasToc();
    }

    DisplayModel* dm = win->AsFixed();
    if (dm) {
        auto engine = dm->GetEngine();
        ctx.supportsAnnots = EngineSupportsAnnotations(engine) && !win->isFullScreen;
        ctx.hasUnsavedAnnotations = EngineHasUnsavedAnnotations(engine);
        int pageNoUnderCursor = dm->GetPageNoByPoint(cursorPos);
        if (pageNoUnderCursor > 0) {
            ctx.isCursorOnPage = true;
        }
        ctx.annotationUnderCursor = dm->GetAnnotationAtPos(cursorPos, nullptr);
        IPageElement* pageEl = dm->GetElementAtPos(cursorPos, nullptr);
        if (pageEl) {
            char* value = pageEl->GetValue();
            ctx.cursorOnLinkTarget = value && pageEl->Is(kindPageElementDest);
            ctx.cursorOnComment = value && pageEl->Is(kindPageElementComment);
            ctx.cursorOnImage = pageEl->Is(kindPageElementImage);
        }
    }

    if (!CanAccessDisk()) {
        ctx.supportsAnnots = false;
        ctx.hasUnsavedAnnotations = false;
    }

    ctx.isSpeaking = TtsIsSpeaking();
    PopulateTabCloseFlags(ctx);
    return ctx;
}

CommandVisibility GetCommandVisibility(int cmdId, const AppCommandCtx& ctx, CommandSurface surface) {
    if (cmdId <= CmdFirst) {
        return CommandVisibility::Hide;
    }

    CustomCommand* cmd = FindCustomCommand(cmdId);
    int origCmdId = cmd ? cmd->origId : 0;
    if (origCmdId == CmdSetTheme) {
        return CommandVisibility::Show;
    }

    if (surface == CommandSurface::Palette) {
        if (CmdIdInI32List(cmdId, gCommandsDebugOnly)) {
            if (!gIsDebugBuild) {
                return CommandVisibility::Hide;
            }
        }
        if (CmdIdInI32List(cmdId, gBlacklistCommandsFromPalette)) {
            return CommandVisibility::Hide;
        }
    }

    if (CmdCloseOtherTabs == cmdId) {
        return ctx.canCloseOtherTabs ? CommandVisibility::Show : CommandVisibility::Hide;
    }
    if (CmdCloseTabsToTheRight == cmdId) {
        return ctx.canCloseTabsToRight ? CommandVisibility::Show : CommandVisibility::Hide;
    }
    if (CmdCloseTabsToTheLeft == cmdId) {
        return ctx.canCloseTabsToLeft ? CommandVisibility::Show : CommandVisibility::Hide;
    }
    if (CmdReopenLastClosedFile == cmdId) {
        return RecentlyCloseDocumentsCount() > 0 ? CommandVisibility::Show : CommandVisibility::Hide;
    }
    if (cmdId == CmdTabGroupSave) {
        if (surface == CommandSurface::Palette) {
            return ctx.hasDocTabs ? CommandVisibility::Show : CommandVisibility::Hide;
        }
        bool enabled = ctx.tab && ctx.tab->win && HasOpenedDocuments(ctx.tab->win);
        return enabled ? CommandVisibility::Show : CommandVisibility::Disable;
    }
    if (cmdId == CmdNextTab || cmdId == CmdPrevTab || cmdId == CmdNextTabSmart || cmdId == CmdPrevTabSmart ||
        cmdId == CmdMoveTabLeft || cmdId == CmdMoveTabRight) {
        return ctx.nTabs >= 2 ? CommandVisibility::Show : CommandVisibility::Hide;
    }
    if ((cmdId == CmdToggleWindowsPreviewer || cmdId == CmdToggleWindowsSearchFilter) && !IsOurExeInstalled()) {
        return CommandVisibility::Hide;
    }

    if (CmdWorksWithoutDocument(cmdId)) {
        return MapForSurface(CommandVisibility::Show, surface);
    }

    if (!ctx.isDocLoaded) {
        return CommandVisibility::Hide;
    }

    if (ctx.tab) {
        int idFirst = CmdOpenWithKnownExternalViewerFirst + 1;
        int idLast = CmdOpenWithKnownExternalViewerLast;
        if (cmdId >= idFirst && cmdId <= idLast) {
            bool canView = CanViewWithKnownExternalViewer(ctx.tab, cmdId);
            return canView ? CommandVisibility::Show : CommandVisibility::Hide;
        }
    }

    bool isKnownEV = (cmdId >= CmdOpenWithKnownExternalViewerFirst) && (cmdId <= CmdOpenWithKnownExternalViewerLast);
    if (origCmdId == CmdViewWithExternalViewer || isKnownEV) {
        if (isKnownEV) {
            bool canView = HasKnownExternalViewerForCmd(cmdId);
            return canView ? CommandVisibility::Show : CommandVisibility::Hide;
        }
        const char* filter = GetCommandStringArg(cmd, kCmdArgFilter, nullptr);
        bool matches = PathMatchFilter(ctx.filePath, filter);
        return matches ? CommandVisibility::Show : CommandVisibility::Hide;
    }

    if ((origCmdId == CmdSelectionHandler) || CmdIdInList(cmdId, gDisableIfNoSelection)) {
        return ctx.hasSelection ? CommandVisibility::Show : CommandVisibility::Disable;
    }

    if (surface == CommandSurface::Palette && cmdId == CmdToggleFrequentlyRead) {
        return CommandVisibility::Hide;
    }

    if (cmdId == CmdToggleMenuBar) {
        return ctx.allowToggleMenuBar ? CommandVisibility::Show : CommandVisibility::Hide;
    }

    if (!ctx.supportsAnnots) {
        if ((cmdId >= (int)CmdCreateAnnotFirst) && (cmdId <= (int)CmdCreateAnnotLast)) {
            return CommandVisibility::Hide;
        }
        if (CmdIdInList(cmdId, removeIfAnnotsNotSupported)) {
            return CommandVisibility::Hide;
        }
    }

    if (ctx.isChm && CmdIdInList(cmdId, removeIfChm)) {
        return CommandVisibility::Hide;
    }

    if (!ctx.canSendEmail && cmdId == CmdSendByEmail) {
        return CommandVisibility::Hide;
    }

    if (!ctx.isPdf) {
        if (cmdId == CmdPdShowInfo || cmdId == CmdPdfBake || cmdId == CmdPdfCompress || cmdId == CmdPdfDecompress ||
            cmdId == CmdPdfEncrypt || cmdId == CmdPdfDecrypt || cmdId == CmdPdfDeletePages ||
            cmdId == CmdPdfExtractPages) {
            return CommandVisibility::Hide;
        }
    }
    if (ctx.pageCount < 2) {
        if (cmdId == CmdPdfDeletePages || cmdId == CmdPdfExtractPages) {
            return CommandVisibility::Hide;
        }
    }
    if (ctx.isPdf && ctx.isPdfEncrypted && cmdId == CmdPdfEncrypt) {
        return CommandVisibility::Hide;
    }
    if (ctx.isPdf && !ctx.isPdfEncrypted && cmdId == CmdPdfDecrypt) {
        return CommandVisibility::Hide;
    }

    if (!ctx.hasToc && cmdId == CmdDocumentShowOutline) {
        return CommandVisibility::Hide;
    }

    if (cmdId == CmdDocumentExtractText) {
        bool canExtract = ctx.engineKind == kindEngineMupdf || ctx.engineKind == kindEngineDjVu;
        if (!canExtract || ctx.isImageCollection) {
            return CommandVisibility::Hide;
        }
    }

    if (cmdId == CmdToggleMangaMode) {
        if (surface == CommandSurface::Palette && ctx.isSinglePage) {
            return CommandVisibility::Hide;
        }
        if (!ctx.isCbx) {
            return CommandVisibility::Hide;
        }
    }

    if (!ctx.annotationUnderCursor && cmdId == CmdDeleteAnnotation) {
        return CommandVisibility::Disable;
    }

    if ((cmdId == CmdSaveAnnotations) || (cmdId == CmdSaveAnnotationsNewFile)) {
        return ctx.hasUnsavedAnnotations ? CommandVisibility::Show : CommandVisibility::Disable;
    }

    if ((cmdId == CmdCheckUpdate) && gIsStoreBuild) {
        return CommandVisibility::Hide;
    }

    if (!HasPermission(Perm::InternetAccess) && CmdIdInList(cmdId, removeIfNoInternetPerms)) {
        return CommandVisibility::Hide;
    }
    if (!HasPermission(Perm::FullscreenAccess) && CmdIdInList(cmdId, removeIfNoFullscreenPerms)) {
        return CommandVisibility::Hide;
    }
    if (!HasPermission(Perm::SavePreferences) && CmdIdInList(cmdId, removeIfNoPrefsPerms)) {
        return CommandVisibility::Hide;
    }
    if (!HasPermission(Perm::PrinterAccess) && cmdId == CmdPrint) {
        return CommandVisibility::Hide;
    }
    if (!CanAccessDisk()) {
        if (CmdIdInList(cmdId, removeIfNoDiskAccessPerm)) {
            return CommandVisibility::Hide;
        }
        if (CmdIdInList(cmdId, removeIfAnnotsNotSupported)) {
            return CommandVisibility::Hide;
        }
        if (cmdId >= CmdOpenWithKnownExternalViewerFirst && cmdId <= CmdOpenWithKnownExternalViewerLast) {
            return CommandVisibility::Hide;
        }
    }
    if (!HasPermission(Perm::CopySelection) && CmdIdInList(cmdId, removeIfNoCopyPerms)) {
        return CommandVisibility::Hide;
    }

    if (!ctx.cursorOnLinkTarget && cmdId == CmdCopyLinkTarget) {
        return CommandVisibility::Hide;
    }
    if (!ctx.cursorOnComment && cmdId == CmdCopyComment) {
        return CommandVisibility::Hide;
    }
    if (!ctx.cursorOnImage && cmdId == CmdCopyImage) {
        return CommandVisibility::Hide;
    }
    if ((cmdId == CmdToggleBookmarks) || (cmdId == CmdToggleTableOfContents)) {
        return ctx.hasToc ? CommandVisibility::Show : CommandVisibility::Hide;
    }

    if (cmdId == CmdPauseReadAloud) {
        return ctx.isSpeaking ? CommandVisibility::Show : CommandVisibility::Hide;
    }
    if (cmdId == CmdContinueReadAloud) {
        return (ctx.canContinueReadAloud && !ctx.isSpeaking) ? CommandVisibility::Show : CommandVisibility::Hide;
    }
    if (cmdId == CmdStopReadAloud) {
        return (ctx.isSpeaking || ctx.canContinueReadAloud) ? CommandVisibility::Show : CommandVisibility::Hide;
    }
    if (cmdId == CmdReadAloudSelection) {
        return ctx.hasSelection ? CommandVisibility::Show : CommandVisibility::Hide;
    }

    return MapForSurface(CommandVisibility::Show, surface);
}
