/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */
 
#include "qpid/management/Manageable.h"
#include "qpid/management/ManagementObject.h"
#include "qpid/framing/FieldTable.h"
#include "qpid/framing/Buffer.h"
#include "qpid/sys/Time.h"
#include "qpid/sys/Thread.h"
#include "qpid/log/Statement.h"
#include <boost/lexical_cast.hpp>

#include <stdlib.h>

using namespace std;
using namespace qpid;
using namespace qpid::management;

void AgentAttachment::setBanks(uint32_t broker, uint32_t bank)
{
    first =
        ((uint64_t) (broker & 0x000fffff)) << 28 |
        ((uint64_t) (bank   & 0x0fffffff));
}

// Deprecated
ObjectId::ObjectId(uint8_t flags, uint16_t seq, uint32_t broker, uint64_t object)
    : agent(0), agentEpoch(seq)
{
    first =
        ((uint64_t) (flags  &       0x0f)) << 60 |
        ((uint64_t) (seq    &     0x0fff)) << 48 |
      ((uint64_t) (broker & 0x000fffff)) << 28;
    second = object;
}


ObjectId::ObjectId(uint8_t flags, uint16_t seq, uint32_t broker)
    : agent(0), second(0), agentEpoch(seq)
{
    first =
        ((uint64_t) (flags  &       0x0f)) << 60 |
        ((uint64_t) (seq    &     0x0fff)) << 48 |
        ((uint64_t) (broker & 0x000fffff)) << 28;
}

ObjectId::ObjectId(AgentAttachment* _agent, uint8_t flags, uint16_t seq)
    : agent(_agent), second(0), agentEpoch(seq)
{

    first =
        ((uint64_t) (flags &   0x0f)) << 60 |
        ((uint64_t) (seq   & 0x0fff)) << 48;
}


ObjectId::ObjectId(istream& in) : agent(0)
{
    string text;
    in >> text;
    fromString(text);
}

ObjectId::ObjectId(const string& text) : agent(0)
{
    fromString(text);
}

void ObjectId::fromString(const string& text)
{
#define FIELDS 5
#if defined (_WIN32) && !defined (atoll)
#  define atoll(X) _atoi64(X)
#endif

    // format:
    // V1: <flags>-<sequence>-<broker-bank>-<agent-bank>-<uint64-app-id>
    // V2: Not used

    string copy(text.c_str());
    char* cText;
    char* field[FIELDS];
    bool  atFieldStart = true;
    int   idx = 0;

    cText = const_cast<char*>(copy.c_str());
    for (char* cursor = cText; *cursor; cursor++) {
        if (atFieldStart) {
            if (idx >= FIELDS)
                throw Exception("Invalid ObjectId format");
            field[idx++] = cursor;
            atFieldStart = false;
        } else {
            if (*cursor == '-') {
                *cursor = '\0';
                atFieldStart = true;
            }
        }
    }

    if (idx != FIELDS)
        throw Exception("Invalid ObjectId format");

    agentEpoch = atoll(field[1]);

    first = (atoll(field[0]) << 60) +
        (atoll(field[1]) << 48) +
        (atoll(field[2]) << 28);

    agentName = string(field[3]);
    second = atoll(field[4]);
}


bool ObjectId::operator==(const ObjectId &other) const
{
    return v2Key == other.v2Key;
}

bool ObjectId::operator<(const ObjectId &other) const
{
    return v2Key < other.v2Key;
}

bool ObjectId::equalV1(const ObjectId &other) const
{
    uint64_t otherFirst = agent == 0 ? other.first : other.first & 0xffff000000000000LL;
    return first == otherFirst && second == other.second;
}

// encode as V1-format binary
void ObjectId::encode(string& buffer) const
{
    const uint32_t len = 16;
    char _data[len];
    qpid::framing::Buffer body(_data, len);

    if (agent == 0)
        body.putLongLong(first);
    else
        body.putLongLong(first | agent->first);
    body.putLongLong(second);

    body.reset();
    body.getRawData(buffer, len);
}

// decode as V1-format binary
void ObjectId::decode(const string& buffer)
{
    const uint32_t len = 16;
    char _data[len];
    qpid::framing::Buffer body(_data, len);

    body.checkAvailable(buffer.length());
    body.putRawData(buffer);
    body.reset();
    first  = body.getLongLong();
    second = body.getLongLong();
    v2Key = boost::lexical_cast<string>(second);
}

// generate the V2 key from the index fields defined
// in the schema.
void ObjectId::setV2Key(const ManagementObject& object)
{
    stringstream oname;
    oname << object.getPackageName() << ":" << object.getClassName() << ":" << object.getKey();
    v2Key = oname.str();
}


// encode as V2-format map
void ObjectId::mapEncode(types::Variant::Map& map) const
{
    map["_object_name"] = v2Key;
    if (!agentName.empty())
        map["_agent_name"] = agentName;
    if (agentEpoch)
        map["_agent_epoch"] = agentEpoch;
}

