#include "auth.h"
#include "bitfields.h"
#include "defines.h"
#include "procs.h"
#include "shared_memory.h"
#include "stream.h"
#include "timing.h"
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <iostream>
#include <sys/mman.h>
#include <sys/sem.h>
#include <unistd.h>

#if defined(__CYGWIN__) || defined(_WIN32)
#include <accctrl.h>
#include <aclapi.h>
#include <windows.h>
#endif

namespace IPC{

#if defined(__CYGWIN__) || defined(_WIN32)
  static std::map<std::string, sharedPage> preservedPages;
  void preservePage(std::string p){preservedPages[p].init(p, 0, false, false);}
  void releasePage(std::string p){preservedPages.erase(p);}
#endif

  ///\brief Empty semaphore constructor, clears all values
  semaphore::semaphore(){
#if defined(__CYGWIN__) || defined(_WIN32)
    mySem = 0;
#else
    mySem = SEM_FAILED;
#endif
    isLocked = 0;
  }

  ///\brief Constructs a named semaphore
  ///\param name The name of the semaphore
  ///\param oflag The flags with which to open the semaphore
  ///\param mode The mode in which to create the semaphore, if O_CREAT is given in oflag, ignored
  /// otherwise \param value The initial value of the semaphore if O_CREAT is given in oflag,
  /// ignored otherwise
  semaphore::semaphore(const char *name, int oflag, mode_t mode, unsigned int value, bool noWait){
#if defined(__CYGWIN__) || defined(_WIN32)
    mySem = 0;
#else
    mySem = SEM_FAILED;
#endif
    isLocked = 0;
    open(name, oflag, mode, value, noWait);
  }

  ///\brief The deconstructor
  semaphore::~semaphore(){close();}

  ///\brief Returns whether we have a valid semaphore
  semaphore::operator bool() const{
#if defined(__CYGWIN__) || defined(_WIN32)
    return mySem != 0;
#else
    return mySem != SEM_FAILED;
#endif
  }

  ///\brief Opens a semaphore
  ///
  /// Closes currently opened semaphore if needed
  ///\param name The name of the semaphore
  ///\param oflag The flags with which to open the semaphore
  ///\param mode The mode in which to create the semaphore, if O_CREAT is given in oflag, ignored
  /// otherwise \param value The initial value of the semaphore if O_CREAT is given in oflag,
  /// ignored otherwise
  void semaphore::open(const char *name, int oflag, mode_t mode, unsigned int value, bool noWait){
    close();
    int timer = 0;
    while (!(*this) && timer++ < 10){
#if defined(__CYGWIN__) || defined(_WIN32)
      std::string semaName = "Global\\";
      semaName += (name + 1);
      if (oflag & O_CREAT){
        if (oflag & O_EXCL){
          // attempt opening, if succes, close handle and return false;
          HANDLE tmpSem = OpenMutex(SYNCHRONIZE, false, semaName.c_str());
          if (tmpSem){
            CloseHandle(tmpSem);
            mySem = 0;
            break;
          }
        }
        SECURITY_ATTRIBUTES security = getSecurityAttributes();
        mySem = CreateMutex(&security, true, semaName.c_str());
        if (value){ReleaseMutex(mySem);}
      }else{
        mySem = OpenMutex(SYNCHRONIZE, false, semaName.c_str());
      }
      if (!(*this)){
        if (GetLastError() == ERROR_FILE_NOT_FOUND && !noWait){// Error code 2
          Util::wait(500);
        }else{
          break;
        }
      }
#else
      if (oflag & O_CREAT){
        mySem = sem_open(name, oflag, mode, value);
#if defined(__APPLE__)
        if (!(*this)){
          if (sem_unlink(name) == 0){
            INFO_MSG("Deleted in-use semaphore: %s", name);
            mySem = sem_open(name, oflag, mode, value);
          }
        }
#endif
      }else{
        mySem = sem_open(name, oflag);
      }
      if (!(*this)){
        if (errno == ENOENT && !noWait){
          Util::wait(500);
        }else{
          break;
        }
      }
#endif
    }
    if (*this){myName = (char *)name;}
  }

  ///\brief Returns the current value of the semaphore
  int semaphore::getVal() const{
#if defined(__CYGWIN__) || defined(_WIN32)
    LONG res;
    ReleaseSemaphore(mySem, 0,
                     &res); // not really release.... just checking to see if I can get the value this way
#else
    int res;
    sem_getvalue(mySem, &res);
#endif
    return res;
  }

