/************************************************************************
**
**  Copyright (C) 2009, 2010, 2011  Strahinja Markovic  <strahinja.markovic@gmail.com>
**
**  This file is part of Sigil.
**
**  Sigil 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 3 of the License, or
**  (at your option) any later version.
**
**  Sigil 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 Sigil.  If not, see <http://www.gnu.org/licenses/>.
**
*************************************************************************/

#include "BookManipulation/CleanSource.h"
#include "Misc/SettingsStore.h"
#include "ResourceObjects/Resource.h"
#include "ResourceObjects/CSSResource.h"
#include "ResourceObjects/OPFResource.h"
#include "ResourceObjects/NCXResource.h"
#include "ResourceObjects/HTMLResource.h"
#include "ResourceObjects/ImageResource.h"
#include "ResourceObjects/MiscTextResource.h"
#include "ResourceObjects/SVGResource.h"
#include "Tabs/CSSTab.h"
#include "Tabs/FlowTab.h"
#include "Tabs/ImageTab.h"
#include "Tabs/MiscTextTab.h"
#include "Tabs/SVGTab.h"
#include "Tabs/NCXTab.h"
#include "Tabs/OPFTab.h"
#include "Tabs/TabManager.h"
#include "Tabs/WellFormedContent.h"
#include "Tabs/TabBar.h"

TabManager::TabManager(QWidget *parent)
    :
    QTabWidget(parent)
{
    QTabBar *tab_bar = new TabBar(this);
    setTabBar(tab_bar);
    connect(tab_bar, SIGNAL(TabBarDoubleClicked()),      this, SIGNAL(ToggleViewStateRequest()));
    connect(tab_bar, SIGNAL(TabBarClicked()),            this, SLOT(SetFocusInTab()));
    connect(tab_bar, SIGNAL(CloseOtherTabsRequest(int)), this, SLOT(CloseOtherTabs(int)));
    connect(this, SIGNAL(currentChanged(int)),         this, SLOT(EmitTabChanged()));
    connect(this, SIGNAL(tabCloseRequested(int)),      this, SLOT(CloseTab(int)));
    setDocumentMode(true);
    setMovable(true);
    setTabsClosable(true);
    setUsesScrollButtons(true);
}


ContentTab &TabManager::GetCurrentContentTab()
{
    QWidget *widget = currentWidget();
    // TODO: turn on this assert after you make sure a tab
    // is created before this is called in MainWindow constructor
    //Q_ASSERT( widget != NULL );
    return *qobject_cast< ContentTab * >(widget);
}

QList<ContentTab *> TabManager::GetContentTabs()
{
    QList <ContentTab *> tabs;

    for (int i = 0; i < count(); ++i) {
        ContentTab *tab = qobject_cast<ContentTab *>(widget(i));
        tabs.append(tab);
    }

    return tabs;
}

QList<Resource *> TabManager::GetTabResources()
{
    QList <ContentTab *> tabs = GetContentTabs();
    QList <Resource *> tab_resources;
    foreach(ContentTab * tab, tabs) {
        tab_resources.append(&tab->GetLoadedResource());
    }
    return tab_resources;
}

void TabManager::tabInserted(int index)
{
    emit TabCountChanged();
}

int TabManager::GetTabCount()
{
    return count();
}

bool TabManager::TryCloseAllTabs()
{
    bool status;

    while (count() > 0) {
        status = TryCloseTab(0);

        if (!status) {
            return false;
        }
    }

    return true;
}


bool TabManager::TryCloseTab(int tab_index)
{
    Q_ASSERT(tab_index >= 0);
    WellFormedContent *content = GetWellFormedContent(tab_index);

    if (content && !content->IsDataWellFormed()) {
        return false;
    }

    ContentTab *tab = qobject_cast<ContentTab *>(widget(tab_index));
    tab->Close();
    emit TabCountChanged();
    return true;
}

bool TabManager::CloseTabForResource(const Resource &resource)
{
    int index = ResourceTabIndex(resource);

    if (index != -1) {
        return TryCloseTab(index);
    }

    // Tab for resource so it's not open.
    return true;
}

