/*
 * (C) Copyright 1996- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include "KDMapped.h"

#include "eckit/filesystem/PathName.h"
#include "eckit/os/Stat.h"


#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>

#include "eckit/memory/MMap.h"


namespace eckit {


KDMapped::KDMapped(const PathName& path, size_t itemCount, size_t itemSize, size_t metadataSize):
    path_(path),
    header_(itemCount, itemSize, metadataSize),
    count_(0),
    size_(0),
    base_(0),
    root_(0),
    addr_(0),
    fd_(-1)
{

    int oflags = O_RDWR|O_CREAT;
    int mflags = PROT_READ|PROT_WRITE;

    if(itemCount == 0) {
        oflags = O_RDWR;
        //mflags = PROT_READ;
    }

    size_t base;

    SYSCALL(fd_ = ::open(path.localPath(),oflags,  0777));
    if(itemCount == 0)
    {

        Stat::Struct s;
        SYSCALL(Stat::stat(path.localPath(),&s));
        size_ = s.st_size;


        int n;
        SYSCALL(n = ::read(fd_,&header_, sizeof(header_)));
        ASSERT(n == sizeof(header_));
        lseek(fd_, 0, SEEK_SET);

        root_ = 1;

        ASSERT(header_.headerSize_  == sizeof(header_));

        base = ((header_.headerSize_
                + header_.metadataSize_
                + header_.itemSize_ - 1) / header_.itemSize_) * header_.itemSize_;

    }
    else
    {
        base = ((header_.headerSize_
                + header_.metadataSize_
                + header_.itemSize_ - 1) / header_.itemSize_) * header_.itemSize_;

        char c = 0;

        count_ = 1;
        size_  = base + (itemCount + 1) * itemSize;

        lseek(fd_, 0, SEEK_SET);
        SYSCALL(::write(fd_,&header_,sizeof(header_)));

        lseek(fd_, size_ - 1, SEEK_SET);
        SYSCALL(::write(fd_,&c,1));
    }

    addr_ = MMap::mmap(0, size_, mflags, MAP_SHARED, fd_, 0 );
    if(addr_ == MAP_FAILED) {
        Log::error() << "open(" << path << ')'
                     << Log::syserr << std::endl;
        throw FailedSystemCall("mmap");
    }

    base_ = reinterpret_cast<char*>(addr_) + base;
}

KDMapped::~KDMapped()
{
    if(addr_)
        SYSCALL(munmap(addr_, size_));
    if(fd_ >= 0)
        SYSCALL(close(fd_));
}

// Warning, takes ownership of maps
KDMapped::KDMapped(const KDMapped& other):
    path_(other.path_),
    header_(other.header_),
    count_(other.count_),
    size_(other.size_),
    base_(other.base_),
    root_(other.root_),
    addr_(other.addr_),
    fd_(other.fd_)
{
    const_cast<KDMapped&>(other).addr_ = 0;
    const_cast<KDMapped&>(other).fd_ = -1;
}

// Warning, takes ownership of maps
KDMapped& KDMapped::operator=(const KDMapped& other)
{
    path_ = other.path_;
    count_= other.count_;
    size_= other.size_;
    addr_= other.addr_;
    fd_ = other.fd_;
    root_ = other.root_;

    header_ = other.header_;
    base_ = other.base_;

    const_cast<KDMapped&>(other).addr_ = 0;
    const_cast<KDMapped&>(other).fd_ = -1;

    return *this;
}

void KDMapped::setMetadata(const void *addr, size_t size) {
    ASSERT(size == header_.metadataSize_);
    char *start = static_cast<char*>(addr_);
    ::memcpy(start + sizeof(header_), addr, size);
}

void KDMapped::getMetadata(void *addr, size_t size) {
    ASSERT(size == header_.metadataSize_);
    char *start = static_cast<char*>(addr_);
    ::memcpy(addr, start + sizeof(header_), size);
}


} //namespace
