#include "JSystem/JParticle/JPADynamicsBlock.h"

#include "JSystem/JParticle/JPAEmitter.h"
#include "nw4r/math/math_triangular.h"

void JPAVolumePoint(JPAEmitterWorkData* work) {
    work->mVolumeCalcData.mVolumePos.setZero();
    work->mVolumeCalcData.mVelOmni.set(work->mpEmtr->get_r_zh(), work->mpEmtr->get_r_zh(),
                                       work->mpEmtr->get_r_zh());
    work->mVolumeCalcData.mVelAxis.set(work->mVolumeCalcData.mVelOmni.x, 0.0f,
                                       work->mVolumeCalcData.mVelOmni.z);
}

void JPAVolumeLine(JPAEmitterWorkData* work) {
    if (work->mpEmtr->checkFlag(JPADynFlag_FixedInterval)) {
        work->mVolumeCalcData.mVolumePos.set(
            0.0f, 0.0f,
            work->mVolumeSize * ((work->mVolumeEmitIdx / (work->mEmitCount - 1.0f) - 0.5f)));
        work->mVolumeEmitIdx++;
    } else {
        work->mVolumeCalcData.mVolumePos.set(0.0f, 0.0f,
                                             work->mVolumeSize * work->mpEmtr->get_r_zh());
    }

    work->mVolumeCalcData.mVelOmni.set(0.0f, 0.0f,
                                       work->mVolumeCalcData.mVolumePos.z * work->mGlobalScl.z);
    work->mVolumeCalcData.mVelAxis.set(0.0f, 0.0f, work->mVolumeCalcData.mVolumePos.z);
}

// NONMATCHING regalloc. Could be issue with mul asm implementations
void JPAVolumeCircle(JPAEmitterWorkData* work) {
    s16 theta;
    f32 distance;
    
    if (work->mpEmtr->checkFlag(JPADynFlag_FixedInterval)) {
        theta = (s16)((work->mVolumeEmitIdx << 16) / work->mEmitCount);
        theta = theta * work->mVolumeSweep;
        work->mVolumeEmitIdx++;
    } else {
        theta = work->mVolumeSweep * work->mpEmtr->get_r_ss();
    }

    distance = work->mpEmtr->get_r_f();
    if (work->mpEmtr->checkFlag(JPADynFlag_FixedDensity)) {
        distance = 1.0f - (distance * distance);
    }

    distance = work->mVolumeSize * (work->mVolumeMinRad + distance * (1.0f - work->mVolumeMinRad));
    work->mVolumeCalcData.mVolumePos.set(distance * nw4r::math::SinIdx(theta), 0.0f,
                                         distance * nw4r::math::CosIdx(theta));
    work->mVolumeCalcData.mVelOmni.mul(work->mVolumeCalcData.mVolumePos, work->mGlobalScl);
    work->mVolumeCalcData.mVelAxis.set(work->mVolumeCalcData.mVolumePos.x, 0.0f,
                                       work->mVolumeCalcData.mVolumePos.z);
}

void JPAVolumeCube(JPAEmitterWorkData* work) {
    work->mVolumeCalcData.mVolumePos.set(work->mpEmtr->get_r_zh() * work->mVolumeSize,
                         work->mpEmtr->get_r_zh() * work->mVolumeSize,
                         work->mpEmtr->get_r_zh() * work->mVolumeSize);
    work->mVolumeCalcData.mVelOmni.mul(work->mVolumeCalcData.mVolumePos, work->mGlobalScl);
    work->mVolumeCalcData.mVelAxis.set(work->mVolumeCalcData.mVolumePos.x, 0.0f, work->mVolumeCalcData.mVolumePos.z);
}