bool TabManager::IsAllTabDataWellFormed()
{
    QList<Resource *> resources = GetTabResources();
    foreach(Resource * resource, resources) {
        int index = ResourceTabIndex(*resource);
        WellFormedContent *content = dynamic_cast<WellFormedContent *>(widget(index));

        if (content) {
            if (!content->IsDataWellFormed()) {
                return false;
            }
        }
    }
    return true;
}

void TabManager::ReloadTabDataForResources(const QList<Resource *> &resources)
{
    foreach(Resource * resource, resources) {
        int index = ResourceTabIndex(*resource);

        if (index != -1) {
            FlowTab *flow_tab = qobject_cast<FlowTab *>(widget(index));

            if (flow_tab) {
                flow_tab->LoadTabContent();
            }
        }
    }
}

void TabManager::ReopenTabs(MainWindow::ViewState view_state)
{
    ContentTab &currentTab = GetCurrentContentTab();
    QList<Resource *> resources = GetTabResources();
    foreach(Resource * resource, resources) {
        CloseTabForResource(*resource);
    }
    foreach(Resource * resource, resources) {
        OpenResource(*resource, -1, -1, QString(), view_state);
    }
    OpenResource(currentTab.GetLoadedResource(), -1, -1, QString(), view_state);
}


void TabManager::SaveTabData()
{
    for (int i = 0; i < count(); ++i) {
        ContentTab *tab = qobject_cast< ContentTab * >(widget(i));

        if (tab) {
            tab->SaveTabContent();
        }
    }
}

void TabManager::LinkClicked(const QUrl &url)
{
    if (url.toString().isEmpty()) {
        return;
    }

    ContentTab &tab = GetCurrentContentTab();
    QString url_string = url.toString();

    // Convert fragments to full filename/fragments
    if (url_string.startsWith("#")) {
        url_string.prepend(tab.GetFilename());
    } else if (url.scheme() == "file") {
        if (url_string.contains("/#")) {
            url_string.insert(url_string.indexOf("/#") + 1, tab.GetFilename());
        }
    }

    emit OpenUrlRequest(QUrl(url_string));
}

void TabManager::OpenResource(Resource &resource,
                              int line_to_scroll_to,
                              int position_to_scroll_to,
                              const QString &caret_location_to_scroll_to,
                              MainWindow::ViewState view_state,
                              const QUrl &fragment,
                              bool precede_current_tab)
{
    if (SwitchedToExistingTab(resource, line_to_scroll_to, position_to_scroll_to, caret_location_to_scroll_to, fragment)) {
        return;
    }

    bool grab_focus = !precede_current_tab;
    ContentTab *new_tab = CreateTabForResource(resource, line_to_scroll_to, position_to_scroll_to,
                          caret_location_to_scroll_to, view_state, fragment, grab_focus);

    if (new_tab) {
        AddNewContentTab(new_tab, precede_current_tab);
        emit ShowStatusMessageRequest("");
    } else {
        QString message = tr("Cannot edit file") + ": " + resource.Filename();
        emit ShowStatusMessageRequest(message);
    }
}


void TabManager::NextTab()
{
    int current_index = currentIndex();

    // No tabs present
    if (current_index == -1) {
        return;
    }

    // Wrap around
    int next_index = current_index != count() - 1 ? current_index + 1 : 0;

    if (widget(next_index) != 0 &&
        current_index != next_index) {
        setCurrentIndex(next_index);
    }
}


void TabManager::PreviousTab()
{
    int current_index = currentIndex();

    // No tabs present
    if (current_index == -1) {
        return;
    }

    // Wrap around
    int previous_index = current_index != 0 ? current_index - 1 : count() - 1;

    if (widget(previous_index) != 0 &&
        current_index != previous_index) {
        setCurrentIndex(previous_index);
    }
}


void TabManager::RemoveTab()
{
    // Can leave window with no tabs, so re-open a tab asap
    removeTab(currentIndex());
}


void TabManager::CloseTab()
{
    CloseTab(currentIndex());
}

