/*
 * Copyright 2000 Murray Cumming
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <bakery/App/App_WithDoc.h>
#include <bakery/App/Dialog_OfferSave.h>
#include <gconfmm.h>
#include <giomm.h>
#include <libintl.h>
#include <algorithm>

//#include <gtk/gtkfilesel.h>

#define BAKERY_GCONF_DIRECTORY_RECENTFILES "recent_files"

namespace Bakery
{

App_WithDoc::type_list_strings App_WithDoc::m_mime_types;

App_WithDoc::App_WithDoc(const Glib::ustring& appname)
: App(appname),
  m_pDocument(0),
  m_bCloseAfterSave(false)
{  
}

App_WithDoc::~App_WithDoc()
{
  //Delete the document:
  if(m_pDocument)
  {
    delete m_pDocument; //This will cause Document::signal_forget to be emitted, so the Views will then null their pointers as well. A smartpointer might be a better way to do this.
    m_pDocument = 0;
  }
}

//static
void  App_WithDoc::add_mime_type(const Glib::ustring& mime_type)
{
  if( std::find(m_mime_types.begin(), m_mime_types.end(), mime_type) == m_mime_types.end() )
    m_mime_types.push_back(mime_type);
}

void App_WithDoc::on_menu_file_close()
{
  if(m_pDocument->get_modified())
  {
    //Offer to save changes:
    m_bCloseAfterSave = true; //Checked in FileChooser signal handler.
    offer_to_save_changes(); //If a File|Exit is in progress, this could cancel it.
  }

  if(!get_operation_cancelled())
    ui_hide(); //The AppInstanceManager will delete it.
}

bool App_WithDoc::open_document_from_data(const guchar* data, std::size_t length)
{
  const bool bTest = m_pDocument->load_from_data(data, length);

  bool bOpenFailed = false;
  if(!bTest) //if open failed.
    bOpenFailed = true;
  else
  {
    //if open succeeded then let the App respond:
    const bool test = on_document_load();
    if(!test)
      bOpenFailed = true; //The application didn't like something about the just-loaded document.
    else
    {
      update_window_title();
      set_document_modified(false); //disables menu and toolbar Save items.

      return true; //success.
    }
  }

  if(bOpenFailed)
  {
    ui_warning(gettext("Open failed."), gettext("The document could not be opened."));

    return false; //failed.
  }
  else
    return true;
}

bool App_WithDoc::open_document(const Glib::ustring& file_uri)
{
  //Check whether it's already open:
  //It could even be open in this instance.
  bool bAlreadyOpen = false;
  App_WithDoc* pAppAlreadyOpen = 0;

  AppInstanceManager::type_listAppInstances apps =  m_AppInstanceManager.get_instances();
  for(AppInstanceManager::type_listAppInstances::iterator iter = apps.begin(); iter != apps.end(); iter++)
  {
    App_WithDoc* pApp = dynamic_cast<App_WithDoc*>(*iter);
    if(pApp)
    {
      Document* pDoc = pApp->get_document();
      if(pDoc->get_file_uri() == file_uri)
      {
        bAlreadyOpen = true;
        pAppAlreadyOpen = pApp;
      }
    }
  }

  if(bAlreadyOpen)
  {
    //Bring it to the front:
    if(pAppAlreadyOpen)
    {
      pAppAlreadyOpen->ui_bring_to_front();
    }

    //Tell user that it's already open:
    ui_warning(gettext("Document already open"), gettext("This document is already open."));

    return true; //success.
  }
  else
  {
    //Open it:
        
    //Load it into a new instance unless the current document is just a default new.
    App_WithDoc* pApp = 0;
    bool bUsingNewInstance = false;
    if(!(get_document()->get_is_new())) //if it's not new.
    {
      //New instance:
      pApp = dynamic_cast<App_WithDoc*>(new_instance());
      pApp->init(); //It's shown too.
      bUsingNewInstance = true;
    }
    else
    {
      pApp = this; //Replace the default new document in this instance.
    }

    //Open it.
    pApp->m_pDocument->set_file_uri(file_uri);
    bool bTest = pApp->m_pDocument->load();

    bool bOpenFailed = false;
    if(!bTest) //if open failed.
      bOpenFailed = true;
    else
    {
      //if open succeeded then let the App respond:
      bool test = pApp->on_document_load();
      if(!test)
        bOpenFailed = true; //The application didn't like something about the just-loaded document.
      else
      {
        pApp->update_window_title();
        set_document_modified(false); //disables menu and toolbar Save items.

        //Update document history list:
        //(Getting the file URI again, in case it has changed while being opened, 
        //for instance if it was a template file that was saved as a real file.)
        if(pApp->m_pDocument)
          document_history_add(pApp->m_pDocument->get_file_uri());

        return true; //success.
      }
    }

    if(bOpenFailed)
    {
      ui_warning(gettext("Open failed."), gettext("The document could not be opened."));

      if(bUsingNewInstance)
      {
        //Remove new instance:
        pApp->get_document()->set_modified(false); //avoid 'do you want to save?' dialog.
        pApp->on_menu_file_close();
      }
      else
      {
        //re-initialize document.
        delete pApp->m_pDocument;
        pApp->m_pDocument = 0;
        pApp->init_create_document();
      }

      return false; //failed.
    }
      
  } //if already open.

  return false; //failed.
}

void App_WithDoc::on_menu_file_open()
{
  //Display File Open dialog and respond to choice:

  //Bring document window to front, to make it clear which document is being changed:
  ui_bring_to_front();

  //Ask user to choose file to open:
  Glib::ustring file_uri = ui_file_select_open();
  if(!file_uri.empty())
    open_document(file_uri);
}

//This is for calling directly, use the other override for the signal handler:
void App_WithDoc::offer_saveas()
{
  on_menu_file_saveas();
}

bool App_WithDoc::file_exists(const Glib::ustring& uri)
{
  //Check whether file exists already:
  {
    // Try to examine the input file.
    Glib::RefPtr<Gio::File> file = Gio::File::create_for_uri(uri);

#ifdef GLIBMM_EXCEPTIONS_ENABLED
    try
    {
      return file->query_exists();
    }
    catch(const Gio::Error& /* ex */)
    {
      return false; //Something went wrong. It does not exist.
    }
