//
// Author: Matthew William Coan
// Email: matthewcoan1976@hotmail.com
// Date: 10/02/2022 10:10 AM
//
// External storage disk stack data structure
// C++ template class.
//
#ifndef _DISKSTACK_H
#define _DISKSTACK_H

#include <iostream>
#include <fstream>
#include <string>
#ifdef OS_WINDOWS
#include <io.h>
#else
#include <unistd.h>
#endif
#include "bfstream.h"

using namespace std;

#ifdef OS_WINDOWS
inline
void ftruncate(int fd, long sz) 
{
   _chsize(fd,sz);
}
#endif

namespace ds {

enum ds_open_mode {
   CREATE_FILE,
   REOPEN_FILE
};

template< class T > 
class diskstack {
   FILE* fptr;
   bfstream fil;
   string fname;
   off_t sz;
   void open(ios_base::openmode mode) {
      fil.open(fname, mode);
      if(fil.fail())
         throw "unable to open stack file...";
   }
public:
   diskstack(const string & fnam, ds_open_mode mode = REOPEN_FILE) :fname(fnam) {
     if(mode == CREATE_FILE) {
        open(ios::binary | ios::in | ios::out | ios::trunc | ios::ate); 
        sz = 0L;
        fil << sz;
     }
     else {
        open(ios::binary | ios::in | ios::out); 
        fil >> sz;
     }
     fptr = fopen(fnam.c_str(), "w+");
   }
   ~diskstack() {
      fclose(fptr);
      fil.close();
   }
   size_t size() const { return sz; }
   void push(const T & data) {
      off_t off;
      fil.seekg(0L, ios::beg);
      sz++;
      fil << sz;
      fil.seekg(0L, ios::end);
      off = fil.tellg();
      fil << data;
      fil << off; 
      fil.flush();
   }
   T pop() {
      T data;
      if(sz <= 0) throw "stack empty...";
      sz--;
      fil.seekg(0L, ios::beg);
      fil << sz;
      fil.seekg(0L, ios::end);
      off_t off = fil.tellg();
      fil.seekg(off-sizeof(off_t), ios::beg);
      fil >> off;
      fil.seekg(off, ios::beg);
      fil >> data;
      fseek(fptr, off, SEEK_SET);
      ftruncate(fileno(fptr), off);
      return data;
   }
   T top() {
      T data;
      if(sz <= 0) throw "stack empty...";
      fil.seekg(0L, ios::end);
      off_t off = fil.tellg();
      fil.seekg(off-sizeof(off_t), ios::beg);
      fil >> off;
      fil.seekg(off, ios::beg);
      fil >> data;
      return data;
   }
   void clear() {
      sz = 0;
      fil.seekg(0L, ios::beg);
      fil << sz;
      off_t off = fil.tellg();
      fseek(fptr, off, SEEK_SET);
      ftruncate(fileno(fptr), off);
   }
   template< class Procedure >
   void foreach(Procedure proc) {
      fil.seekg(0L, ios::beg);
      off_t off;
      fil >> off;
      T data;
      do {
         fil >> data;
         proc(data);
         fil >> off;
      }
      while(!fil.fail());
      fil.clear();
   }
};

}

#endif /* _DISKSTACK_H */
