#include <string>
#include <stdexcept>
#include <iostream>
#include <cassert>
#include <thread>
#include <chrono>

#include <cstdlib>
#include <unistd.h>
#include <sys/file.h>
#include <sys/stat.h>

struct FileLock
  {
  std::string _path;

  FileLock(const std::string& p, bool acq = false)
    : _path(p)
    {
    if (acq)
      {
      bool r = acquire();
      if (!r) throw std::runtime_error("FileLock::acquire failed");
      }
    }

  ~FileLock()
    {
    bool r = release();
    if (!r) std::cerr << "FileLock::release failed";
    }
  
  const char* path() const {return _path.c_str();}
  
  // #############################################################################

#define CHECK_OK_(v, pred)                                \
  {                                                       \
  bool res = (pred);                                        \
  if (!res) std::cerr<< ("FileLock: (" #pred ") = false");   \
  v &= res;                                                 \
  }
  
  bool acquire()
    {
    bool ok = true;
    int r;
    
    // auto fp = std::fopen(_path.c_str(), "w");
    // if (!fp)
    //   return false;

    // int fd = ::fileno(fp);

    auto m = umask(0);
    int fd = ::open(_path.c_str(), O_RDWR | O_CREAT, 0666);
    umask(m);
    
    CHECK_OK_(ok, fd != -1);
    if (ok)
      {
      r = ::flock(fd, LOCK_EX | LOCK_NB);
      CHECK_OK_(ok, r != -1);
      if (ok)
        {                       // write pid
        pid_t pid = getpid();
        auto pstr = std::to_string(pid);
        auto wr = ::write(fd, pstr.c_str(), pstr.size());
        CHECK_OK_(ok, wr == pstr.size());
        }
      }
    
    // CHECK_OK_(ok,  std::fclose(fp) == 0);
    CHECK_OK_(ok, ::close(fd) == 0);
    
    return ok;
    }

  // #############################################################################
  
  bool release()
    {
    bool ok = true;
    
    // auto fp = std::fopen(_path.c_str(), "r");
    // if (!fp)
    //   return false;
    
    // int fd = ::fileno(fp);
    
    int fd = ::open(_path.c_str(), O_RDONLY);
    CHECK_OK_(ok, fd != -1);
    if (ok)
      {
      int r = ::flock(fd, LOCK_UN | LOCK_NB);
      CHECK_OK_(ok, r != -1);
      }
      
    // pid should == getpid()
      
    // CHECK_OK_(ok, std::fclose(fp) == 0);
    CHECK_OK_(ok, ::close(fd) == 0);

    // necessary?
    CHECK_OK_(ok, std::remove(_path.c_str()) != -1);
    
    return ok;
    }

#undef CHECK_OK_
      
  };

int main(int ac, const char* av[])
{
  assert((ac >= 2) && "usage: prog file_name");

  FileLock fl(av[1], true);

  while (1)
  {
    std::this_thread::sleep_for(std::chrono::seconds{1});
    std::cout << "waiting... \n";
  }
}