  ///\brief Posts to the semaphore, increases its value by one
  void semaphore::post(){
    if (!*this || !isLocked){
      FAIL_MSG("Attempted to unlock a non-locked semaphore: '%s'!", myName.c_str());
#if DEBUG >= DLVL_DEVEL
      BACKTRACE;
#endif
      return;
    }
#if defined(__CYGWIN__) || defined(_WIN32)
    ReleaseMutex(mySem);
#else
    sem_post(mySem);
#endif
    --isLocked;
#if DEBUG >= DLVL_DEVEL
    if (!isLocked){
      uint64_t micros = Util::getMicros(lockTime);
      if (micros > 10000){
        INFO_MSG("Semaphore %s was locked for %.3f ms", myName.c_str(), (double)micros / 1000.0);
      }
    }
#endif
  }

  ///\brief Posts to the semaphore, increases its value by count
  void semaphore::post(size_t count){
    for (size_t i = 0; i < count; ++i){post();}
  }

  ///\brief Waits for the semaphore, decreases its value by one
  void semaphore::wait(){
    if (*this){
#if DEBUG >= DLVL_DEVEL
      uint64_t preLockTime = Util::getMicros();
#endif
#if defined(__CYGWIN__) || defined(_WIN32)
      WaitForSingleObject(mySem, INFINITE);
#else
      int tmp;
      do{tmp = sem_wait(mySem);}while (tmp == -1 && errno == EINTR);
#endif
#if DEBUG >= DLVL_DEVEL
      lockTime = Util::getMicros();
      if (lockTime - preLockTime > 50000){
        INFO_MSG("Semaphore %s took %.3f ms to lock", myName.c_str(), (double)(lockTime-preLockTime) / 1000.0);
      }
#endif
      ++isLocked;
    }
  }

  ///\brief Waits for the semaphore, decreases its value by count
  void semaphore::wait(size_t count){
    for (size_t i = 0; i < count; ++i){wait();}
  }


  ///\brief Tries to wait for the semaphore, returns true if successful, false otherwise
  bool semaphore::tryWait(){
    if (!(*this)){return false;}
    int result;
#if defined(__CYGWIN__) || defined(_WIN32)
    result = WaitForSingleObject(mySem, 0); // wait at most 1ms
    if (result == 0x80){
      WARN_MSG("Consistency error caught on semaphore %s", myName.c_str());
      result = 0;
    }
#else
    do{result = sem_trywait(mySem);}while (result == -1 && errno == EINTR);
#endif
    isLocked += (result == 0 ? 1 : 0);
    if (isLocked == 1){lockTime = Util::getMicros();}
    return isLocked;
  }

  ///\brief Tries to wait for the semaphore for a given amount of ms, returns true if successful, false
  /// otherwise
  bool semaphore::tryWait(uint64_t ms){
    if (!(*this)){return false;}
    int result;
#if defined(__CYGWIN__) || defined(_WIN32)
    result = WaitForSingleObject(mySem, ms); // wait at most 1s
    if (result == 0x80){
      WARN_MSG("Consistency error caught on semaphore %s", myName.c_str());
      result = 0;
    }
#elif defined(__APPLE__)
    /// \todo (roxlu) test tryWaitOneSecond, shared_memory.cpp
    uint64_t now = Util::getMicros();
    uint64_t timeout = now + (ms * 1000);
    while (now < timeout){
      if (0 == sem_trywait(mySem)){
        isLocked = true;
        return true;
      }
      usleep(100e3);
      now = Util::getMicros();
    }
    return false;
#else
    struct timespec wt;
    wt.tv_sec = ms / 1000;
    wt.tv_nsec = ms % 1000;
    result = sem_timedwait(mySem, &wt);
#endif
    return (isLocked = (result == 0));
  }

  ///\brief Tries to wait for the semaphore for a single second, returns true if successful, false
  /// otherwise
  bool semaphore::tryWaitOneSecond(){
    if (!(*this)){return false;}
    int result;
#if defined(__CYGWIN__) || defined(_WIN32)
    result = WaitForSingleObject(mySem, 1000); // wait at most 1s
    if (result == 0x80){
      WARN_MSG("Consistency error caught on semaphore %s", myName.c_str());
      result = 0;
    }
#elif defined(__APPLE__)
    /// \todo (roxlu) test tryWaitOneSecond, shared_memory.cpp
    uint64_t now = Util::getMicros();
    uint64_t timeout = now + 1e6;
    result = 1;
    while (result && now < timeout){
      result = sem_trywait(mySem);
      usleep(100e3);
      now = Util::getMicros();
    }
#else
    struct timespec wt;
    wt.tv_sec = 1;
    wt.tv_nsec = 0;
    result = sem_timedwait(mySem, &wt);
#endif
    isLocked += (result == 0 ? 1 : 0);
    if (isLocked == 1){lockTime = Util::getMicros();}
    return isLocked;
  }