void TabManager::CloseOtherTabs()
{
    CloseOtherTabs(currentIndex());
}

void TabManager::CloseOtherTabs(int index)
{
    if (count() <= 1 || index < 0 || index >= count()) {
        return;
    }

    int max_index = count() - 1;

    // Close all tabs after the tab
    for (int i = index + 1; i <= max_index; i++) {
        CloseTab(index + 1);
    }

    // Close all tabs before the tab
    for (int i = 0; i < index; i++) {
        CloseTab(0);
    }
}


void TabManager::MakeCentralTab(ContentTab *tab)
{
    Q_ASSERT(tab);
    setCurrentIndex(indexOf(tab));
}

void TabManager::EmitTabChanged()
{
    ContentTab *current_tab = qobject_cast< ContentTab * >(currentWidget());

    if (m_LastContentTab.data() != current_tab) {
        emit TabChanged(m_LastContentTab.data(), current_tab);
        m_LastContentTab = QWeakPointer< ContentTab >(current_tab);
    }
}


void TabManager::DeleteTab(ContentTab *tab_to_delete)
{
    Q_ASSERT(tab_to_delete);
    removeTab(indexOf(tab_to_delete));
    tab_to_delete->deleteLater();
}


void TabManager::CloseTab(int tab_index)
{
    if (count() <= 1) {
        return;
    }

    TryCloseTab(tab_index);
}


void TabManager::UpdateTabName(ContentTab *renamed_tab)
{
    Q_ASSERT(renamed_tab);
    setTabText(indexOf(renamed_tab), renamed_tab->GetFilename());
}

void TabManager::SetFocusInTab()
{
    ContentTab &tab = GetCurrentContentTab();

    if (&tab != NULL) {
        tab.setFocus();
    }
}


WellFormedContent *TabManager::GetWellFormedContent(int index)
{
    return dynamic_cast< WellFormedContent * >(widget(index));
}



// Returns the index of the tab the index is loaded in, -1 if it isn't
int TabManager::ResourceTabIndex(const Resource &resource) const
{
    QString identifier(resource.GetIdentifier());
    int index = -1;

    for (int i = 0; i < count(); ++i) {
        ContentTab *tab = qobject_cast< ContentTab * >(widget(i));

        if (tab && tab->GetLoadedResource().GetIdentifier() == identifier) {
            index = i;
            break;
        }
    }

    return index;
}


bool TabManager::SwitchedToExistingTab(Resource &resource,
                                       int line_to_scroll_to,
                                       int position_to_scroll_to,
                                       const QString &caret_location_to_scroll_to,
                                       const QUrl &fragment)
{
    int resource_index = ResourceTabIndex(resource);

    // If the resource is already opened in
    // some tab, then we just switch to it
    if (resource_index != -1) {
        setCurrentIndex(resource_index);
        QWidget *tab = widget(resource_index);
        Q_ASSERT(tab);
        tab->setFocus();
        FlowTab *flow_tab = qobject_cast< FlowTab * >(tab);

        if (flow_tab != NULL) {
            if (!caret_location_to_scroll_to.isEmpty()) {
                flow_tab->ScrollToCaretLocation(caret_location_to_scroll_to);
            } else if (position_to_scroll_to > 0) {
                flow_tab->ScrollToPosition(position_to_scroll_to);
            } else if (!fragment.toString().isEmpty()) {
                flow_tab->ScrollToFragment(fragment.toString());
            } else if (line_to_scroll_to > 0) {
                flow_tab->ScrollToLine(line_to_scroll_to);
            }

            return true;
        }

        TextTab *text_tab = qobject_cast< TextTab * >(tab);

        if (text_tab != NULL) {
            text_tab->ScrollToLine(line_to_scroll_to);
            return true;
        }

        ImageTab *image_tab = qobject_cast< ImageTab * >(tab);

        if (image_tab != NULL) {
            return true;
        }
    }

    return false;
}


