//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Ion Gaztanaga 2009-2012. Distributed under the Boost
// Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org/libs/interprocess for documentation.
//
//////////////////////////////////////////////////////////////////////////////

#ifndef BOOST_INTERPROCESS_PORTABLE_INTERMODULE_SINGLETON_HPP
#define BOOST_INTERPROCESS_PORTABLE_INTERMODULE_SINGLETON_HPP

#if defined(_MSC_VER)
#pragma once
#endif

#include <boost/interprocess/detail/config_begin.hpp>
#include <boost/interprocess/detail/workaround.hpp>

#include <boost/interprocess/detail/managed_global_memory.hpp>
#include <boost/interprocess/detail/intermodule_singleton_common.hpp>
#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/detail/atomic.hpp>
#include <boost/interprocess/detail/os_thread_functions.hpp>
#include <boost/interprocess/detail/shared_dir_helpers.hpp>
#include <boost/interprocess/detail/os_file_functions.hpp>
#include <boost/interprocess/detail/file_locking_helpers.hpp>
#include <boost/assert.hpp>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <string>

namespace boost{
namespace interprocess{
namespace ipcdetail{

typedef basic_managed_global_memory<shared_memory_object, true>    managed_global_memory;

namespace intermodule_singleton_helpers {

static void create_tmp_subdir_and_get_pid_based_filepath
   (const char *subdir_name, const char *file_prefix, OS_process_id_t pid, std::string &s, bool creation_time = false)
{
   //Let's create a lock file for each process gmem that will mark if
   //the process is alive or not
   create_shared_dir_and_clean_old(s);
   s += "/";
   s += subdir_name;
   if(!open_or_create_directory(s.c_str())){
      error_info err = system_error_code();
      throw interprocess_exception(err);
   }
   s += "/";
   s += file_prefix;
   if(creation_time){
      std::string sstamp;
      get_pid_creation_time_str(sstamp);
      s += sstamp;
   }
   else{
      pid_str_t pid_str;
      get_pid_str(pid_str, pid);
      s += pid_str;
   }
}

static bool check_if_filename_complies_with_pid
   (const char *filename, const char *prefix, OS_process_id_t pid, std::string &file_suffix, bool creation_time = false)
{
   //Check if filename complies with lock file name pattern
   std::string fname(filename);
   std::string fprefix(prefix);
   if(fname.size() <= fprefix.size()){
      return false;
   }
   fname.resize(fprefix.size());
   if(fname != fprefix){
      return false;
   }

   //If not our lock file, delete it if we can lock it
   fname = filename;
   fname.erase(0, fprefix.size());
   pid_str_t pid_str;
   get_pid_str(pid_str, pid);
   file_suffix = pid_str;
   if(creation_time){
      std::size_t p = fname.find('_');
      if (p == std::string::npos){
         return false;
      }
      std::string save_suffix(fname);
      fname.erase(p);
      fname.swap(file_suffix);
      bool ret = (file_suffix == fname);
      file_suffix.swap(save_suffix);
      return ret;
   }
   else{
      fname.swap(file_suffix);
      return (file_suffix == fname);
   }
}

template<>
struct thread_safe_global_map_dependant<managed_global_memory>
{
   private:
   static const int GMemMarkToBeRemoved = -1;
   static const int GMemNotPresent      = -2;

   static const char *get_lock_file_subdir_name()
   {  return "gmem";  }

   static const char *get_lock_file_base_name()
   {  return "lck";  }

   static void create_and_get_singleton_lock_file_path(std::string &s)
   {
      create_tmp_subdir_and_get_pid_based_filepath
         (get_lock_file_subdir_name(), get_lock_file_base_name(), get_current_process_id(), s, true);
   }

   struct gmem_erase_func
   {
      gmem_erase_func(const char *shm_name, const char *singleton_lock_file_path, managed_global_memory & shm)
         :shm_name_(shm_name), singleton_lock_file_path_(singleton_lock_file_path), shm_(shm)
      {}

      void operator()()
      {
         locking_file_serial_id *pserial_id = shm_.find<locking_file_serial_id>("lock_file_fd").first;
         if(pserial_id){
            pserial_id->fd = GMemMarkToBeRemoved;
         }
         delete_file(singleton_lock_file_path_);
         shared_memory_object::remove(shm_name_);
      }

      const char * const shm_name_;
      const char * const singleton_lock_file_path_;
      managed_global_memory & shm_;
   };

   //This function applies shared memory erasure logic based on the passed lock file.
   static void apply_gmem_erase_logic(const char *filepath, const char *filename)
   {
      int fd = GMemMarkToBeRemoved;
      try{
         std::string str;
         //If the filename is current process lock file, then avoid it
         if(check_if_filename_complies_with_pid
            (filename, get_lock_file_base_name(), get_current_process_id(), str, true)){
            return;
         }
         //Open and lock the other process' lock file
         fd = try_open_and_lock_file(filepath);
         if(fd < 0){
            return;
         }
         //If done, then the process is dead so take global shared memory name
         //(the name is based on the lock file name) and try to apply erasure logic
         str.insert(0, get_map_base_name());
         try{
            managed_global_memory shm(open_only, str.c_str());
            gmem_erase_func func(str.c_str(), filepath, shm);
            shm.try_atomic_func(func);
         }
         catch(interprocess_exception &e){
            //If shared memory is not found erase the lock file
            if(e.get_error_code() == not_found_error){
               delete_file(filepath);
            }
         }
      }
      catch(...){

      }
      if(fd >= 0){
         close_lock_file(fd);
      }
   }