  ///\brief Closes the currently opened semaphore
  void semaphore::close(){
    if (*this){
      while (isLocked){post();}
#if defined(__CYGWIN__) || defined(_WIN32)
      CloseHandle(mySem);
      mySem = 0;
#else
      sem_close(mySem);
      mySem = SEM_FAILED;
#endif
    }
    myName.clear();
  }

  /// Closes the semaphore, without unlocking it first.
  /// Intended to be called from forked child processes, to drop the reference to the semaphore.
  void semaphore::abandon(){
    if (*this){
#if defined(__CYGWIN__) || defined(_WIN32)
      CloseHandle(mySem);
      mySem = 0;
#else
      sem_close(mySem);
      mySem = SEM_FAILED;
#endif
    }
    myName.clear();
  }

  /// Unlinks the previously opened semaphore, closing it (if open) in the process.
  void semaphore::unlink(){
#if defined(__CYGWIN__) || defined(_WIN32)
    while (isLocked){post();}
#endif
#if !defined(__CYGWIN__) && !defined(_WIN32)
    if (myName.size()){sem_unlink(myName.c_str());}
#endif
    if (*this){
#if defined(__CYGWIN__) || defined(_WIN32)
      CloseHandle(mySem);
      mySem = 0;
#else
      sem_close(mySem);
      mySem = SEM_FAILED;
#endif
    }
    myName.clear();
  }

#if defined(__CYGWIN__) || defined(_WIN32)
  SECURITY_ATTRIBUTES semaphore::getSecurityAttributes(){
    ///\todo We really should clean this up sometime probably
    /// We currently have everything static, because the result basically depends on everything
    static SECURITY_ATTRIBUTES result;
    static bool resultValid = false;
    static SECURITY_DESCRIPTOR securityDescriptor;
    if (resultValid){return result;}

    InitializeSecurityDescriptor(&securityDescriptor, SECURITY_DESCRIPTOR_REVISION);
    if (!SetSecurityDescriptorDacl(&securityDescriptor, TRUE, NULL, FALSE)){
      FAIL_MSG("Failed to set pSecurityDescriptor: %u", GetLastError());
      return result;
    }

    result.nLength = sizeof(SECURITY_ATTRIBUTES);
    result.lpSecurityDescriptor = &securityDescriptor;
    result.bInheritHandle = FALSE;

    resultValid = true;
    return result;
  }
#endif

  /// brief Creates a shared page
  ///\param name_ The name of the page to be created
  ///\param len_ The size to make the page
  ///\param master_ Whether to create or merely open the page
  ///\param autoBackoff When only opening the page, wait for it to appear or fail
  sharedPage::sharedPage(const std::string &name_, uint64_t len_, bool master_, bool autoBackoff){
    handle = 0;
    len = 0;
    master = false;
    mapped = 0;
    init(name_, len_, master_, autoBackoff);
  }

  ///\brief Creates a copy of a shared page
  ///\param rhs The page to copy
  sharedPage::sharedPage(const sharedPage &rhs){
    handle = 0;
    len = 0;
    master = false;
    mapped = 0;
    init(rhs.name, rhs.len, rhs.master);
  }

  ///\brief Default destructor
  sharedPage::~sharedPage(){close();}

#ifdef SHM_ENABLED

  /// Returns true if the open file still exists.
  /// \TODO Not implemented under Windows.
  bool sharedPage::exists(){
#if defined(__CYGWIN__) || defined(_WIN32)
    return true; // Not implemented under Windows...
#else
#ifdef SHM_ENABLED
    struct stat sb;
    if (fstat(handle, &sb)){return false;}
    return (sb.st_nlink > 0);
#else
    return true;
#endif
#endif
  }

