/*
 * (C) Copyright 1996-2012 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 <unistd.h>

#include "eckit/config/Resource.h"
#ifdef HAVE_AIO_H
#include "eckit/io/AIOHandle.h"
#endif
#include "eckit/io/FileHandle.h"
#include "eckit/log/Log.h"
#include "eckit/thread/ThreadSingleton.h"
#include "eckit/utils/Literals.h"
#include "eckit/utils/StringTools.h"

#include "odc/LibOdc.h"
#include "odc/ODBAPISettings.h"

using namespace eckit;
using namespace eckit::literals;
using namespace std;

template class eckit::ThreadSingleton<odc::ODBAPISettings>;
static ThreadSingleton<odc::ODBAPISettings> instance_;

bool odc::ODBAPISettings::debug = false;

void odc::ODBAPISettings::setHome(const char* argv0) {
    const char* env(getenv("odc_HOME"));
    if (env) {
        home_ = env;
        Log::info() << "odc_HOME set to " << home_ << endl;
    }
    else {
        string full;
        if (argv0[0] == '/') {
            char* absoluteArgv0;
            // The resolved_path == NULL feature, not standardized in POSIX.1-2001, but standardized in POSIX.1-2008
            if (0 == (absoluteArgv0 = ::realpath(argv0, 0)))
                throw eckit::FailedSystemCall(string("realpath ") + argv0);
            full = string(absoluteArgv0);
            ::free(absoluteArgv0);
        }
        else if (argv0[0] == '.' && argv0[1] == '/') {
            constexpr size_t bufferLen = 8_KiB;
            char buffer[bufferLen];
            full = string(::getcwd(buffer, bufferLen)) + string(argv0 + 1);
        }
        else {
            vector<string> ps(StringTools::split(":", getenv("PATH")));
            for (size_t i(0); i < ps.size(); ++i) {
                // TODO: perhaps we should also check if the file is readable, executable, etc...
                if (PathName(ps[i] + "/" + argv0).exists()) {
                    full = ps[i] + "/" + argv0;
                    if (ps[i][0] != '/') {
                        constexpr size_t bufferLen = 8_KiB;
                        char buffer[bufferLen];
                        full = string(::getcwd(buffer, bufferLen)) + full;
                    }
                    break;
                }
            }
        }
        vector<string> ps(StringTools::split("/", full));
        LOG_DEBUG_LIB(odc::LibOdc) << "ODBAPISettings::setHome: argv0: " << ps << endl;
        ASSERT("odb executable should be in a bin directory" && ps.size() >= 2 && ps[ps.size() - 2] == "bin");
        ps.pop_back();  // odb
        ps.pop_back();  // bin
        home_ = "/" + StringTools::join("/", ps);
        Log::info() << "odc_HOME inferred as " << home_ << endl;
    }
}

string odc::ODBAPISettings::fileInHome(const string& fileName) {
    ASSERT(fileName[0] == '~');
    ASSERT(fileName[1] == '/');
    return home_ + fileName.substr(1);
}

void odc::ODBAPISettings::treatIntegersAsDoubles(bool flag) {
    integersAsDoubles_ = flag;
}

bool odc::ODBAPISettings::integersAsDoubles() const {
    return integersAsDoubles_;
}

void debugMeNow() {
    Log::info() << "Debug me now" << endl;
    odc::ODBAPISettings::debug = true;
}

namespace odc {

ODBAPISettings& ODBAPISettings::instance() {
    // ASSERT( &instance_.instance() != 0 );
    return instance_.instance();
}

ODBAPISettings::ODBAPISettings() :
    headerBufferSize_(Resource<long>("$ODC_HEADER_BUFFER_SIZE;-headerBufferSize;headerBufferSize", 4 * 1024 * 1024)),
    setvbufferSize_(Resource<long>("$ODC_SETVBUFFER_SIZE;-setvbufferSize;setvbufferSize", 8 * 1024 * 1024)),
    useAIO_(Resource<bool>("$ODC_USE_AIO", false)),
    integersAsDoubles_(Resource<bool>("$ODC_INTEGERS_AS_DOUBLES", true)) {}

size_t ODBAPISettings::headerBufferSize() {
    return headerBufferSize_;
}
void ODBAPISettings::headerBufferSize(size_t n) {
    headerBufferSize_ = n;
}

size_t ODBAPISettings::setvbufferSize() {
    return setvbufferSize_;
}
void ODBAPISettings::setvbufferSize(size_t n) {
    setvbufferSize_ = n;
}

void ODBAPISettings::createDirectories(const PathName& path) {
    vector<string> parts(StringTools::split("/", path));
    if (parts.size() < 2)
        return;

    parts.pop_back();
    PathName directory((string(path)[0] == '/' ? "/" : "") + StringTools::join("/", parts));

    LOG_DEBUG_LIB(LibOdc) << "Making sure diretory " << directory << " exists" << endl;

    directory.mkdir();
}

DataHandle* ODBAPISettings::writeToFile(const PathName& fn, const Length& length, bool openDataHandle) {
    // ODB-122 Create subdirectories before creating a file
    createDirectories(fn);

#ifdef HAVE_AIO_H
    DataHandle* h(useAIO_ ? static_cast<DataHandle*>(new AIOHandle(fn)) : static_cast<DataHandle*>(new FileHandle(fn)));
#else
    DataHandle* h(static_cast<DataHandle*>(new FileHandle(fn)));
#endif
    if (openDataHandle)
        h->openForWrite(length);
    return h;
}

DataHandle* ODBAPISettings::appendToFile(const PathName& fn, const Length& length, bool openDataHandle) {
#ifdef HAVE_AIO_H
    DataHandle* h(useAIO_ ? static_cast<DataHandle*>(new AIOHandle(fn)) : static_cast<DataHandle*>(new FileHandle(fn)));
#else
    DataHandle* h(static_cast<DataHandle*>(new FileHandle(fn)));
#endif
    if (openDataHandle)
        h->openForAppend(length);
    return h;
}

}  // namespace odc
