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

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-9 by Raw Material Software Ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the GNU General
   Public License (Version 2), as published by the Free Software Foundation.
   A copy of the license is included in the JUCE distribution, or can be found
   online at www.gnu.org/licenses.

   JUCE 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.

  ------------------------------------------------------------------------------

   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.rawmaterialsoftware.com/juce for more information.

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

#include "jucer_OpenDocumentManager.h"
#include "jucer_SourceCodeEditor.h"
#include "Drawable Editor/jucer_DrawableEditor.h"
#include "jucer_ItemPreviewComponent.h"
#include "Component Editor/jucer_ComponentEditor.h"


//==============================================================================
class SourceCodeDocument  : public OpenDocumentManager::Document
{
public:
    SourceCodeDocument (const File& file_)
        : modDetector (file_)
    {
        codeDoc = new CodeDocument();
        reloadFromFile();
    }

    ~SourceCodeDocument()
    {
    }

    bool loadedOk() const                               { return true; }
    bool isForFile (const File& file) const             { return modDetector.getFile() == file; }
    bool isForNode (const ValueTree& node) const        { return false; }
    bool refersToProject (Project& project) const       { return false; }
    const String getName() const                        { return modDetector.getFile().getFileName(); }
    const String getType() const                        { return modDetector.getFile().getFileExtension() + " file"; }
    bool needsSaving() const                            { return codeDoc != 0 && codeDoc->hasChangedSinceSavePoint(); }
    bool hasFileBeenModifiedExternally()                { return modDetector.hasBeenModified(); }

    void reloadFromFile()
    {
        modDetector.updateHash();

        ScopedPointer <InputStream> in (modDetector.getFile().createInputStream());

        if (in != 0)
            codeDoc->loadFromStream (*in);
    }

    bool save()
    {
        TemporaryFile temp (modDetector.getFile());
        ScopedPointer <FileOutputStream> out (temp.getFile().createOutputStream());

        if (out == 0 || ! codeDoc->writeToStream (*out))
            return false;

        out = 0;
        return temp.overwriteTargetFileWithTemporary();
    }

    Component* createEditor()
    {
        CodeTokeniser* tokeniser = 0;

        if (SourceCodeEditor::isCppFile (modDetector.getFile()))
            tokeniser = &cppTokeniser;

        return new SourceCodeEditor (this, *codeDoc, tokeniser);
    }

private:
    FileModificationDetector modDetector;
    ScopedPointer <CodeDocument> codeDoc;
    CPlusPlusCodeTokeniser cppTokeniser;
};

//==============================================================================
class ComponentDocumentType  : public OpenDocumentManager::Document
{
public:
    ComponentDocumentType (Project* project_, const File& file_)
        : project (project_),
          modDetector (file_)
    {
        reloadFromFile();
    }

    ~ComponentDocumentType()
    {
        componentDoc = 0;
    }

    static bool isComponentFile (const File& file)  { return ComponentDocument::isComponentFile (file); }

    bool loadedOk() const                           { return componentDoc != 0; }
    bool isForFile (const File& file) const         { return modDetector.getFile() == file; }
    bool isForNode (const ValueTree& node) const    { return false; }
    bool refersToProject (Project& p) const         { return project == &p; }
    const String getType() const                    { return "Jucer Component"; }
    const String getName() const                    { return modDetector.getFile().getFileName(); }
    bool needsSaving() const                        { return componentDoc != 0 && componentDoc->hasChangedSinceLastSave(); }
    bool hasFileBeenModifiedExternally()            { return modDetector.hasBeenModified(); }

    void reloadFromFile()
    {
        modDetector.updateHash();

        if (componentDoc == 0)
            componentDoc = new ComponentDocument (project, modDetector.getFile());

        if (! componentDoc->reload())
            componentDoc = 0;
    }

    bool save()
    {
        return componentDoc->save();
    }

    Component* createEditor()
    {
        if (componentDoc == 0)
        {
            jassertfalse;
            return 0;
        }

        return new ComponentEditor (this, project, componentDoc);
    }

private:
    Project* project;
    FileModificationDetector modDetector;
    ScopedPointer <ComponentDocument> componentDoc;
};

