/**
 * @file lllandmark.cpp
 * @brief Landmark asset class
 *
 * $LicenseInfo:firstyear=2002&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"
#include "lllandmark.h"

#include <errno.h>

#include "message.h"
#include "llregionhandle.h"

std::pair<LLUUID, U64> LLLandmark::mLocalRegion;
LLLandmark::region_map_t LLLandmark::mRegions;
LLLandmark::region_callback_map_t LLLandmark::sRegionCallbackMap;

LLLandmark::LLLandmark() :
    mGlobalPositionKnown(false)
{
}

LLLandmark::LLLandmark(const LLVector3d& pos) :
    mGlobalPositionKnown(true),
    mGlobalPos( pos )
{
}

bool LLLandmark::getGlobalPos(LLVector3d& pos)
{
    if(mGlobalPositionKnown)
    {
        pos = mGlobalPos;
    }
    else if(mRegionID.notNull())
    {
        F32 g_x = -1.0;
        F32 g_y = -1.0;
        if(mRegionID == mLocalRegion.first)
        {
            from_region_handle(mLocalRegion.second, &g_x, &g_y);
        }
        else
        {
            region_map_t::iterator it = mRegions.find(mRegionID);
            if(it != mRegions.end())
            {
                from_region_handle((*it).second.mRegionHandle, &g_x, &g_y);
            }
        }
        if((g_x > 0.f) && (g_y > 0.f))
        {
            pos.mdV[0] = g_x + mRegionPos.mV[0];
            pos.mdV[1] = g_y + mRegionPos.mV[1];
            pos.mdV[2] = mRegionPos.mV[2];
            setGlobalPos(pos);
        }
    }
    return mGlobalPositionKnown;
}

void LLLandmark::setGlobalPos(const LLVector3d& pos)
{
    mGlobalPos = pos;
    mGlobalPositionKnown = true;
}

bool LLLandmark::getRegionID(LLUUID& region_id)
{
    if(mRegionID.notNull())
    {
        region_id = mRegionID;
        return true;
    }
    return false;
}

LLVector3 LLLandmark::getRegionPos() const
{
    return mRegionPos;
}


// static
LLLandmark* LLLandmark::constructFromString(const char *buffer, const S32 buffer_size)
{
    S32 chars_read = 0;
    S32 chars_read_total = 0;
    S32 count = 0;
    U32 version = 0;

    bool bad_block = false;
    LLLandmark* result = NULL;

    // read version
    count = sscanf( buffer, "Landmark version %u\n%n", &version, &chars_read );
    chars_read_total += chars_read;

    if (count != 1
        || chars_read_total >= buffer_size)
    {
        bad_block = true;
    }

    if (!bad_block)
    {
        switch (version)
        {
            case 1:
            {
                LLVector3d pos;
                // read position
                count = sscanf(buffer + chars_read_total, "position %lf %lf %lf\n%n", pos.mdV + VX, pos.mdV + VY, pos.mdV + VZ, &chars_read);
                if (count != 3)
                {
                    bad_block = true;
                }
                else
                {
                    LL_DEBUGS("Landmark") << "Landmark read: " << pos << LL_ENDL;
                    result = new LLLandmark(pos);
                }
                break;
            }
            case 2:
            {
                // *NOTE: Changing the buffer size will require changing the
                // scanf call below.
                char region_id_str[MAX_STRING];
                LLVector3 pos;
                LLUUID region_id;
                count = sscanf( buffer + chars_read_total,
                                "region_id %254s\n%n",
                                region_id_str,
                                &chars_read);
                chars_read_total += chars_read;

                if (count != 1
                    || chars_read_total >= buffer_size
                    || !LLUUID::validate(region_id_str))
                {
                    bad_block = true;
                }

                if (!bad_block)
                {
                    region_id.set(region_id_str);
                    if (region_id.isNull())
                    {
                        bad_block = true;
                    }
                }

                if (!bad_block)
                {
                    count = sscanf(buffer + chars_read_total, "local_pos %f %f %f\n%n", pos.mV + VX, pos.mV + VY, pos.mV + VZ, &chars_read);
                    if (count != 3)
                    {
                        bad_block = true;
                    }
                    else
                    {
                        result = new LLLandmark;
                        result->mRegionID = region_id;
                        result->mRegionPos = pos;
                    }
                }
                break;
            }
            default:
            {
                LL_INFOS("Landmark") << "Encountered Unknown landmark version " << version << LL_ENDL;
                break;
            }
        }
    }

    if (bad_block)
    {
        LL_INFOS("Landmark") << "Bad Landmark Asset: bad _DATA_ block." << LL_ENDL;
    }
    return result;
}


// static
void LLLandmark::registerCallbacks(LLMessageSystem* msg)
{
    msg->setHandlerFunc("RegionIDAndHandleReply", &processRegionIDAndHandle);
}

// static
void LLLandmark::requestRegionHandle(
    LLMessageSystem* msg,
    const LLHost& upstream_host,
    const LLUUID& region_id,
    region_handle_callback_t callback)
{
    if(region_id.isNull())
    {
        // don't bother with checking - it's 0.
        LL_DEBUGS("Landmark") << "requestRegionHandle: null" << LL_ENDL;
        if(callback)
        {
            const U64 U64_ZERO = 0;
            callback(region_id, U64_ZERO);
        }
    }
    else
    {
        if(region_id == mLocalRegion.first)
        {
            LL_DEBUGS("Landmark") << "requestRegionHandle: local" << LL_ENDL;
            if(callback)
            {
                callback(region_id, mLocalRegion.second);
            }
        }
        else
        {
            region_map_t::iterator it = mRegions.find(region_id);
            if(it == mRegions.end())
            {
                LL_DEBUGS("Landmark") << "requestRegionHandle: upstream" << LL_ENDL;
                if(callback)
                {
                    region_callback_map_t::value_type vt(region_id, callback);
                    sRegionCallbackMap.insert(vt);
                }
                LL_DEBUGS("Landmark") << "Landmark requesting information about: "
                         << region_id << LL_ENDL;
                msg->newMessage("RegionHandleRequest");
                msg->nextBlock("RequestBlock");
                msg->addUUID("RegionID", region_id);
                msg->sendReliable(upstream_host);
            }
            else if(callback)
            {
                // we have the answer locally - just call the callack.
                LL_DEBUGS("Landmark") << "requestRegionHandle: ready" << LL_ENDL;
                callback(region_id, (*it).second.mRegionHandle);
            }
        }
    }

    // As good a place as any to expire old entries.
    expireOldEntries();
}

// static
void LLLandmark::setRegionHandle(const LLUUID& region_id, U64 region_handle)
{
    mLocalRegion.first = region_id;
    mLocalRegion.second = region_handle;
}


// static
void LLLandmark::processRegionIDAndHandle(LLMessageSystem* msg, void**)
{
    LLUUID region_id;
    msg->getUUID("ReplyBlock", "RegionID", region_id);
    mRegions.erase(region_id);
    CacheInfo info;
    const F32 CACHE_EXPIRY_SECONDS = 60.0f * 10.0f; // ten minutes
    info.mTimer.setTimerExpirySec(CACHE_EXPIRY_SECONDS);
    msg->getU64("ReplyBlock", "RegionHandle", info.mRegionHandle);
    region_map_t::value_type vt(region_id, info);
    mRegions.insert(vt);

#if LL_DEBUG
    U32 grid_x, grid_y;
    grid_from_region_handle(info.mRegionHandle, &grid_x, &grid_y);
    LL_DEBUGS() << "Landmark got reply for region: " << region_id << " "
             << grid_x << "," << grid_y << LL_ENDL;
#endif

    // make all the callbacks here.
    region_callback_map_t::iterator it;
    while((it = sRegionCallbackMap.find(region_id)) != sRegionCallbackMap.end())
    {
        (*it).second(region_id, info.mRegionHandle);
        sRegionCallbackMap.erase(it);
    }
}

// static
void LLLandmark::expireOldEntries()
{
    for(region_map_t::iterator it = mRegions.begin(); it != mRegions.end(); )
    {
        if((*it).second.mTimer.hasExpired())
        {
            mRegions.erase(it++);
        }
        else
        {
            ++it;
        }
    }
}