  ///\brief Unmaps a shared page if allowed
  void sharedPage::unmap(){
    if (mapped){
#if defined(__CYGWIN__) || defined(_WIN32)
      // under Cygwin, the mapped location is shifted by 4 to contain the page size.
      UnmapViewOfFile(mapped - 4);
#else
      munmap(mapped, len);
#endif
      mapped = 0;
      len = 0;
    }
  }

  ///\brief Closes a shared page if allowed
  void sharedPage::close(){
    unmap();
    if (handle > 0){
      INSANE_MSG("Closing page %s in %s mode", name.c_str(), master ? "master" : "client");
#if defined(__CYGWIN__) || defined(_WIN32)
      CloseHandle(handle);
#else
      ::close(handle);
      if (master && name != ""){shm_unlink(name.c_str());}
#endif
      handle = 0;
    }
  }

  ///\brief Returns whether the shared page is valid or not
  sharedPage::operator bool() const{return mapped != 0;}

  ///\brief Assignment operator
  void sharedPage::operator=(sharedPage &rhs){
    init(rhs.name, rhs.len, rhs.master);
    /// \todo This is bad. The assignment operator changes the rhs value? What the hell?
    rhs.master = false; // Make sure the memory does not get unlinked
  }

  ///\brief Initialize a page, de-initialize before if needed
  ///\param name_ The name of the page to be created
  ///\param len_ The size to make the page
  ///\param master_ Whether to create or merely open the page
  ///\param autoBackoff When only opening the page, wait for it to appear or fail
  void sharedPage::init(const std::string &name_, uint64_t len_, bool master_, bool autoBackoff){
    close();
    name = name_;
    len = len_;
    master = master_;
    mapped = 0;
    if (name.size()){
      INSANE_MSG("Opening page %s in %s mode %s auto-backoff", name.c_str(),
                 master ? "master" : "client", autoBackoff ? "with" : "without");
#if defined(__CYGWIN__) || defined(_WIN32)
      if (master){
        // Under cygwin, all pages are 4 bytes longer than claimed.
        handle = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, len + 4, name.c_str());
      }else{
        int i = 0;
        do{
          if (i != 0){Util::wait(1000);}
          handle = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, name.c_str());
          i++;
        }while (i < 10 && !handle && autoBackoff);
      }
      if (!handle){
        MEDIUM_MSG("%s for page %s failed with error code %u",
                   (master ? "CreateFileMapping" : "OpenFileMapping"), name.c_str(), GetLastError());
        return;
      }
      mapped = (char *)MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, 0);
      if (!mapped){
        FAIL_MSG("MapViewOfFile for page %s failed with error code %u", name.c_str(), GetLastError());
        return;
      }
      // Under cygwin, the extra 4 bytes contain the real size of the page.
      if (master){
        Bit::htobl(mapped, len);
      }else{
        len = Bit::btohl(mapped);
      }
      // Now shift by those 4 bytes.
      mapped += 4;
#else
      handle = shm_open(name.c_str(), (master ? O_CREAT | O_EXCL : 0) | O_RDWR, ACCESSPERMS);
      if (handle == -1){
        if (master){
          if (len > 1){ERROR_MSG("Overwriting old page for %s", name.c_str());}
          handle = shm_open(name.c_str(), O_CREAT | O_RDWR, ACCESSPERMS);
        }else{
          int i = 0;
          while (i < 10 && handle == -1 && autoBackoff){
            i++;
            Util::wait(1000);
            handle = shm_open(name.c_str(), O_RDWR, ACCESSPERMS);
          }
        }
      }
      if (handle == -1){
        if (!master_ && autoBackoff){
          HIGH_MSG("shm_open for page %s failed: %s", name.c_str(), strerror(errno));
        }
        return;
      }
      if (handle > 0 && handle < 3){
        int tmpHandle = fcntl(handle, F_DUPFD, 3);
        if (tmpHandle >= 3){
          DONTEVEN_MSG("Remapped handle for page %s from %d to %d!", name.c_str(), handle, tmpHandle);
          ::close(handle);
          handle = tmpHandle;
        }
      }
      if (master){
        if (ftruncate(handle, len) < 0){
          FAIL_MSG("truncate to %" PRIu64 " for page %s failed: %s", len, name.c_str(), strerror(errno));
          return;
        }
      }else{
        struct stat buffStats;
        int xRes = fstat(handle, &buffStats);
        if (xRes < 0){return;}
        len = buffStats.st_size;
        if (!len){
          mapped = 0;
          return;
        }
      }
      mapped = (char *)mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, handle, 0);
      if (mapped == MAP_FAILED){
        FAIL_MSG("mmap for page %s failed: %s", name.c_str(), strerror(errno));
        mapped = 0;
        return;
      }