//==============================================================================
class DrawableDocumentType  : public OpenDocumentManager::Document
{
public:
    DrawableDocumentType (Project* project_, const File& file_)
        : project (project_),
          modDetector (file_)
    {
        reloadFromFile();
    }

    ~DrawableDocumentType()
    {
        drawableDoc = 0;
    }

    static bool isDrawableFile (const File& file)   { return file.hasFileExtension (".drawable"); }

    bool loadedOk() const                           { return drawableDoc != 0; }
    bool isForFile (const File& file) const         { return modDetector.getFile() == file; }
    bool isForNode (const ValueTree& node) const    { return false; }
    bool refersToProject (Project& p) const         { return project == &p; }
    const String getType() const                    { return "Drawable"; }
    const String getName() const                    { return modDetector.getFile().getFileName(); }
    bool needsSaving() const                        { return drawableDoc != 0 && drawableDoc->hasChangedSinceLastSave(); }
    bool hasFileBeenModifiedExternally()            { return modDetector.hasBeenModified(); }

    void reloadFromFile()
    {
        modDetector.updateHash();

        if (drawableDoc == 0)
            drawableDoc = new DrawableDocument (project, modDetector.getFile());

        if (! drawableDoc->reload())
            drawableDoc = 0;
    }

    bool save()
    {
        return drawableDoc->save();
    }

    Component* createEditor()
    {
        jassert (drawableDoc != 0);

        if (drawableDoc == 0)
            return 0;

        return new DrawableEditor (this, project, drawableDoc);
    }

private:
    Project* project;
    FileModificationDetector modDetector;
    ScopedPointer <DrawableDocument> drawableDoc;
};

//==============================================================================
class UnknownDocument  : public OpenDocumentManager::Document
{
public:
    UnknownDocument (Project* project_, const File& file_)
       : project (project_), file (file_)
    {
        reloadFromFile();
    }

    ~UnknownDocument() {}

    bool loadedOk() const                           { return true; }
    bool isForFile (const File& file_) const        { return file == file_; }
    bool isForNode (const ValueTree& node_) const   { return false; }
    bool refersToProject (Project& p) const         { return project == &p; }
    bool needsSaving() const                        { return false; }
    bool save()                                     { return true; }
    bool hasFileBeenModifiedExternally()            { return fileModificationTime != file.getLastModificationTime(); }
    void reloadFromFile()                           { fileModificationTime = file.getLastModificationTime(); }
    const String getName() const                    { return file.getFileName(); }
    Component* createEditor()                       { return new ItemPreviewComponent (file); }

    const String getType() const
    {
        if (file.getFileExtension().isNotEmpty())
            return file.getFileExtension() + " file";

        jassertfalse
        return "Unknown";
    }

private:
    Project* const project;
    const File file;
    Time fileModificationTime;

    UnknownDocument (const UnknownDocument&);
    UnknownDocument& operator= (const UnknownDocument&);
};


//==============================================================================
OpenDocumentManager::OpenDocumentManager()
{
}

OpenDocumentManager::~OpenDocumentManager()
{
    clearSingletonInstance();
}

juce_ImplementSingleton_SingleThreaded (OpenDocumentManager);

//==============================================================================
void OpenDocumentManager::registerEditor (DocumentEditorComponent* editor)
{
    editors.add (editor);
}

void OpenDocumentManager::deregisterEditor (DocumentEditorComponent* editor)
{
    editors.removeValue (editor);
}

//==============================================================================
bool OpenDocumentManager::canOpenFile (const File& file)
{
    return DrawableDocumentType::isDrawableFile (file)
            || SourceCodeEditor::isTextFile (file);
}

