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

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   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 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-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.

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

#include "../../../Application/jucer_Headers.h"
#include "jucer_DependencyPathPropertyComponent.h"

//==============================================================================
DependencyPathValueSource::DependencyPathValueSource (const Value& projectSettingsPath,
                                                      Identifier globalSettingsKey,
                                                      DependencyPathOS osThisSettingAppliesTo)
  : projectSettingsValue (projectSettingsPath),
    globalKey (globalSettingsKey),
    os (osThisSettingAppliesTo),
    globalSettingsValue (getAppSettings().getStoredPath (globalKey)),
    fallbackValue (getAppSettings().getFallbackPathForOS (globalKey, os))
{
    globalSettingsValue.addListener (this);
    fallbackValue.addListener (this);
}

bool DependencyPathValueSource::isValidPath (const File& relativeTo) const
{
    // if we are on another OS than the one which this path setting is for,
    // we have no way of knowing whether the path is valid - so just assume it is:
    if (! appliesToThisOS())
        return true;

    return getAppSettings().isGlobalPathValid (relativeTo, globalKey, getValue().toString());
}

bool DependencyPathValueSource::isValidPath() const
{
    return isValidPath (File::getCurrentWorkingDirectory());
}

//==============================================================================
DependencyPathPropertyComponent::DependencyPathPropertyComponent (const File& pathRelativeToUse,
                                                                  const Value& value,
                                                                  const String& propertyName)
try : TextPropertyComponent (propertyName, 1024, false),
      pathRelativeTo (pathRelativeToUse),
      pathValue (value),
      pathValueSource (dynamic_cast<DependencyPathValueSource&> (pathValue.getValueSource()))
{
    bool initialValueIsEmpty = ! pathValueSource.isUsingProjectSettings();

    getValue().referTo (pathValue);

    // the following step is necessary because the above referTo() has internally called setValue(),
    // which has set the project value to whatever is displayed in the label (this may be the
    // global/fallback value). In this case we have to reset the project value to blank:
    if (initialValueIsEmpty)
        getValue().setValue (String());

    getValue().addListener (this);

    if (auto* label = dynamic_cast<Label*> (getChildComponent (0)))
        label->onEditorShow = [this, label] { setEditorText (label); };
    else
        jassertfalse;

    lookAndFeelChanged();
}
catch (const std::bad_cast&)
{
    // a DependencyPathPropertyComponent must be initialised with a Value
    // that is referring to a DependencyPathValueSource!
    jassertfalse;
    throw;
}

void DependencyPathPropertyComponent::valueChanged (Value& value)
{
    // this callback handles the update of this setting in case
    // the user changed the global preferences.
    if (value.refersToSameSourceAs (pathValue) && pathValueSource.isUsingGlobalSettings())
        textWasEdited();
}

void DependencyPathPropertyComponent::textWasEdited()
{
    setColour (textColourId, getTextColourToDisplay());
    TextPropertyComponent::textWasEdited();
}

Colour DependencyPathPropertyComponent::getTextColourToDisplay() const
{
    if (! pathValueSource.isUsingProjectSettings())
        return pathValueSource.isValidPath (pathRelativeTo) ? findColour (widgetTextColourId).withMultipliedAlpha (0.5f)
                                                            : Colours::red.withMultipliedAlpha (0.5f);

    return pathValueSource.isValidPath (pathRelativeTo) ? findColour (widgetTextColourId)
                                                        : Colours::red;
}

void DependencyPathPropertyComponent::setEditorText (Label* label)
{
    if (! pathValueSource.isUsingProjectSettings())
        if (auto editor = label->getCurrentTextEditor())
            editor->setText (String(), dontSendNotification);
}

void DependencyPathPropertyComponent::lookAndFeelChanged()
{
    textWasEdited();
}

//==============================================================================
DependencyFilePathPropertyComponent::DependencyFilePathPropertyComponent (Value& value,
                                                                          const String& propertyDescription,
                                                                          bool isDir,
                                                                          const String& wc,
                                                                          const File& rootToUseForRelativePaths)