#endif
    }
  }

#endif

  /// brief Creates a shared file
  ///\param name_ The name of the file to be created
  ///\param len_ The size to make the file
  ///\param master_ Whether to create or merely open the file
  ///\param autoBackoff When only opening the file, wait for it to appear or fail
  sharedFile::sharedFile(const std::string &name_, uint64_t len_, bool master_, bool autoBackoff)
      : handle(0), name(name_), len(len_), master(master_), mapped(NULL){
    handle = 0;
    name = name_;
    len = len_;
    master = master_;
    mapped = 0;
    init(name_, len_, master_, autoBackoff);
  }

  ///\brief Creates a copy of a shared page
  ///\param rhs The page to copy
  sharedFile::sharedFile(const sharedFile &rhs){
    handle = 0;
    name = "";
    len = 0;
    master = false;
    mapped = 0;
    init(rhs.name, rhs.len, rhs.master);
  }

  ///\brief Returns whether the shared file is valid or not
  sharedFile::operator bool() const{return mapped != 0;}

  ///\brief Assignment operator
  void sharedFile::operator=(sharedFile &rhs){
    init(rhs.name, rhs.len, rhs.master);
    rhs.master = false; // Make sure the memory does not get unlinked
  }

  ///\brief Unmaps a shared file if allowed
  void sharedFile::unmap(){
    if (mapped && len){
      munmap(mapped, len);
      mapped = 0;
      len = 0;
    }
  }

  /// Unmaps, closes and unlinks (if master and name is set) the shared file.
  void sharedFile::close(){
    unmap();
    if (handle > 0){
      ::close(handle);
      if (master && name != ""){unlink(name.c_str());}
      handle = 0;
    }
  }

  /// Returns true if the open file still exists.
  bool sharedFile::exists(){
    struct stat sb;
    if (fstat(handle, &sb)){return false;}
    return (sb.st_nlink > 0);
  }

  ///\brief Initialize a page, de-initialize before if needed
  ///\param name_ The name of the page to be created
  ///\param len_ The size to make the page
  ///\param master_ Whether to create or merely open the page
  ///\param autoBackoff When only opening the page, wait for it to appear or fail
  void sharedFile::init(const std::string &name_, uint64_t len_, bool master_, bool autoBackoff){
    close();
    name = name_;
    len = len_;
    master = master_;
    mapped = 0;
    if (name.size()){
      /// \todo Use ACCESSPERMS instead of 0600?
      handle = open(std::string(Util::getTmpFolder() + name).c_str(),
                    (master ? O_CREAT | O_TRUNC | O_EXCL : 0) | O_RDWR, (mode_t)0600);
      if (handle == -1){
        if (master){
          HIGH_MSG("Overwriting old file for %s", name.c_str());
          handle = open(std::string(Util::getTmpFolder() + name).c_str(),
                        O_CREAT | O_TRUNC | O_RDWR, (mode_t)0600);
        }else{
          int i = 0;
          while (i < 10 && handle == -1 && autoBackoff){
            i++;
            Util::wait(1000);
            handle = open(std::string(Util::getTmpFolder() + name).c_str(), O_RDWR, (mode_t)0600);
          }
        }
      }
      if (handle == -1){
        HIGH_MSG("shf_open for page %s failed: %s", name.c_str(), strerror(errno));
        return;
      }
      if (master){
        if (ftruncate(handle, len) < 0){
          INFO_MSG("ftruncate to len for shf page %s failed: %s", name.c_str(), strerror(errno));
          return;
        }
      }else{
        struct stat buffStats;
        int xRes = fstat(handle, &buffStats);
        if (xRes < 0){return;}
        len = buffStats.st_size;
      }
      mapped = (char *)mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, handle, 0);
      if (mapped == MAP_FAILED){
        mapped = 0;
        return;
      }
    }
  }

  ///\brief Default destructor
  sharedFile::~sharedFile(){close();}

  ///\brief Creates a semaphore guard, locks the semaphore on call
  semGuard::semGuard(semaphore *thisSemaphore) : mySemaphore(thisSemaphore){mySemaphore->wait();}

  ///\brief Destructs a semaphore guard, unlocks the semaphore on call
  semGuard::~semGuard(){mySemaphore->post();}
}// namespace IPC