static void JPAVolumeSphere(JPAEmitterWorkData* work) {
    s16 phi, r28;
    if (work->mpEmtr->checkFlag(JPADynFlag_FixedInterval)) {
        phi = u16(work->mVolumeX * 0x8000 / (work->mDivNumber - 1) + 0x4000);
        u16 r26 = u16(work->mVolumeAngleNum * 0x10000 / (work->mVolumeAngleMax - 1));
        r28 = f32(r26) * work->mVolumeSweep + 0x8000;
        work->mVolumeAngleNum++;
        if (work->mVolumeAngleNum == work->mVolumeAngleMax) {
            work->mVolumeAngleNum = 0;
            work->mVolumeX++;
            if (work->mVolumeX * 2 < work->mDivNumber) {
                work->mVolumeAngleMax = work->mVolumeAngleMax != 1 ?
                    work->mVolumeAngleMax + 4 : work->mVolumeAngleMax + 3;
            } else {
                work->mVolumeAngleMax = work->mVolumeAngleMax != 4 ? work->mVolumeAngleMax - 4 : 1;
            }
        }
    } else {
        phi = work->mpEmtr->get_r_ss() >> 1;
        r28 = work->mVolumeSweep * work->mpEmtr->get_r_ss();
    }

    f32 f31 = work->mpEmtr->get_r_f();
    if (work->mpEmtr->checkFlag(JPADynFlag_FixedDensity)) {
        f31 = 1.0f - f31 * f31 * f31;
    }
    f31 = work->mVolumeSize * (work->mVolumeMinRad + f31 * (1.0f - work->mVolumeMinRad));
    work->mVolumeCalcData.mVolumePos.set(f31 * nw4r::math::CosIdx(phi) * nw4r::math::SinIdx(r28), -f31 * nw4r::math::SinIdx(phi),
                                         f31 * nw4r::math::CosIdx(phi) * nw4r::math::CosIdx(r28));
    work->mVolumeCalcData.mVelOmni.set(
        work->mVolumeCalcData.mVolumePos.x * work->mGlobalScl.x,
        work->mVolumeCalcData.mVolumePos.y * work->mGlobalScl.y, work->mVolumeCalcData.mVolumePos.z * work->mGlobalScl.z
    );
    work->mVolumeCalcData.mVelAxis.set(work->mVolumeCalcData.mVolumePos.x, 0.0f,
                                       work->mVolumeCalcData.mVolumePos.z);
}

static void JPAVolumeCylinder(JPAEmitterWorkData* work) {
    s16 r30 = work->mVolumeSweep * work->mpEmtr->get_r_ss();
    f32 f31 = work->mpEmtr->get_r_f();
    if (work->mpEmtr->checkFlag(JPADynFlag_FixedDensity)) {
        f31 = 1.0f - f31 * f31;
    }
    f31 = work->mVolumeSize * (work->mVolumeMinRad + f31 * (1.0f - work->mVolumeMinRad));
    work->mVolumeCalcData.mVolumePos.set(
        f31 * nw4r::math::SinIdx(r30), work->mVolumeSize * work->mpEmtr->get_r_zp(), f31 * nw4r::math::CosIdx(r30));
    work->mVolumeCalcData.mVelOmni.set(
        work->mVolumeCalcData.mVolumePos.x * work->mGlobalScl.x,
        work->mVolumeCalcData.mVolumePos.y * work->mGlobalScl.y, work->mVolumeCalcData.mVolumePos.z * work->mGlobalScl.z
    );
    work->mVolumeCalcData.mVelAxis.set(work->mVolumeCalcData.mVolumePos.x, 0.0f,
                                       work->mVolumeCalcData.mVolumePos.z);
}