try : TextPropertyComponent (propertyDescription, 1024, false),
      pathRelativeTo (rootToUseForRelativePaths),
      pathValue (value),
      pathValueSource (dynamic_cast<DependencyPathValueSource&> (pathValue.getValueSource())),
      browseButton ("..."),
      isDirectory (isDir),
      wildcards (wc)
{
    auto initialValueIsEmpty = ! pathValueSource.isUsingProjectSettings();

    getValue().referTo (pathValue);

    if (initialValueIsEmpty)
        getValue().setValue (String());

    getValue().addListener (this);

    if (auto* label = dynamic_cast<Label*> (getChildComponent (0)))
        label->onEditorShow = [this, label] { setEditorText (label); };
    else
        jassertfalse;

    setInterestedInFileDrag (false);

    addAndMakeVisible (browseButton);
    browseButton.onClick = [this] { browse(); };

    lookAndFeelChanged();
}
catch (const std::bad_cast&)
{
    // a DependencyPathPropertyComponent must be initialised with a Value
    // that is referring to a DependencyPathValueSource!
    jassertfalse;
    throw;
}

void DependencyFilePathPropertyComponent::resized()
{
    auto bounds = getLookAndFeel().getPropertyComponentContentPosition (*this);

    browseButton.setBounds (bounds.removeFromRight (30));
    getChildComponent (0)->setBounds (bounds);
}

void DependencyFilePathPropertyComponent::paintOverChildren (Graphics& g)
{
    if (highlightForDragAndDrop)
    {
        g.setColour (findColour (defaultHighlightColourId).withAlpha (0.5f));
        g.fillRect (getChildComponent (0)->getBounds());
    }
}

void DependencyFilePathPropertyComponent::filesDropped (const StringArray& files, int, int)
{
    const File firstFile (files[0]);

    if (isDirectory)
        setTo (firstFile.isDirectory() ? firstFile
                                       : firstFile.getParentDirectory());
    else
        setTo (firstFile);

    highlightForDragAndDrop = false;
}

void DependencyFilePathPropertyComponent::setTo (const File& f)
{
    pathValue = (pathRelativeTo == File()) ? f.getFullPathName()
                                           : f.getRelativePathFrom (pathRelativeTo);

    textWasEdited();
}

void DependencyFilePathPropertyComponent::enablementChanged()
{
    getValue().referTo (isEnabled() ? pathValue
                                    : pathValueSource.appliesToThisOS() ? pathValueSource.getGlobalSettingsValue()
                                                                        : pathValueSource.getFallbackSettingsValue());
    textWasEdited();
    repaint();
}

void DependencyFilePathPropertyComponent::textWasEdited()
{
    setColour (textColourId, getTextColourToDisplay());
    TextPropertyComponent::textWasEdited();
}

void DependencyFilePathPropertyComponent::valueChanged (Value& value)
{
    if ((value.refersToSameSourceAs (pathValue) && pathValueSource.isUsingGlobalSettings())
             || value.refersToSameSourceAs (pathValueSource.getGlobalSettingsValue()))
        textWasEdited();
}

void DependencyFilePathPropertyComponent::setEditorText (Label* label)
{
    if (! pathValueSource.isUsingProjectSettings())
        if (auto editor = label->getCurrentTextEditor())
            editor->setText (String(), dontSendNotification);
}

void DependencyFilePathPropertyComponent::browse()
{
    auto currentFile = pathRelativeTo.getChildFile (pathValue.toString());

    if (isDirectory)
    {
        FileChooser chooser ("Select directory", currentFile);

        if (chooser.browseForDirectory())
            setTo (chooser.getResult());
    }
    else
    {
        FileChooser chooser ("Select file", currentFile, wildcards);

        if (chooser.browseForFileToOpen())
            setTo (chooser.getResult());
    }
}

Colour DependencyFilePathPropertyComponent::getTextColourToDisplay() const
{
    auto alpha = 1.0f;
    auto key = pathValueSource.getKey();
    const auto& globalSettingsValue = pathValueSource.getGlobalSettingsValue();

    if (! pathValueSource.isUsingProjectSettings() && isEnabled())
        alpha = 0.5f;

    if ((key == Ids::defaultUserModulePath && getValue().toString().contains (";")) || ! pathValueSource.appliesToThisOS())
        return findColour (widgetTextColourId).withMultipliedAlpha (alpha);

    auto usingGlobalPath = (getValue().refersToSameSourceAs (globalSettingsValue));

    auto isValidPath = getAppSettings().isGlobalPathValid (pathRelativeTo, key,
                                                           (usingGlobalPath ? globalSettingsValue : pathValue).toString());

    return isValidPath ? findColour (widgetTextColourId).withMultipliedAlpha (alpha)
                       : Colours::red.withMultipliedAlpha (alpha);
}