ContentTab *TabManager::CreateTabForResource(Resource &resource,
        int line_to_scroll_to,
        int position_to_scroll_to,
        const QString &caret_location_to_scroll_to,
        MainWindow::ViewState view_state,
        const QUrl &fragment,
        bool grab_focus)
{
    ContentTab *tab = NULL;
    SettingsStore ss;

    switch (resource.Type()) {
        case Resource::HTMLResourceType: {
            HTMLResource *html_resource = qobject_cast<HTMLResource *>(&resource);
            if (!html_resource) {
                break;
            }
            tab = new FlowTab(*html_resource,
                              fragment,
                              view_state,
                              line_to_scroll_to,
                              position_to_scroll_to,
                              caret_location_to_scroll_to,
                              grab_focus,
                              this);
            connect(tab,  SIGNAL(LinkClicked(const QUrl &)), this, SLOT(LinkClicked(const QUrl &)));
            connect(tab,  SIGNAL(OldTabRequest(QString, HTMLResource &)),
                    this, SIGNAL(OldTabRequest(QString, HTMLResource &)));
            if (ss.cleanOn() & CLEANON_OPEN) {
                html_resource->SetText(CleanSource::Clean(html_resource->GetText()));
            }
            break;
        }

        case Resource::CSSResourceType: {
            tab = new CSSTab(*(qobject_cast< CSSResource * >(&resource)), line_to_scroll_to, this);
            break;
        }

        case Resource::ImageResourceType: {
            tab = new ImageTab(*(qobject_cast< ImageResource * >(&resource)), this);
            break;
        }

        case Resource::MiscTextResourceType: {
            tab = new MiscTextTab(*(qobject_cast< MiscTextResource * >(&resource)), line_to_scroll_to, this);
            break;
        }

        case Resource::SVGResourceType: {
            tab = new SVGTab(*(qobject_cast< SVGResource * >(&resource)), line_to_scroll_to, this);
            break;
        }

        case Resource::OPFResourceType: {
            tab = new OPFTab(*(qobject_cast< OPFResource * >(&resource)), line_to_scroll_to, this);
            break;
        }

        case Resource::NCXResourceType: {
            tab = new NCXTab(*(qobject_cast< NCXResource * >(&resource)), line_to_scroll_to, this);
            break;
        }

        case Resource::XMLResourceType: {
            tab = new XMLTab(*(qobject_cast< XMLResource * >(&resource)), line_to_scroll_to, this);
            break;
        }

        case Resource::TextResourceType: {
            tab = new TextTab(*(qobject_cast< TextResource * >(&resource)), CodeViewEditor::Highlight_NONE, line_to_scroll_to, this);
            break;
        }

        default:
            break;
    }

    // Set whether to inform or auto correct well-formed errors.
    WellFormedContent *wtab = dynamic_cast< WellFormedContent * >(tab);

    if (wtab) {
        // In case of well-formed errors we want the tab to be focused.
        connect(tab,  SIGNAL(CentralTabRequest(ContentTab *)),
                this, SLOT(MakeCentralTab(ContentTab *)));    //, Qt::QueuedConnection );
    }

    return tab;
}


bool TabManager::AddNewContentTab(ContentTab *new_tab, bool precede_current_tab)
{
    if (new_tab == NULL) {
        return false;
    }

    if (!precede_current_tab) {
        addTab(new_tab, new_tab->GetIcon(), new_tab->GetFilename());
        setCurrentWidget(new_tab);
        new_tab->setFocus();
    } else {
        insertTab(currentIndex(), new_tab, new_tab->GetIcon(), new_tab->GetFilename());
    }

    connect(new_tab, SIGNAL(DeleteMe(ContentTab *)), this, SLOT(DeleteTab(ContentTab *)));
    connect(new_tab, SIGNAL(TabRenamed(ContentTab *)), this, SLOT(UpdateTabName(ContentTab *)));
    return true;
}

void TabManager::UpdateTabDisplay()
{
    for (int i = 0; i < count(); ++i) {
        ContentTab *tab = dynamic_cast<ContentTab *>(widget(i));

        if (tab) {
            tab->UpdateDisplay();
        }
    }
}
