/**
 * @file llpartdata.cpp
 * @brief Particle system data packing
 *
 * $LicenseInfo:firstyear=2003&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 "llpartdata.h"
#include "message.h"

#include "lldatapacker.h"
#include "v4coloru.h"

#include "llsdutil.h"
#include "llsdutil_math.h"



const S32 PS_PART_DATA_GLOW_SIZE = 2;
const S32 PS_PART_DATA_BLEND_SIZE = 2;
const S32 PS_LEGACY_PART_DATA_BLOCK_SIZE = 4 + 2 + 4 + 4 + 2 + 2; //18
const S32 PS_SYS_DATA_BLOCK_SIZE = 68;
const S32 PS_MAX_DATA_BLOCK_SIZE = PS_SYS_DATA_BLOCK_SIZE+
                                    PS_LEGACY_PART_DATA_BLOCK_SIZE +
                                    PS_PART_DATA_BLEND_SIZE +
                                    PS_PART_DATA_GLOW_SIZE+
                                    8; //two S32 size fields

const S32 PS_LEGACY_DATA_BLOCK_SIZE = PS_SYS_DATA_BLOCK_SIZE + PS_LEGACY_PART_DATA_BLOCK_SIZE;

const F32 MAX_PART_SCALE = 4.f;

bool LLPartData::hasGlow() const
{
    return mStartGlow > 0.f || mEndGlow > 0.f;
}

bool LLPartData::hasBlendFunc() const
{
    return mBlendFuncSource != LLPartData::LL_PART_BF_SOURCE_ALPHA || mBlendFuncDest != LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_ALPHA;
}

S32 LLPartData::getSize() const
{
    S32 size = PS_LEGACY_PART_DATA_BLOCK_SIZE;
    if (hasGlow()) size += PS_PART_DATA_GLOW_SIZE;
    if (hasBlendFunc()) size += PS_PART_DATA_BLEND_SIZE;

    return size;
}


bool LLPartData::unpackLegacy(LLDataPacker &dp)
{
    LLColor4U coloru;

    dp.unpackU32(mFlags, "pdflags");
    dp.unpackFixed(mMaxAge, "pdmaxage", false, 8, 8);

    dp.unpackColor4U(coloru, "pdstartcolor");
    mStartColor.setVec(coloru);
    dp.unpackColor4U(coloru, "pdendcolor");
    mEndColor.setVec(coloru);
    dp.unpackFixed(mStartScale.mV[0], "pdstartscalex", false, 3, 5);
    dp.unpackFixed(mStartScale.mV[1], "pdstartscaley", false, 3, 5);
    dp.unpackFixed(mEndScale.mV[0], "pdendscalex", false, 3, 5);
    dp.unpackFixed(mEndScale.mV[1], "pdendscaley", false, 3, 5);

    mStartGlow = 0.f;
    mEndGlow = 0.f;
    mBlendFuncSource = LLPartData::LL_PART_BF_SOURCE_ALPHA;
    mBlendFuncDest = LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_ALPHA;

    return true;
}

bool LLPartData::unpack(LLDataPacker &dp)
{
    S32 size = 0;
    dp.unpackS32(size, "partsize");

    unpackLegacy(dp);
    size -= PS_LEGACY_PART_DATA_BLOCK_SIZE;

    if (mFlags & LL_PART_DATA_GLOW)
    {
        if (size < PS_PART_DATA_GLOW_SIZE) return false;

        U8 tmp_glow = 0;
        dp.unpackU8(tmp_glow,"pdstartglow");
        mStartGlow = tmp_glow / 255.f;
        dp.unpackU8(tmp_glow,"pdendglow");
        mEndGlow = tmp_glow / 255.f;

        size -= PS_PART_DATA_GLOW_SIZE;
    }
    else
    {
        mStartGlow = 0.f;
        mEndGlow = 0.f;
    }

    if (mFlags & LL_PART_DATA_BLEND)
    {
        if (size < PS_PART_DATA_BLEND_SIZE) return false;
        dp.unpackU8(mBlendFuncSource,"pdblendsource");
        dp.unpackU8(mBlendFuncDest,"pdblenddest");
        size -= PS_PART_DATA_BLEND_SIZE;
    }
    else
    {
        mBlendFuncSource = LLPartData::LL_PART_BF_SOURCE_ALPHA;
        mBlendFuncDest = LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_ALPHA;
    }

    if (size > 0)
    { //leftover bytes, unrecognized parameters
        U8 feh = 0;
        while (size > 0)
        { //read remaining bytes in block
            dp.unpackU8(feh, "whippang");
            size--;
        }

        //this particle system won't display properly, better to not show anything
        return false;
    }


    return true;
}

void LLPartData::setFlags(const U32 flags)
{
    mFlags = flags;
}


void LLPartData::setMaxAge(const F32 max_age)
{
    mMaxAge = llclamp(max_age, 0.f, 30.f);
}


void LLPartData::setStartScale(const F32 xs, const F32 ys)
{
    mStartScale.mV[VX] = llmin(xs, MAX_PART_SCALE);
    mStartScale.mV[VY] = llmin(ys, MAX_PART_SCALE);
}


void LLPartData::setEndScale(const F32 xs, const F32 ys)
{
    mEndScale.mV[VX] = llmin(xs, MAX_PART_SCALE);
    mEndScale.mV[VY] = llmin(ys, MAX_PART_SCALE);
}


void LLPartData::setStartColor(const LLVector3 &rgb)
{
    mStartColor.setVec(rgb.mV[0], rgb.mV[1], rgb.mV[2]);
}


void LLPartData::setEndColor(const LLVector3 &rgb)
{
    mEndColor.setVec(rgb.mV[0], rgb.mV[1], rgb.mV[2]);
}

void LLPartData::setStartAlpha(const F32 alpha)
{
    mStartColor.mV[3] = alpha;
}
void LLPartData::setEndAlpha(const F32 alpha)
{
    mEndColor.mV[3] = alpha;
}

// static
bool LLPartData::validBlendFunc(S32 func)
{
    if (func >= 0
        && func < LL_PART_BF_COUNT
        && func != UNSUPPORTED_DEST_ALPHA
        && func != UNSUPPORTED_ONE_MINUS_DEST_ALPHA)
    {
        return true;
    }
    return false;
}

LLPartSysData::LLPartSysData()
{
    mCRC = 0;
    mFlags = 0;

    mPartData.mFlags = 0;
    mPartData.mStartColor = LLColor4(1.f, 1.f, 1.f, 1.f);
    mPartData.mEndColor = LLColor4(1.f, 1.f, 1.f, 1.f);
    mPartData.mStartScale = LLVector2(1.f, 1.f);
    mPartData.mEndScale = LLVector2(1.f, 1.f);
    mPartData.mMaxAge = 10.0;
    mPartData.mBlendFuncSource = LLPartData::LL_PART_BF_SOURCE_ALPHA;
    mPartData.mBlendFuncDest = LLPartData::LL_PART_BF_ONE_MINUS_SOURCE_ALPHA;
    mPartData.mStartGlow = 0.f;
    mPartData.mEndGlow = 0.f;

    mMaxAge = 0.0;
    mStartAge = 0.0;
    mPattern = LL_PART_SRC_PATTERN_DROP;            // Pattern for particle velocity
    mInnerAngle = 0.0;                              // Inner angle of PATTERN_ANGLE_*
    mOuterAngle = 0.0;                              // Outer angle of PATTERN_ANGLE_*
    mBurstRate = 0.1f;                              // How often to do a burst of particles
    mBurstPartCount = 1;                            // How many particles in a burst
    mBurstSpeedMin = 1.f;                       // Minimum particle velocity
    mBurstSpeedMax = 1.f;                       // Maximum particle velocity
    mBurstRadius = 0.f;

    mNumParticles = 0;
}

bool LLPartSysData::unpackSystem(LLDataPacker &dp)
{
    dp.unpackU32(mCRC, "pscrc");
    dp.unpackU32(mFlags, "psflags");
    dp.unpackU8(mPattern, "pspattern");
    dp.unpackFixed(mMaxAge, "psmaxage", false, 8, 8);
    dp.unpackFixed(mStartAge, "psstartage", false, 8, 8);
    dp.unpackFixed(mInnerAngle, "psinnerangle", false, 3, 5);
    dp.unpackFixed(mOuterAngle, "psouterangle", false, 3, 5);
    dp.unpackFixed(mBurstRate, "psburstrate", false, 8, 8);
    mBurstRate = llmax(0.01f, mBurstRate);
    dp.unpackFixed(mBurstRadius, "psburstradius", false, 8, 8);
    dp.unpackFixed(mBurstSpeedMin, "psburstspeedmin", false, 8, 8);
    dp.unpackFixed(mBurstSpeedMax, "psburstspeedmax", false, 8, 8);
    dp.unpackU8(mBurstPartCount, "psburstpartcount");

    dp.unpackFixed(mAngularVelocity.mV[0], "psangvelx", true, 8, 7);
    dp.unpackFixed(mAngularVelocity.mV[1], "psangvely", true, 8, 7);
    dp.unpackFixed(mAngularVelocity.mV[2], "psangvelz", true, 8, 7);

    dp.unpackFixed(mPartAccel.mV[0], "psaccelx", true, 8, 7);
    dp.unpackFixed(mPartAccel.mV[1], "psaccely", true, 8, 7);
    dp.unpackFixed(mPartAccel.mV[2], "psaccelz", true, 8, 7);

    dp.unpackUUID(mPartImageID, "psuuid");
    dp.unpackUUID(mTargetUUID, "pstargetuuid");
    return true;
}

bool LLPartSysData::unpackLegacy(LLDataPacker &dp)
{
    unpackSystem(dp);
    mPartData.unpackLegacy(dp);

    return true;
}

bool LLPartSysData::unpack(LLDataPacker &dp)
{
    // syssize is currently unused.  Adding now when modifying the 'version to make extensible in the future
    S32 size = 0;
    dp.unpackS32(size, "syssize");

    if (size != PS_SYS_DATA_BLOCK_SIZE)
    { //unexpected size, this viewer doesn't know how to parse this particle system

        //skip to LLPartData block
        U8 feh = 0;

        for (S32 i = 0; i < size; ++i)
        {
            dp.unpackU8(feh, "whippang");
        }

        dp.unpackS32(size, "partsize");
        //skip LLPartData block
        for (S32 i = 0; i < size; ++i)
        {
            dp.unpackU8(feh, "whippang");
        }
        return false;
    }

    unpackSystem(dp);

    return mPartData.unpack(dp);
}

std::ostream& operator<<(std::ostream& s, const LLPartSysData &data)
{
    s << "Flags: " << std::hex << data.mFlags;
    s << "Pattern: " << std::hex << (U32) data.mPattern << "\n";
    s << "Source Age: [" << data.mStartAge << ", " << data.mMaxAge << "]\n";
    s << "Particle Age: " << data.mPartData.mMaxAge << "\n";
    s << "Angle: [" << data.mInnerAngle << ", " << data.mOuterAngle << "]\n";
    s << "Burst Rate: " << data.mBurstRate << "\n";
    s << "Burst Radius: " << data.mBurstRadius << "\n";
    s << "Burst Speed: [" << data.mBurstSpeedMin << ", " << data.mBurstSpeedMax << "]\n";
    s << "Burst Part Count: " << std::hex << (U32) data.mBurstPartCount << "\n";
    s << "Angular Velocity: " << data.mAngularVelocity << "\n";
    s << "Accel: " << data.mPartAccel;
    return s;
}

bool LLPartSysData::isNullPS(const S32 block_num)
{
    U8 ps_data_block[PS_MAX_DATA_BLOCK_SIZE];
    U32 crc;

    S32 size;
    // Check size of block
    size = gMessageSystem->getSize("ObjectData", block_num, "PSBlock");

    if (!size)
    {
        return true;
    }

    if (size > PS_MAX_DATA_BLOCK_SIZE)
    {
        //size is too big, newer particle version unsupported
        return true;
    }

    gMessageSystem->getBinaryData("ObjectData", "PSBlock", ps_data_block, size, block_num, PS_MAX_DATA_BLOCK_SIZE);

    LLDataPackerBinaryBuffer dp(ps_data_block, size);
    if (size > PS_LEGACY_DATA_BLOCK_SIZE)
    {
        // non legacy systems pack a size before the CRC
        S32 tmp = 0;
        dp.unpackS32(tmp, "syssize");

        if (tmp > PS_SYS_DATA_BLOCK_SIZE)
        { //unknown system data block size, don't know how to parse it, treat as NULL
            return true;
        }
    }

    dp.unpackU32(crc, "crc");

    if (crc == 0)
    {
        return true;
    }
    return false;
}

bool LLPartSysData::unpackBlock(const S32 block_num)
{
    U8 ps_data_block[PS_MAX_DATA_BLOCK_SIZE];

    // Check size of block
    S32 size = gMessageSystem->getSize("ObjectData", block_num, "PSBlock");

    if (size > PS_MAX_DATA_BLOCK_SIZE)
    {
        // Larger packets are newer and unsupported
        return false;
    }

    // Get from message
    gMessageSystem->getBinaryData("ObjectData", "PSBlock", ps_data_block, size, block_num, PS_MAX_DATA_BLOCK_SIZE);

    LLDataPackerBinaryBuffer dp(ps_data_block, size);

    if (size == PS_LEGACY_DATA_BLOCK_SIZE)
    {
        return unpackLegacy(dp);
    }
    else
    {
        return unpack(dp);
    }
}

bool LLPartSysData::isLegacyCompatible() const
{
    return !mPartData.hasGlow() && !mPartData.hasBlendFunc();
}

void LLPartSysData::clampSourceParticleRate()
{
    F32 particle_rate = 0;
    particle_rate = mBurstPartCount/mBurstRate;
    if (particle_rate > 256.f)
    {
        mBurstPartCount = llfloor(((F32)mBurstPartCount)*(256.f/particle_rate));
    }
}

void LLPartSysData::setPartAccel(const LLVector3 &accel)
{
    mPartAccel.mV[VX] = llclamp(accel.mV[VX], -100.f, 100.f);
    mPartAccel.mV[VY] = llclamp(accel.mV[VY], -100.f, 100.f);
    mPartAccel.mV[VZ] = llclamp(accel.mV[VZ], -100.f, 100.f);
}