static void JPAVolumeTorus(JPAEmitterWorkData* work) {
    s16 theta = work->mVolumeSweep * work->mpEmtr->get_r_ss();
    s16 phi = work->mpEmtr->get_r_ss();
    f32 rad = work->mVolumeSize * work->mVolumeMinRad;
    work->mVolumeCalcData.mVelAxis.set(rad * nw4r::math::SinIdx(theta) * nw4r::math::CosIdx(phi), rad * nw4r::math::SinIdx(phi),
                                       rad * nw4r::math::CosIdx(theta) * nw4r::math::CosIdx(phi));
    work->mVolumeCalcData.mVolumePos.set(
        work->mVolumeCalcData.mVelAxis.x + work->mVolumeSize * nw4r::math::SinIdx(theta),
        work->mVolumeCalcData.mVelAxis.y,
        work->mVolumeCalcData.mVelAxis.z + work->mVolumeSize * nw4r::math::CosIdx(theta));
    work->mVolumeCalcData.mVelOmni.set(
        work->mVolumeCalcData.mVolumePos.x * work->mGlobalScl.x,
        work->mVolumeCalcData.mVolumePos.y * work->mGlobalScl.y, work->mVolumeCalcData.mVolumePos.z * work->mGlobalScl.z
    );
}

JPADynamicsBlock::JPADynamicsBlock(u8 const* data) {
    mpData = (const JPADynamicsBlockData*)data;
    init();
}

enum {
    VOL_Cube = 0x00,
    VOL_Sphere = 0x01,
    VOL_Cylinder = 0x02,
    VOL_Torus = 0x03,
    VOL_Point = 0x04,
    VOL_Circle = 0x05,
    VOL_Line = 0x06,
};

void JPADynamicsBlock::init() {
    switch (getVolumeType()) {
    case VOL_Cube:
        mpCalcVolumeFunc = &JPAVolumeCube;
        break;
    case VOL_Sphere:
        mpCalcVolumeFunc = &JPAVolumeSphere;
        break;
    case VOL_Cylinder:
        mpCalcVolumeFunc = &JPAVolumeCylinder;
        break;
    case VOL_Torus:
        mpCalcVolumeFunc = &JPAVolumeTorus;
        break;
    case VOL_Point:
        mpCalcVolumeFunc = &JPAVolumePoint;
        break;
    case VOL_Circle:
        mpCalcVolumeFunc = &JPAVolumeCircle;
        break;
    case VOL_Line:
        mpCalcVolumeFunc = &JPAVolumeLine;
        break;
    }
}

void JPADynamicsBlock::create(JPAEmitterWorkData* work) {
    if (work->mpEmtr->checkStatus(JPAEmtrStts_RateStepEmit)) {
        s32 emitCount = 0;

        if (work->mpEmtr->checkFlag(JPADynFlag_FixedInterval)) {
            emitCount = getVolumeType() == VOL_Sphere ? 4 * getDivNumber() * getDivNumber() + 2 : getDivNumber();

            work->mVolumeEmitIdx = 0;
        } else {
            f32 newPtclCount =
                work->mpEmtr->mRate * (getRateRndm() * work->mpEmtr->get_r_zp() + 1.0f);
            emitCount = work->mpEmtr->mEmitCount += newPtclCount;
            work->mpEmtr->mEmitCount -= emitCount;

            if (work->mpEmtr->checkStatus(JPAEmtrStts_FirstEmit) && 0.0f < newPtclCount &&
                newPtclCount < 1.0f)
                emitCount = 1;
        }

        work->mEmitCount = emitCount;
        if (work->mpEmtr->checkStatus(JPAEmtrStts_StopEmit)) {
            emitCount = 0;
        }

        JPABaseParticle* ptcl = NULL;
        s32 createCount = emitCount;
        while (createCount > 0) {
            ptcl = work->mpEmtr->createParticle();
            if (ptcl == NULL)
                break;
            createCount--;
        }
    }

    if (++work->mpEmtr->mRateStepTimer >= (work->mpEmtr->mRateStep + 1)) {
        work->mpEmtr->mRateStepTimer -= work->mpEmtr->mRateStep + 1;
        work->mpEmtr->setStatus(JPAEmtrStts_RateStepEmit);
    } else {
        work->mpEmtr->clearStatus(JPAEmtrStts_RateStepEmit);
    }

    work->mpEmtr->clearStatus(JPAEmtrStts_FirstEmit);
}
