//  BMP
//  Copyright (C) 2005-2008 BMP development.
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License Version 2
//  as published by the Free Software Foundation.
//
//  This program 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 this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//  --
//
//  The BMPx project hereby grants permission for non-GPL compatible GStreamer
//  plugins to be used and distributed together with GStreamer and BMPx. This
//  permission is above and beyond the permissions granted by the GPL license
//  BMPx is covered by.

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif //HAVE_CONFIG_H

#include <glibmm.h>
#include <glibmm/i18n.h>

#include <utility>

#include <boost/algorithm/string.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/format.hpp>

#include "paths.hh"

#include "util-file.hh"
#include "util.hh"
#include "vfs.hh"

using namespace Glib;

namespace Bmp
{
    namespace VFS
    {
      namespace
      {
        inline bool
        is_module (std::string const& basename)
        {
          return Util::str_has_suffix_nocase (basename, "." G_MODULE_SUFFIX);
        }

        enum
        {
          LIB_BASENAME,
          LIB_PLUGNAME,
          LIB_SUFFIX
        };

      } // anonymous

      bool
      VFS::load_plugins_transport (std::string const& path, std::string const& type)
      {
        using namespace boost::algorithm;

        std::string basename (path_get_basename (path));
        std::string pathname (path_get_dirname  (path));

        if (!is_module (basename)) return false;

        std::vector<std::string> subs;
        split (subs, basename, is_any_of ("_."));
        std::string dir   = build_filename (BMP_VFS_PLUGIN_DIR, type);
        std::string name  = type + "_" + subs[LIB_PLUGNAME];
        std::string mpath = Module::build_path (dir, name);

        Module module (mpath, ModuleFlags (0));

        if (module)
        {
          module.make_resident ();

          PluginTransportBase* (*plugin_create) ();
          if (!module.get_symbol ("plugin_create", (void*&)plugin_create))
          {
            g_critical ("Unable to acquire plugin_create symbol from library %s", mpath.c_str ());
          }
          else
          {
            PluginTransportBase* plugin = plugin_create ();
            transport_plugins.insert (std::make_pair (subs[LIB_PLUGNAME], plugin));
          }
        }
        else
        {
          g_critical ("Failed to load Transport plugin '%s': %s", mpath.c_str (), module.get_last_error().c_str() );
        }

        return false;
      }

      bool
      VFS::load_plugins_container (std::string const& path, std::string const& type)
      {
        using namespace boost::algorithm;

        std::string basename (path_get_basename (path));
        std::string pathname (path_get_dirname  (path));

        if (!is_module (basename)) return false;

        std::vector<std::string> subs;
        split (subs, basename, is_any_of ("_."));
        std::string dir   = build_filename (BMP_VFS_PLUGIN_DIR, type);
        std::string name  = type + "_" + subs[LIB_PLUGNAME];
        std::string mpath = Module::build_path (dir, name);

        Module module (mpath, ModuleFlags (0));

        if (module)
        {
          module.make_resident ();

          PluginContainerBase* (*plugin_create) ();
          if (!module.get_symbol ("plugin_create", (void*&)plugin_create))
          {
            g_critical ("Unable to acquire plugin_create symbol from library %s", mpath.c_str ());
          }
          else
          {
            PluginContainerBase* plugin = plugin_create ();
            container_plugins.insert (std::make_pair (subs[LIB_PLUGNAME], plugin));
            ExportData exportdata = plugin->get_export_data ();
          }
        }
        else
        {
          g_critical ("Failed to load Container plugin '%s': %s", mpath.c_str (), module.get_last_error().c_str() );
        }

        return false;
      }

      VFS::VFS ()
      {
          Util::dir_for_each_entry (BMP_TRANSPORT_PLUGIN_DIR,
                                    sigc::bind (sigc::mem_fun (*this, &VFS::load_plugins_transport), "transport"));

          Util::dir_for_each_entry (BMP_CONTAINER_PLUGIN_DIR,
                                    sigc::bind (sigc::mem_fun (*this, &VFS::load_plugins_container), "container"));
      }