   public:

   static bool remove_old_gmem()
   {
      std::string refcstrRootDirectory;
      get_shared_dir(refcstrRootDirectory);
      refcstrRootDirectory += "/";
      refcstrRootDirectory += get_lock_file_subdir_name();
      return for_each_file_in_dir(refcstrRootDirectory.c_str(), apply_gmem_erase_logic);
   }

   struct lock_file_logic
   {
      lock_file_logic(managed_global_memory &shm)
         : mshm(shm)
      {  shm.atomic_func(*this); }

      void operator()(void)
      {
         retry_with_new_map = false;

         //First find the file locking descriptor id
         locking_file_serial_id *pserial_id =
            mshm.find<locking_file_serial_id>("lock_file_fd").first;

         int fd;
         //If not found schedule a creation
         if(!pserial_id){
            fd = GMemNotPresent;
         }
         //Else get it
         else{
            fd = pserial_id->fd;
         }
         //If we need to create a new one, do it
         if(fd == GMemNotPresent){
            std::string lck_str;
            //Create a unique current pid based lock file path
            create_and_get_singleton_lock_file_path(lck_str);
            //Open or create and lock file
            int fd_lockfile = open_or_create_and_lock_file(lck_str.c_str());
            //If failed, write a bad file descriptor to notify other modules that
            //something was wrong and unlink shared memory. Mark the function object
            //to tell caller to retry with another shared memory
            if(fd_lockfile < 0){
               this->register_lock_file(GMemMarkToBeRemoved);
               std::string s;
               get_map_name(s);
               shared_memory_object::remove(s.c_str());
               retry_with_new_map = true;
            }
            //If successful, register the file descriptor
            else{
               this->register_lock_file(fd_lockfile);
            }
         }
         //If the fd was invalid (maybe a previous try failed) notify caller that
         //should retry creation logic, since this shm might have been already
         //unlinked since the shm was removed
         else if (fd == GMemMarkToBeRemoved){
            retry_with_new_map = true;
         }
         //If the stored fd is not valid (a open fd, a normal file with the
         //expected size, or does not have the same file id number,
         //then it's an old shm from an old process with the same pid.
         //If that's the case, mark it as invalid
         else if(!is_valid_fd(fd) ||
               !is_normal_file(fd) ||
               0 != get_size(fd) ||
               !compare_file_serial(fd, *pserial_id)){
            pserial_id->fd = GMemMarkToBeRemoved;
            std::string s;
            get_map_name(s);
            shared_memory_object::remove(s.c_str());
            retry_with_new_map = true;
         }
         else{
            //If the lock file is ok, increment reference count of
            //attached modules to shared memory
            atomic_inc32(&pserial_id->modules_attached_to_gmem_count);
         }
      }

      bool retry() const { return retry_with_new_map; }

      private:
      locking_file_serial_id * register_lock_file(int fd)
      {
         locking_file_serial_id *pinfo = mshm.construct<locking_file_serial_id>("lock_file_fd")();
         fill_file_serial_id(fd, *pinfo);
         return pinfo;
      }

      managed_global_memory &mshm;
      bool retry_with_new_map;
   };

   static void construct_map(void *addr)
   {
      std::string s;
      intermodule_singleton_helpers::get_map_name(s);
      const char *MapName = s.c_str();
      const std::size_t MapSize = intermodule_singleton_helpers::get_map_size();;
      ::new (addr)managed_global_memory(open_or_create, MapName, MapSize);
   }

   struct unlink_map_logic
   {
      unlink_map_logic(managed_global_memory &mshm)
         : mshm_(mshm)
      {  mshm.atomic_func(*this);  }

      void operator()()
      {
         locking_file_serial_id *pserial_id =
            mshm_.find<locking_file_serial_id>
               ("lock_file_fd").first;
         BOOST_ASSERT(0 != pserial_id);
         if(1 == atomic_dec32(&pserial_id->modules_attached_to_gmem_count)){
            int fd = pserial_id->fd;
            if(fd > 0){
               pserial_id->fd = GMemMarkToBeRemoved;
               std::string s;
               create_and_get_singleton_lock_file_path(s);
               delete_file(s.c_str());
               close_lock_file(fd);
               intermodule_singleton_helpers::get_map_name(s);
               shared_memory_object::remove(s.c_str());
            }
         }
      }

      private:
      managed_global_memory &mshm_;
   };

   static ref_count_ptr *find(managed_global_memory &map, const char *name)
   {
      return map.find<ref_count_ptr>(name).first;
   }

   static ref_count_ptr *insert(managed_global_memory &map, const char *name, const ref_count_ptr &ref)
   {
      return map.construct<ref_count_ptr>(name)(ref);
   }

   static bool erase(managed_global_memory &map, const char *name)
   {
      return map.destroy<ref_count_ptr>(name);
   }

   template<class F>
   static void atomic_func(managed_global_memory &map, F &f)
   {
      map.atomic_func(f);
   }
};

}  //namespace intermodule_singleton_helpers {

template<typename C, bool LazyInit = true, bool Phoenix = false>
class portable_intermodule_singleton
   : public intermodule_singleton_impl<C, LazyInit, Phoenix, managed_global_memory>
{};

}  //namespace ipcdetail{
}  //namespace interprocess{
}  //namespace boost{

#include <boost/interprocess/detail/config_end.hpp>

#endif   //#ifndef BOOST_INTERPROCESS_PORTABLE_INTERMODULE_SINGLETON_HPP
