/*
 * (C) Copyright 1996-2013 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 "metkit/pointdb/GribFieldInfo.h"
#include "metkit/codes/api/CodesAPI.h"
#include "metkit/pointdb/GribDataSource.h"

#include <bitset>

using namespace eckit;


extern "C" {
unsigned long grib_decode_unsigned_long(const unsigned char* p, long* offset, int bits);
double grib_power(long s, long n);
}

namespace metkit {
namespace pointdb {

static Mutex mutex;

#define MISSING 9999

static int bits[65536] = {
#include "metkit/pointdb/bits.h"
};

static uint64_t masks[64] = {
#include "metkit/pointdb/masks.h"
};

static inline int count_bits(unsigned long long n) {
    return bits[n & 0xffffu] + bits[(n >> 16) & 0xffffu] + bits[(n >> 32) & 0xffffu] + bits[(n >> 48) & 0xffffu];
}

GribFieldInfo::GribFieldInfo() :
    referenceValue_(0),
    binaryScaleFactor_(0),
    decimalScaleFactor_(0),
    bitsPerValue_(0),
    offsetBeforeData_(0),
    offsetBeforeBitmap_(0),
    numberOfValues_(0),
    numberOfDataPoints_(0),
    sphericalHarmonics_(0) {}

void GribFieldInfo::update(const codes::CodesHandle& h) {
    binaryScaleFactor_  = h.getLong("binaryScaleFactor");
    decimalScaleFactor_ = h.getLong("decimalScaleFactor");
    bitsPerValue_       = h.getLong("bitsPerValue");
    referenceValue_     = h.getDouble("referenceValue");
    offsetBeforeData_   = h.getLong("offsetBeforeData");
    numberOfDataPoints_ = h.getLong("numberOfDataPoints");
    numberOfValues_     = h.getLong("numberOfValues");
    sphericalHarmonics_ = h.getLong("sphericalHarmonics");

    if (h.getLong("bitmapPresent"))
        offsetBeforeBitmap_ = h.getLong("offsetBeforeBitmap");
    else
        offsetBeforeBitmap_ = 0;

    if (!sphericalHarmonics_)
        geographyHash_ = h.getString("md5GridSection");
}

void GribFieldInfo::print(std::ostream& s) const {
    s << "GribFieldInfo[";

    s << "binaryScaleFactor=" << binaryScaleFactor_;
    s << ",decimalScaleFactor=" << decimalScaleFactor_;
    s << ",bitsPerValue=" << bitsPerValue_;
    s << ",referenceValue=" << referenceValue_;
    s << ",offsetBeforeData=" << offsetBeforeData_;
    s << ",numberOfDataPoints=" << numberOfDataPoints_;
    s << ",numberOfValues=" << numberOfValues_;
    s << ",offsetBeforeBitmap=" << offsetBeforeBitmap_;
    s << ",sphericalHarmonics=" << sphericalHarmonics_;
    s << ",geographyHash=" << geographyHash_;

    s << "]";
}


double GribFieldInfo::interpolate(GribDataSource& f, double& lat, double& lon) const {
    NOTIMP;
}

double GribFieldInfo::value(const GribDataSource& f, size_t index) const {
    unsigned char buf[8];

    if (bitsPerValue_ == 0)
        return referenceValue_;

    ASSERT(!sphericalHarmonics_);

    if (offsetBeforeBitmap_) {
        ASSERT(index < numberOfDataPoints_);

        Log::info() << "offsetBeforeBitmap_ " << offsetBeforeBitmap_ << ",index " << index << std::endl;


        Offset offset(offsetBeforeBitmap_);
        ASSERT(f.seek(offset) == offset);
        size_t count = 0;

        uint64_t n;
        ASSERT(sizeof(n) == 8);

        size_t skip = index / (sizeof(n) * 8);

        Log::info() << "Read " << skip << " words" << std::endl;
        for (size_t i = 0; i < skip; ++i) {
            ASSERT(f.read(&n, sizeof(n)) == sizeof(n));
            count += count_bits(n);
        }
        Log::info() << "Count " << count << std::endl;

        size_t pos = index % (sizeof(n) * 8);

        Log::info() << "Read last bits, " << pos << std::endl;
        ASSERT(f.read(&n, sizeof(n)) == sizeof(n));

        Log::info() << "Read last bits, " << pos << ", before mask " << std::bitset<64>(n) << std::endl;


        n &= masks[pos];

        Log::info() << "Read last bits, " << pos << ", after mask " << std::bitset<64>(n) << ", bits=" << count_bits(n)
                    << std::endl;


        count += count_bits(n);
        Log::info() << "Count " << count << std::endl;

        n = (n >> (64 - pos)) & 1;

        if (!n) {
            return MISSING;
        }

        ASSERT(count);
        index = count - 1;
    }

    Log::info() << "index " << index << ", numberOfValues " << numberOfValues_ << std::endl;
    ASSERT(index < numberOfValues_);

    {
        Offset offset = off_t(offsetBeforeData_) + off_t(index * bitsPerValue_ / 8);
        ASSERT(f.seek(offset) == offset);


        long len = (bitsPerValue_ + 7) / 8;
        ASSERT(f.read(buf, len) == len);
    }

    long bitp       = (index * bitsPerValue_) % 8;
    unsigned long p = grib_decode_unsigned_long(buf, &bitp, bitsPerValue_);

    // TODO: store the precomputed values
    double s = grib_power(binaryScaleFactor_, 2);
    double d = grib_power(-decimalScaleFactor_, 10);

    double v = (double)(((p * s) + referenceValue_) * d);

    return v;
}

}  // namespace pointdb
}  // namespace metkit