#else
      std::auto_ptr<Gio::Error> error;
      retrun file->query_exists(error);
#endif
  }
}

void App_WithDoc::on_menu_file_saveas()
{
  //Display File Save dialog and respond to choice:

  //Bring document window to front, to make it clear which document is being saved:
  //This doesn't work: TODO.
  ui_bring_to_front();

  //Show the save dialog:  
  const Glib::ustring& file_uriOld = m_pDocument->get_file_uri();

  Glib::ustring file_uri = ui_file_select_save(file_uriOld); //Also asks for overwrite confirmation.
  if(!file_uri.empty())
  {
    //Enforce the file extension:
    file_uri = m_pDocument->get_file_uri_with_extension(file_uri);

    bool bUseThisFileUri = true;

    //Save to this filepath:
    if(bUseThisFileUri)
    {
      m_pDocument->set_file_uri(file_uri, true); //true = enforce file extension
      bool bTest  = m_pDocument->save();

      if(!bTest)
      {
        ui_warning(gettext("Save failed."), gettext("There was an error while saving the file. Your changes have not been saved."));
      }
      else
      {
        //Disable Save and SaveAs menu items:
        after_successful_save();
      }

      update_window_title();


      //Close if this save was a result of a File|Close or File|Exit:.
      //if(bTest && m_bCloseAfterSave) //Don't close if the save failed.
      //{
      //  on_menu_file_close(); //This could be the second time, but now there are no unsaved changes.
      //}
    }
    else
    {
      //Let the user choose a different file path,
      //because he decided not to overwrite the 1st one.
      offer_saveas(); //recursive.
    }
  }
  else
  {
    cancel_close_or_exit();
  }
}

void App_WithDoc::on_menu_file_save()
{
  if(m_pDocument)
  {
    //If there is already a filepath, then save to that location:
    if(!(m_pDocument->get_file_uri().empty()))
    {
      bool bTest = m_pDocument->save();

      if(bTest)
      {
        //Disable Save and SaveAs menu items:
        after_successful_save();

        //Close the document if this save was in response to a 'Do you want to save before closing?':
        //if(m_bCloseAfterSave) // || m_bExiting
        //  close_mark_or_destroy();
      }
      else
      {
        //The save failed. Tell the user and don't do anything else:
        ui_warning(gettext("Save failed."), gettext("There was an error while saving the file. Your changes have not been saved."));

        cancel_close_or_exit();
      }
    }
    else
    {
      //If there is no filepath, then ask for one and save to that location:
      offer_saveas();
    }
  }

  if(!m_bCloseAfterSave) //Don't try to do anything after closing - this instance would not exist anymore.
  {
    update_window_title();
  }

}

