/* Copyright (c) 2005 by Devin Carraway
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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
 */

#ifndef EASYMMAP_H
#define EASYMMAP_H

#include <string>
#include <stdexcept>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>

template <class T>
class AutoMmap {
    public:
        class iterator {
            public:
                iterator(T v)
                :v_(v)
                {}
                inline T operator*() const { return v_; }
            private:
                const T v_;
        };

        AutoMmap(int fd, size_t winsize)
        : map_(NULL),
    	 fd_(fd),
    	 windowpos_(0), windowposT_(0),
    	 mapcount_(1)
        {
    	    windowsize_ = winsize * getpagesize();
    	    windowsizeT_ = windowsize_ / sizeof(T);
			windowendT_ = windowposT_ + windowsizeT_;

            void *tmp;
            tmp = mmap(NULL,
                       windowsize_,
                       PROT_READ | PROT_WRITE,
                       MAP_SHARED,
                       fd_,
                       windowpos_);
            if (tmp == MAP_FAILED) {
                std::string e("mmap: ");
                e += strerror(errno);
                throw std::runtime_error(e);
            }
            windowsizeT_ = windowsize_ / sizeof(T);
            map_ = tmp;
        }

        ~AutoMmap()
        {
            if (map_ != NULL) munmap(map_, windowsize_);
        }

        inline T operator*() {
            return at_(0);
        }

        inline T operator[](size_t offset) {
            if (offset < windowposT_ || offset >= windowendT_) {
				slide_(offset);
            }
	        return *((T *)(map_) + (offset - windowposT_));
        }

        inline iterator operator+(size_t offset) {
            if (offset < windowposT_ || offset >= windowendT_) {
				slide_(offset);
            }
            return iterator(*((T *)(map_) + (offset - windowposT_)));
        }

	int mapcount() { int r = mapcount_; mapcount_ = 0; return r; }


    private:

        void slide_(size_t elem) {
//            fprintf(stderr, "offset=%d pos=%d size=%d\n",
//                        elem, windowpos_, windowsize_);
            void *tmp;
            if (map_ != NULL && munmap(map_, windowsize_) < 0) {
                std::string e("munmap: ");
                e += strerror(errno);
                throw std::runtime_error(e);
            }
            windowposT_ = elem - (elem % windowsizeT_);
			windowendT_ = windowposT_ + windowsizeT_;
            windowpos_ = windowposT_ * sizeof(T);
            tmp = mmap(NULL,
                       windowsize_,
                       PROT_READ | PROT_WRITE,
                       MAP_SHARED,
                       fd_,
                       windowpos_);
            if (tmp == MAP_FAILED) {
                std::string e("mmap: ");
                e += strerror(errno);
                throw std::runtime_error(e);
            }
			mapcount_++;
            map_ = tmp;
//                fprintf(stderr, "moved window to %d\n", windowpos_);
        }

        void *map_;
        int fd_;
        size_t windowpos_, windowposT_, windowsize_, windowsizeT_;
		size_t windowendT_;
		int mapcount_;
};

#endif


