/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2022 - Raw Material Software Limited

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 7 End-User License
   Agreement and JUCE Privacy Policy.

   End User License Agreement: www.juce.com/juce-7-licence
   Privacy Policy: www.juce.com/juce-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
class FileChooser::NonNative final : public std::enable_shared_from_this<NonNative>,
                                     public FileChooser::Pimpl
{
public:
    NonNative (FileChooser& fileChooser, int flags, FilePreviewComponent* preview)
        : owner (fileChooser),
          selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories)   != 0),
          selectsFiles       ((flags & FileBrowserComponent::canSelectFiles)         != 0),
          warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting)   != 0),

          filter (selectsFiles ? owner.filters : String(), selectsDirectories ? "*" : String(), {}),
          browserComponent (flags, owner.startingFile, &filter, preview),
          dialogBox (owner.title, {}, browserComponent, warnAboutOverwrite,
                     browserComponent.findColour (AlertWindow::backgroundColourId), owner.parent)
    {}

    ~NonNative() override
    {
        dialogBox.exitModalState (0);
    }

    void launch() override
    {
        dialogBox.centreWithDefaultSize (nullptr);

        const std::weak_ptr<NonNative> ref (shared_from_this());
        auto* callback = ModalCallbackFunction::create ([ref] (int r)
        {
            if (auto locked = ref.lock())
                locked->modalStateFinished (r);
        });

        dialogBox.enterModalState (true, callback, true);
    }

    void runModally() override
    {
       #if JUCE_MODAL_LOOPS_PERMITTED
        modalStateFinished (dialogBox.show() ? 1 : 0);
       #else
        jassertfalse;
       #endif
    }

private:
    void modalStateFinished (int returnValue)
    {
        Array<URL> result;

        if (returnValue != 0)
        {
            for (int i = 0; i < browserComponent.getNumSelectedFiles(); ++i)
                result.add (URL (browserComponent.getSelectedFile (i)));
        }

        owner.finished (result);
    }

    //==============================================================================
    FileChooser& owner;
    bool selectsDirectories, selectsFiles, warnAboutOverwrite;

    WildcardFileFilter filter;
    FileBrowserComponent browserComponent;
    FileChooserDialogBox dialogBox;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NonNative)
};

//==============================================================================
FileChooser::FileChooser (const String& chooserBoxTitle,
                          const File& currentFileOrDirectory,
                          const String& fileFilters,
                          const bool useNativeBox,
                          const bool treatFilePackagesAsDirectories,
                          Component* parentComponentToUse)
    : title (chooserBoxTitle),
      filters (fileFilters),
      startingFile (currentFileOrDirectory),
      parent (parentComponentToUse),
      useNativeDialogBox (useNativeBox && isPlatformDialogAvailable()),
      treatFilePackagesAsDirs (treatFilePackagesAsDirectories)
{
   #ifndef JUCE_MAC
    ignoreUnused (treatFilePackagesAsDirs);
   #endif

    if (! fileFilters.containsNonWhitespaceChars())
        filters = "*";
}

FileChooser::~FileChooser()
{
    asyncCallback = nullptr;
}

#if JUCE_MODAL_LOOPS_PERMITTED
bool FileChooser::browseForFileToOpen (FilePreviewComponent* previewComp)
{
    return showDialog (FileBrowserComponent::openMode
                        | FileBrowserComponent::canSelectFiles,
                       previewComp);
}

bool FileChooser::browseForMultipleFilesToOpen (FilePreviewComponent* previewComp)
{
    return showDialog (FileBrowserComponent::openMode
                        | FileBrowserComponent::canSelectFiles
                        | FileBrowserComponent::canSelectMultipleItems,
                       previewComp);
}

bool FileChooser::browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComp)
{
    return showDialog (FileBrowserComponent::openMode
                        | FileBrowserComponent::canSelectFiles
                        | FileBrowserComponent::canSelectDirectories
                        | FileBrowserComponent::canSelectMultipleItems,
                       previewComp);
}

bool FileChooser::browseForFileToSave (const bool warnAboutOverwrite)
{
    return showDialog (FileBrowserComponent::saveMode
                        | FileBrowserComponent::canSelectFiles
                        | (warnAboutOverwrite ? FileBrowserComponent::warnAboutOverwriting : 0),
                       nullptr);
}

bool FileChooser::browseForDirectory()
{
    return showDialog (FileBrowserComponent::openMode
                        | FileBrowserComponent::canSelectDirectories,
                       nullptr);
}

bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previewComp)
{
    detail::FocusRestorer focusRestorer;

    pimpl = createPimpl (flags, previewComp);
    pimpl->runModally();

    // ensure that the finished function was invoked
    jassert (pimpl == nullptr);

    return (results.size() > 0);
}
#endif

void FileChooser::launchAsync (int flags, std::function<void (const FileChooser&)> callback,
                               FilePreviewComponent* previewComp)
{
    // You must specify a callback when using launchAsync
    jassert (callback);

    // you cannot run two file chooser dialog boxes at the same time
    jassert (asyncCallback == nullptr);

    asyncCallback = std::move (callback);

    pimpl = createPimpl (flags, previewComp);
    pimpl->launch();
}

std::shared_ptr<FileChooser::Pimpl> FileChooser::createPimpl (int flags, FilePreviewComponent* previewComp)
{
    results.clear();

    // the preview component needs to be the right size before you pass it in here..
    jassert (previewComp == nullptr || (previewComp->getWidth() > 10
                                         && previewComp->getHeight() > 10));

    if (pimpl != nullptr)
    {
        // you cannot run two file chooser dialog boxes at the same time
        jassertfalse;
        pimpl.reset();
    }

    // You've set the flags for both saveMode and openMode!
    jassert (! (((flags & FileBrowserComponent::saveMode) != 0)
                && ((flags & FileBrowserComponent::openMode) != 0)));

   #if JUCE_WINDOWS
    const bool selectsFiles       = (flags & FileBrowserComponent::canSelectFiles) != 0;
    const bool selectsDirectories = (flags & FileBrowserComponent::canSelectDirectories) != 0;

    if (useNativeDialogBox && ! (selectsFiles && selectsDirectories))
   #else
    if (useNativeDialogBox)
   #endif
    {
        return showPlatformDialog (*this, flags, previewComp);
    }

    return std::make_unique<NonNative> (*this, flags, previewComp);
}

Array<File> FileChooser::getResults() const noexcept
{
    Array<File> files;

    for (auto url : getURLResults())
        if (url.isLocalFile())
            files.add (url.getLocalFile());

    return files;
}

File FileChooser::getResult() const
{
    auto fileResults = getResults();

    // if you've used a multiple-file select, you should use the getResults() method
    // to retrieve all the files that were chosen.
    jassert (fileResults.size() <= 1);

    return fileResults.getFirst();
}

URL FileChooser::getURLResult() const
{
    // if you've used a multiple-file select, you should use the getResults() method
    // to retrieve all the files that were chosen.
    jassert (results.size() <= 1);

    return results.getFirst();
}

void FileChooser::finished (const Array<URL>& asyncResults)
{
    const auto callback = std::exchange (asyncCallback, nullptr);

    results = asyncResults;

    pimpl.reset();

    if (callback)
        callback (*this);
}

#if ! JUCE_ANDROID
void FileChooser::registerCustomMimeTypeForFileExtension ([[maybe_unused]] const String& mimeType,
                                                          [[maybe_unused]] const String& fileExtension)
{
}
#endif

//==============================================================================
FilePreviewComponent::FilePreviewComponent() {}
FilePreviewComponent::~FilePreviewComponent() {}

} // namespace juce