void App_WithDoc::init()
{  
  init_create_document();

  //Call base method:
  App::init();

  on_document_load(); //Show default empty document in the View.

  set_document_modified(false); //Disable Save menu item.
}

void App_WithDoc::init_create_document()
{
  //Overrides should call this base method at the end.

  if(!m_pDocument)
  {
    m_pDocument = new Document();
  }

  m_pDocument->set_is_new(true); //Can't be modified if it's just been created.
  
  m_pDocument->signal_modified().connect(sigc::mem_fun(*this, &App_WithDoc::on_document_modified));

  update_window_title();
}

Document* App_WithDoc::get_document()
{
  return m_pDocument;
}

const Document* App_WithDoc::get_document() const
{
  return m_pDocument;
}

void App_WithDoc::offer_to_save_changes()
{
  if(m_pDocument)
  {
    if(m_pDocument->get_modified())
    {
      set_operation_cancelled(false); //Initialize it again. It might be set later in this method by cancel_close_or_exit().
      
      //The document has unsaved changes,
      //so ask the user whether the document should be saved:
      enumSaveChanges buttonClicked = ui_offer_to_save_changes();

      //Respond to button that was clicked:
      switch(buttonClicked)
      {
        case(SAVECHANGES_Save):
        {
          on_menu_file_save(); //If File|Exit is in progress, this could cancel it.
          break;
        }
        
        case(SAVECHANGES_Discard):
        {
          //Close if this save offer was a result of a FileClose (It probably always is):
          //close_mark_or_destroy();
          //Do nothing - the caller will probably hide() the window to have it deleted.
          break;
        }
        
        case(SAVECHANGES_Cancel): //Cancel.
        {
          cancel_close_or_exit();
          break;
        }
        
        default:
        {
          break;
        }
      }   
    }
  }
}

void App_WithDoc::close_mark_or_destroy()
{
  ui_hide();
}

void App_WithDoc::cancel_close_or_exit()
{
  set_operation_cancelled();
  m_bCloseAfterSave = false;
  m_AppInstanceManager.cancel_close_all();

  //exit_destroy_marked_instances(); //Clean up after an exit.
}

bool App_WithDoc::on_document_load()
{
  //Show document contents:
  if(m_pDocument)
  {
    Bakery::ViewBase* pView = m_pDocument->get_view();
    if(pView)
      pView->load_from_document();

    //Set document as unmodified (this could have been wrongly set during the load):
    set_document_modified(false);

    return true;
  }
  else
    return false; //I can't think of any reason why this would happen.
  
  //If you are not using Views, then override this to fill your various windows with stuff according to the contents of the document.
}

void App_WithDoc::update_window_title()
{

}

void App_WithDoc::on_document_modified(bool modified)
{
  //Change the displayed 'modified' status.
  //This method could be overridden to e.g. enable a Save icon or enable the Save menu item.
  //TODO: enable/disable the Save menu item.

  ui_show_modification_status();

  //Change title bar:
  update_window_title();
}

void App_WithDoc::set_document_modified(bool bModified /* = true */)
{
  m_pDocument->set_modified(bModified);

  //Enable/Disable Save menu item and toolbar item:
  ui_show_modification_status();
}

void App_WithDoc::on_menu_edit_copy()
{
  Bakery::ViewBase* pView = m_pDocument->get_view();
  if(pView)
    pView->clipboard_copy();
}

void App_WithDoc::on_menu_edit_paste()
{
  Bakery::ViewBase* pView = m_pDocument->get_view();
  if(pView)
    pView->clipboard_paste();
}

void App_WithDoc::on_menu_edit_clear()
{
  Bakery::ViewBase* pView = m_pDocument->get_view();
  if(pView)
    pView->clipboard_clear();
}

void App_WithDoc::after_successful_save()
{
  set_document_modified(false); //enables/disables menu and toolbar widgets.
    
  //Update document history list:
  document_history_add(m_pDocument->get_file_uri());     
}

Glib::ustring App_WithDoc::get_conf_fullkey(const Glib::ustring& key)
{
  return "/apps/" + m_strAppName + "/" + key;
}


void App_WithDoc::document_history_add(const Glib::ustring& file_uri)
{
  //Override this.
}

void App_WithDoc::document_history_remove(const Glib::ustring& file_uri)
{
  //Override this.
}

} //namespace