OpenDocumentManager::Document* OpenDocumentManager::getDocumentForFile (Project* project, const File& file)
{
    for (int i = documents.size(); --i >= 0;)
        if (documents.getUnchecked(i)->isForFile (file))
            return documents.getUnchecked(i);

    Document* d = 0;

    if (ComponentDocumentType::isComponentFile (file))
        d = new ComponentDocumentType (project, file);
    else if (DrawableDocumentType::isDrawableFile (file))
        d = new DrawableDocumentType (project, file);
    else if (SourceCodeEditor::isTextFile (file))
        d = new SourceCodeDocument (file);
    else
        d = new UnknownDocument (project, file);

    documents.add (d);
    commandManager->commandStatusChanged();
    return d;
}

int OpenDocumentManager::getNumOpenDocuments() const
{
    return documents.size();
}

OpenDocumentManager::Document* OpenDocumentManager::getOpenDocument (int index) const
{
    return documents.getUnchecked (index);
}

void OpenDocumentManager::moveDocumentToTopOfStack (Document* doc)
{
    for (int i = documents.size(); --i >= 0;)
    {
        if (doc == documents.getUnchecked(i))
        {
            documents.move (i, 0);
            commandManager->commandStatusChanged();
            break;
        }
    }
}

FileBasedDocument::SaveResult OpenDocumentManager::saveIfNeededAndUserAgrees (OpenDocumentManager::Document* doc)
{
    if (! doc->needsSaving())
        return FileBasedDocument::savedOk;

    const int r = AlertWindow::showYesNoCancelBox (AlertWindow::QuestionIcon,
                                                   TRANS("Closing document..."),
                                                   TRANS("Do you want to save the changes to \"")
                                                       + doc->getName() + "\"?",
                                                   TRANS("save"),
                                                   TRANS("discard changes"),
                                                   TRANS("cancel"));

    if (r == 1)
    {
        // save changes
        return doc->save() ? FileBasedDocument::savedOk
                           : FileBasedDocument::failedToWriteToFile;
    }
    else if (r == 2)
    {
        // discard changes
        return FileBasedDocument::savedOk;
    }

    return FileBasedDocument::userCancelledSave;
}


bool OpenDocumentManager::closeDocument (int index, bool saveIfNeeded)
{
    Document* doc = documents [index];

    if (doc != 0)
    {
        if (saveIfNeeded)
        {
            if (saveIfNeededAndUserAgrees (doc) != FileBasedDocument::savedOk)
                return false;
        }

        for (int i = editors.size(); --i >= 0;)
            if (editors.getUnchecked(i)->getDocument() == doc)
                editors.getUnchecked(i)->deleteSelf();

        documents.remove (index);
        commandManager->commandStatusChanged();
    }

    return true;
}

bool OpenDocumentManager::closeDocument (Document* document, bool saveIfNeeded)
{
    return closeDocument (documents.indexOf (document), saveIfNeeded);
}

void OpenDocumentManager::closeFile (const File& f, bool saveIfNeeded)
{
    for (int i = documents.size(); --i >= 0;)
    {
        Document* d = documents.getUnchecked (i);

        if (d->isForFile (f))
            closeDocument (i, saveIfNeeded);
    }
}

bool OpenDocumentManager::closeAllDocumentsUsingProject (Project& project, bool saveIfNeeded)
{
    for (int i = documents.size(); --i >= 0;)
    {
        Document* d = documents.getUnchecked (i);

        if (d->refersToProject (project))
        {
            if (! closeDocument (i, saveIfNeeded))
                return false;
        }
    }

    return true;
}

bool OpenDocumentManager::anyFilesNeedSaving() const
{
    for (int i = documents.size(); --i >= 0;)
    {
        Document* d = documents.getUnchecked (i);

        if (d->needsSaving())
            return true;
    }

    return false;
}

bool OpenDocumentManager::saveAll()
{
    for (int i = documents.size(); --i >= 0;)
    {
        Document* d = documents.getUnchecked (i);

        if (! d->save())
            return false;
    }

    return true;
}

void OpenDocumentManager::reloadModifiedFiles()
{
    for (int i = documents.size(); --i >= 0;)
    {
        Document* d = documents.getUnchecked (i);

        if (d->hasFileBeenModifiedExternally())
            d->reloadFromFile();
    }
}