// decode as v2-format map
void ObjectId::mapDecode(const types::Variant::Map& map)
{
    types::Variant::Map::const_iterator i;

    if ((i = map.find("_object_name")) != map.end())
        v2Key = i->second.asString();
    else
        throw Exception("Required _object_name field missing.");

    if ((i = map.find("_agent_name")) != map.end())
        agentName = i->second.asString();

    if ((i = map.find("_agent_epoch")) != map.end())
        agentEpoch = i->second.asInt64();
}


ObjectId::operator types::Variant::Map() const
{
    types::Variant::Map m;
    mapEncode(m);
    return m;
}



namespace qpid {
namespace management {

ostream& operator<<(ostream& out, const ObjectId& i)
{
    uint64_t virtFirst = i.first;
    if (i.agent)
        virtFirst |= i.agent->getFirst();

    out << ((virtFirst & 0xF000000000000000LL) >> 60) <<
        "-" << ((virtFirst & 0x0FFF000000000000LL) >> 48) <<
        "-" << ((virtFirst & 0x0000FFFFF0000000LL) >> 28) <<
        "-" << i.agentName <<
        "-" << i.second <<
        "(" << i.v2Key << ")";
    return out;
}

}}

ManagementObject::ManagementObject(Manageable* _core) :
createTime(qpid::sys::Duration(sys::EPOCH, sys::now())),
        destroyTime(0), updateTime(createTime), configChanged(true),
        instChanged(true), deleted(false),
        coreObject(_core), flags(0), forcePublish(false) {}

void ManagementObject::setUpdateTime()
{
    updateTime = sys::Duration(sys::EPOCH, sys::now());
}

void ManagementObject::resourceDestroy()
{
    QPID_LOG(trace, "Management object marked deleted: " << getObjectId().getV2Key());
    destroyTime = sys::Duration(sys::EPOCH, sys::now());
    deleted     = true;
}

int ManagementObject::maxThreads = 1;
int ManagementObject::nextThreadIndex = 0;

void ManagementObject::writeTimestamps (string& buf) const
{
    char _data[4000];
    qpid::framing::Buffer body(_data, 4000);

    body.putShortString (getPackageName ());
    body.putShortString (getClassName ());
    body.putBin128      (getMd5Sum ());
    body.putLongLong    (updateTime);
    body.putLongLong    (createTime);
    body.putLongLong    (destroyTime);

    uint32_t len = body.getPosition();
    body.reset();
    body.getRawData(buf, len);

    string oid;
    objectId.encode(oid);
    buf += oid;
}

void ManagementObject::readTimestamps (const string& buf)
{
    char _data[4000];
    qpid::framing::Buffer body(_data, 4000);
    string unused;
    uint8_t unusedUuid[16];

    body.checkAvailable(buf.length());
    body.putRawData(buf);
    body.reset();

    body.getShortString(unused);
    body.getShortString(unused);
    body.getBin128(unusedUuid);
    updateTime = body.getLongLong();
    createTime = body.getLongLong();
    destroyTime = body.getLongLong();
}

uint32_t ManagementObject::writeTimestampsSize() const
{
    return 1 + getPackageName().length() +  // str8
      1 + getClassName().length() +       // str8
      16 +                                // bin128
      8 +                                 // uint64
      8 +                                 // uint64
      8 +                                 // uint64
      objectId.encodedSize();             // objectId
}


void ManagementObject::writeTimestamps (types::Variant::Map& map) const
{
    // types::Variant::Map oid, sid;

    // sid["_package_name"] = getPackageName();
    // sid["_class_name"] = getClassName();
    // sid["_hash"] = qpid::types::Uuid(getMd5Sum());
    // map["_schema_id"] = sid;

    // objectId.mapEncode(oid);
    // map["_object_id"] = oid;

    map["_update_ts"] = updateTime;
    map["_create_ts"] = createTime;
    map["_delete_ts"] = destroyTime;
}

void ManagementObject::readTimestamps (const types::Variant::Map& map)
{
    types::Variant::Map::const_iterator i;

    if ((i = map.find("_update_ts")) != map.end())
        updateTime = i->second.asUint64();
    if ((i = map.find("_create_ts")) != map.end())
        createTime = i->second.asUint64();
    if ((i = map.find("_delete_ts")) != map.end())
        destroyTime = i->second.asUint64();
}


void ManagementObject::setReference(ObjectId) {}

int ManagementObject::getThreadIndex() {
    static QPID_TSS int thisIndex = -1;
    if (thisIndex == -1) {
        Mutex::ScopedLock mutex(accessLock);
        thisIndex = nextThreadIndex;
        if (nextThreadIndex < maxThreads - 1)
            nextThreadIndex++;
    }
    return thisIndex;
}


// void ManagementObject::mapEncode(types::Variant::Map& map,
//                                  bool includeProperties,
//                                  bool includeStatistics)
// {
//     types::Variant::Map values;

//     writeTimestamps(map);

//     mapEncodeValues(values, includeProperties, includeStatistics);
//     map["_values"] = values;
// }

// void ManagementObject::mapDecode(const types::Variant::Map& map)
// {
//     types::Variant::Map::const_iterator i;

//     readTimestamps(map);

//     if ((i = map.find("_values")) != map.end())
//         mapDecodeValues(i->second.asMap());
// }