      VFS::~VFS ()
      {
      }

      void
      VFS::get_containers (ExportDataList & list)
      {
        for (ContainerPlugins::iterator i = container_plugins.begin(); i != container_plugins.end(); ++i)
        {
          ExportData const& exportdata (i->second->get_export_data ());
          if (!exportdata.extension.empty())
          {
            list.push_back (exportdata);
          }
        }
      }

      bool
      VFS::has_container (ustring const& uri)
      {
        for (ContainerPlugins::iterator i = container_plugins.begin(); i != container_plugins.end(); ++i)
        {
          PluginContainerBase *container = i->second;
          if (container->can_process (uri))
            return true;
        }
        return false;
      }

      void
      VFS::read (Handle & handle, ProcessingFlags flags)
      {
        ustring uri (handle.get_uri ());

        if (flags != TRANSPORT)
          throw InvalidFlagsError();

        TransportPlugins::iterator t;
        if (flags & TRANSPORT)
        {
          for(  t = transport_plugins.begin();
              ! t->second->can_process(uri) && t != transport_plugins.end();
              ++t);

          if( t == transport_plugins.end() )
            throw ProcessingError ((boost::format (_("No transport found for URI %s")) % uri).str());
        }

        try{
          if (flags & TRANSPORT) t->second->handle_read (handle);
          }
        catch (ProcessingError & cxe)
          {
            g_log (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "VFS: Error during processing: %s", cxe.what());
            throw;
          }
      }

      void
      VFS::read (Handle & handle, VUri & list, ProcessingFlags flags)
      {
        ustring uri (handle.get_uri ());

        if (flags == 0)
          throw InvalidFlagsError();

        ContainerPlugins::iterator c;
        if (flags & CONTAINER)
        {
          for (c = container_plugins.begin(); c != container_plugins.end(); ++c)
          {
            try{
              if (c->second->can_process (uri))
                break;
              }
            catch (...) {}
          }

          if (c == container_plugins.end())
          {
            // if there is no container, it also prolly doensn't need a transport (at leas tnot with this variant of read())
            return;
          }
        }

        TransportPlugins::iterator t;
        if (flags & TRANSPORT)
        {
          for (t = transport_plugins.begin(); t != transport_plugins.end(); ++t)
          {
            try{
              if (t->second->can_process(uri))
                break;
              }
            catch (...) {}
          }

          if (t == transport_plugins.end())
            throw ProcessingError ((boost::format (_("No transport found for URI %s")) % uri).str());
        }

        try{
          if (flags & TRANSPORT) t->second->handle_read (handle);
          if (flags & CONTAINER) c->second->handle_read (handle, list);
          }
        catch (ProcessingError & cxe)
          {
            g_log (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "VFS: Error during processing: %s", cxe.what());
            throw;
          }
      }

      void
      VFS::write (TrackV const& list, ustring const& uri, std::string const& name)
      {
        Handle handle (uri);

        ContainerPlugins::iterator c = container_plugins.find (name);
        if (c == container_plugins.end())
          throw ProcessingError ((boost::format (_("No container found for format %s")) % name).str());
           
        TransportPlugins::iterator t = transport_plugins.begin ();
        for ( ; t != transport_plugins.end (); ++t)
        {
          try{
            if (t->second->can_process (uri))
              break;
            }
          catch (...) {}
        }

        if (t == transport_plugins.end())
          throw ProcessingError ((boost::format (_("No transport found for URI %s")) % uri).str());

        try{
          c->second->handle_write (handle, list);
          t->second->handle_write (handle);
          }
        catch (ProcessingError & cxe)
          {
            g_log (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "VFS: Error during processing: %s", cxe.what());
            throw;
          }
      }

  } // namespace VFS
} // namespace Bmp
