#include "JSystem/JSystem.h" // IWYU pragma: keep

#include "JSystem/JAudio2/JASChannel.h"
#if TARGET_PC
#include "dusk/audio/DuskDsp.hpp"
#endif
#include "JSystem/JAudio2/JASAiCtrl.h"
#include "JSystem/JAudio2/JASCalc.h"
#include "JSystem/JAudio2/JASDriverIF.h"
#include "JSystem/JAudio2/JASDSPChannel.h"
#include "JSystem/JSupport/JSupport.h"
#include "JSystem/JMath/JMATrigonometric.h"
#include "JSystem/JGeometry.h"

DUSK_GAME_DATA OSMessageQueue JASChannel::sBankDisposeMsgQ;

DUSK_GAME_DATA OSMessage JASChannel::sBankDisposeMsg[16];

DUSK_GAME_DATA OSMessage JASChannel::sBankDisposeList[16];

DUSK_GAME_DATA int JASChannel::sBankDisposeListSize;

JASChannel::JASChannel(Callback i_callback, void* i_callbackData) :
    mStatus(STATUS_STOP),
    mDspCh(NULL),
    mCallback(i_callback),
    mCallbackData(i_callbackData),
    mUpdateTimer(0),
    mBankDisposeID(NULL),
    mKey(0),
    mVelocity(0x7f),
    mKeySweep(0.0f),
    mKeySweepTarget(0.0f),
    mKeySweepCount(0),
    mSkipSamples(0)
{
    field_0xdc.mChannelType = 0;
    field_0x104 = 0;
    mMixConfig[0].whole = 0x150;
    mMixConfig[1].whole = 0x210;
    mMixConfig[2].whole = 0x352;
    mMixConfig[3].whole = 0x412;
    mMixConfig[4].whole = 0;
    mMixConfig[5].whole = 0;
    mPriority = 0x13f;
    mPauseFlag = false;
}

JASChannel::~JASChannel() {
    if (mDspCh != NULL) {
        JUT_WARN(62, "%s","~JASChannel:: mDspCh != NULL");
        mDspCh->drop();
    }
    if (mCallback != NULL) {
        mCallback(CB_STOP, this, NULL, mCallbackData);
    }
}


int JASChannel::play() {
    JASDSPChannel* channel = JASDSPChannel::alloc(JSULoByte(mPriority), dspUpdateCallback, this);
    if (channel == NULL) {
        JKR_DELETE(this);
        return 0;
    }
    mDspCh = channel;
    channel->start();
    mStatus = STATUS_PLAY;
    return 1;
}

int JASChannel::playForce() {
    JASDSPChannel* channel = JASDSPChannel::allocForce(JSULoByte(mPriority),
                                                       dspUpdateCallback, this);
    if (channel == NULL) {
        JKR_DELETE(this);
        return 0;
    }
    mDspCh = channel;
    channel->start();
    mStatus = STATUS_PLAY;
    return 1;
}

void JASChannel::release(u16 i_directRelease) {
    if (mStatus == STATUS_PLAY) {
        if (i_directRelease != 0) {
            setDirectRelease(i_directRelease);
        }
        for (u32 i = 0; i < 2; i++) {
            if (mOscillators[i].isValid()) {
                mOscillators[i].release();
            }
        }
        JUT_ASSERT(135, mDspCh);
        mDspCh->setPriority(JSUHiByte(mPriority));
        mStatus = STATUS_RELEASE;
    }
}

void JASChannel::setOscInit(u32 oscnum, JASOscillator::Data const* i_data) {
    JUT_ASSERT(147, oscnum < OSC_NUM);
    mOscillators[oscnum].initStart(i_data);
}

void JASChannel::setMixConfig(u32 bus, u16 i_config) {
    JUT_ASSERT(153, bus < BUSOUT_CPUCH);
    mMixConfig[bus].whole = i_config;
}

f32 JASChannel::calcEffect(JASChannel::PanVector const* i_vector) {
    return i_vector->mSound + i_vector->mEffect + i_vector->mChannel;
}

f32 JASChannel::calcPan(JASChannel::PanVector const* i_vector) {
    return 0.5f + (i_vector->mSound - 0.5f) + (i_vector->mEffect - 0.5f)
        + (i_vector->mChannel - 0.5f);
}

void JASChannel::effectOsc(u32 oscnum, JASOscillator::EffectParams* i_params) {
    JUT_ASSERT(176, oscnum < OSC_NUM);
    f32 value = mOscillators[oscnum].getValue();
    switch (mOscillators[oscnum].getTarget()) {
    case JASOscillator::TARGET_PITCH:
        i_params->mPitch *= value;
        break;
    case JASOscillator::TARGET_VOLUME:
        i_params->mVolume *= value;
        break;
    case JASOscillator::TARGET_PAN:
        value -= 0.5;
        i_params->mPan += value;
        break;
    case JASOscillator::TARGET_FXMIX:
        i_params->mFxMix += value;
        break;
    case JASOscillator::TARGET_DOLBY:
        i_params->mDolby += value;
        break;
    case JASOscillator::TARGET_5:
        i_params->_14 *= value;
        break;
    case JASOscillator::TARGET_6:
        i_params->_18 *= value;
        break;
    default:
        JUT_WARN(205, "%s", "Invalid osc target");
    }
}

void JASChannel::setKeySweepTarget(s32 i_target, u32 i_count) {
    if (i_count == 0) {
        mKeySweep = i_target;
    } else {
        mKeySweep = 0.0f;
        mKeySweepTarget = i_target;
    }
    mKeySweepCount = i_count;
}

void JASChannel::updateEffectorParam(JASDsp::TChannel* i_channel, u16* i_mixerVolume,
                                     JASOscillator::EffectParams const& i_params) {
    PanVector pan_vector, fxmix_vector, dolby_vector;
    pan_vector.mSound = mSoundParams.mPan;
    pan_vector.mChannel = mParams.mPan;
    pan_vector.mEffect = i_params.mPan;
    fxmix_vector.mSound = mSoundParams.mFxMix;
    fxmix_vector.mChannel = mParams.mFxMix;
    fxmix_vector.mEffect = i_params.mFxMix;
    dolby_vector.mSound = mSoundParams.mDolby;
    dolby_vector.mChannel = mParams.mDolby;
    dolby_vector.mEffect = i_params.mDolby;

    f32 pan = 0.5f;
    f32 dolby = 0.0f;
#if TARGET_PC
    u32 effectiveOutputMode = dusk::audio::EnableHrtf ? JAS_OUTPUT_SURROUND : JASDriver::getOutputMode();
#else
    u32 effectiveOutputMode = JASDriver::getOutputMode();
#endif
    switch (effectiveOutputMode) {
    case JAS_OUTPUT_MONO:
        break;
    case JAS_OUTPUT_STEREO:
        pan = calcPan(&pan_vector);
        break;
    case JAS_OUTPUT_SURROUND:
        pan = calcPan(&pan_vector);
        dolby = calcEffect(&dolby_vector);
        break;
    }
    f32 fxmix = calcEffect(&fxmix_vector);
    f32 volume = mVelocity / 127.0f;
    volume = volume * volume;
    volume = mSoundParams.mVolume * i_params.mVolume * mParams.mVolume
        * (i_params._18 * mTremolo.getValue() + 1.0f) * volume;
    
    if (volume < 0.0f) {
        volume = 0.0f;
    }
    pan = JASCalc::clamp01(pan);
    fxmix = JASCalc::clamp01(fxmix);
    dolby = JASCalc::clamp01(dolby);

    if (isDolbyMode()) {
        updateAutoMixer(i_channel, volume, pan, fxmix, dolby);
    } else {
        updateMixer(volume, pan, fxmix, dolby, i_mixerVolume);
    }
}

s32 JASChannel::dspUpdateCallback(u32 i_type, JASDsp::TChannel* i_channel, void* i_this) {
    JASChannel* _this = static_cast<JASChannel*>(i_this);
    switch (i_type) {
    case JASDSPChannel::CB_PLAY:
        return _this->updateDSPChannel(i_channel);
    case JASDSPChannel::CB_START:
        return _this->initialUpdateDSPChannel(i_channel);
    case JASDSPChannel::CB_STOP:
    case JASDSPChannel::CB_DROP:
        _this->mDspCh->free();
        _this->mDspCh = NULL;
        JKR_DELETE(_this);
        return -1;
    default:
        JUT_WARN(323, "Unexpected JASDSPChannel::UpdateStatus %d", i_type);
    }
    return 0;
}

s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) {
    if (isDolbyMode()) {
        i_channel->initAutoMixer();
    }
    
    if (mCallback != NULL) {
        mCallback(CB_START, this, i_channel, mCallbackData);
    }

    if (field_0xdc.mWaveInfo.field_0x20[0] == 0) {
        JUT_WARN_DEVICE(346, 2, "%s", "Lost wave data while playing");
        mDspCh->free();
        mDspCh = NULL;
        JKR_DELETE(this);
        return -1;
    }
    
    if (checkBankDispose()) {
        JUT_WARN_DEVICE(357, 2, "%s","Lost bank data while playing");
        mDspCh->free();
        mDspCh = NULL;
        JKR_DELETE(this);
        return -1;
    }

    switch (field_0xdc.mChannelType) {
    case 0:
        i_channel->setWaveInfo(field_0xdc.mWaveInfo, mWaveAramAddress, mSkipSamples);
        break;
    case 2:
        i_channel->setOscInfo(mOscillatorSomething);
        break;
    }

    for (u8 i = 0; i < DSP_OUTPUT_CHANNELS; i++) {
        MixConfig mix_config = mMixConfig[i];
        u32 output_mode = JASDriver::getOutputMode();
        if (output_mode == JAS_OUTPUT_MONO) {
            switch (mix_config.parts.upper) {
            case 8:
                mix_config.parts.upper = 11;
                break;
            case 9:
                mix_config.parts.upper = 2;
                break;
            }
        } else if (output_mode == JAS_OUTPUT_STEREO && mix_config.parts.upper == 8) {
            mix_config.parts.upper = 11;
        }
        i_channel->setBusConnect(i, mix_config.parts.upper);
    }

    JASOscillator::EffectParams effect_params;
    for (u32 i = 0; i < 2; i++) {
        if (mOscillators[i].isValid()) {
            mOscillators[i].update();
            effectOsc(i, &effect_params);
        }
    }
    mVibrate.resetCounter();
    mTremolo.resetCounter();
    u16 mixer_volume[DSP_OUTPUT_CHANNELS];
    updateEffectorParam(i_channel, mixer_volume, effect_params);
    for (u8 i = 0; i < DSP_OUTPUT_CHANNELS; i++) {
        i_channel->setMixerInitVolume(i, mixer_volume[i]);
    }

    f32 pitch = JASCalc::pow2(mParams.field_0x8 + (mKey + mKeySweep) / 12.0f
                                                + effect_params._14 * mVibrate.getValue());
    pitch = mSoundParams.mPitch * effect_params.mPitch * pitch * mParams.mPitch * 4096.0f;
    if (pitch < 0.0f) {
        pitch = 0.0f;
    }
    i_channel->setPitch(pitch);

    i_channel->setPauseFlag(mPauseFlag);
    i_channel->field_0x066 = 0;
    return 0;
}

s32 JASChannel::updateDSPChannel(JASDsp::TChannel* i_channel) {
    JUT_ASSERT(444, mStatus == STATUS_PLAY || mStatus == STATUS_RELEASE);
    JUT_ASSERT(445, mDspCh);
    if (mCallback != NULL) {
        mCallback(CB_PLAY, this, i_channel, mCallbackData);
    }

    if (field_0xdc.mWaveInfo.field_0x20[0] == 0) {
        JUT_WARN_DEVICE(456, 2, "%s","Lost wave data while playing");
        mDspCh->free();
        mDspCh = NULL;
        JKR_DELETE(this);
        return -1;
    }
    
    if (checkBankDispose()) {
        JUT_WARN_DEVICE(467, 2, "%s", "Lost bank data while playing");
        mDspCh->free();
        mDspCh = NULL;
        JKR_DELETE(this);
        return -1;
    }

    i_channel->setPauseFlag(mPauseFlag);
    JASOscillator::EffectParams effect_params;
    if (mPauseFlag) {
        if (mOscillators[0].isRelease()) {
            mDspCh->free();
            mDspCh = NULL;
            JKR_DELETE(this);
            return -1;
        }
    } else {
        f32 inc = 32028.5f / JASDriver::getDacRate();
        mVibrate.incCounter(inc);
        mTremolo.incCounter(inc);
        if (mUpdateTimer != 0) {
            mUpdateTimer--;
            if (mUpdateTimer == 0 && mCallback != NULL) {
                mCallback(CB_TIMER, this, i_channel, mCallbackData);
            }
        }
        inc = 48000.0f / JASDriver::getDacRate();
        for (u32 i = 0; i < 2; i++) {
            if (mOscillators[i].isValid()) {
                mOscillators[i].incCounter(inc);
                effectOsc(i, &effect_params);
                if (i == 0 && mOscillators[i].isStop()) {
                    mDspCh->free();
                    mDspCh = NULL;
                    JKR_DELETE(this);
                    return -1;
                }
            }
        }
    }

    u16 mixer_volume[6];
    updateEffectorParam(i_channel, mixer_volume, effect_params);
    for (u8 i = 0; i < 6; i++) {
        i_channel->setMixerVolume(i, mixer_volume[i]);
    }

    f32 pitch = JASCalc::pow2(mParams.field_0x8 + (mKey + mKeySweep) / 12.0f
                                                + effect_params._14 * mVibrate.getValue());
    pitch = mSoundParams.mPitch * effect_params.mPitch * pitch * mParams.mPitch * 4096.0f;
    if (pitch < 0.0f) {
        pitch = 0.0f;
    }
    i_channel->setPitch(pitch);

    if (!mPauseFlag && mKeySweepCount != 0) {
        mKeySweep += (mKeySweepTarget - mKeySweep) / mKeySweepCount;
        mKeySweepCount--;
    }
    return 0;
}

void JASChannel::updateAutoMixer(JASDsp::TChannel* i_channel, f32 volume, f32 pan,
                                 f32 fxmix, f32 dolby) {
    if (JASDriver::getOutputMode() == JAS_OUTPUT_MONO) {
        volume *= 0.707f;
    }
    volume = JASCalc::clamp01(volume);

    u16 dspVolume = volume * JASDriver::getChannelLevel_dsp();
    u8 dspPan = pan * 127.5f;
    u8 dspDolby = dolby * 127.5f;
    u8 dspFxMix = fxmix * 127.5f;
    i_channel->setAutoMixer(dspVolume, dspPan, dspDolby, dspFxMix, 0);
}

void JASChannel::updateMixer(f32 i_volume, f32 i_pan, f32 i_fxmix, f32 i_dolby, u16* i_volumeOut) {
    for (u32 i = 0; i < 6; i++) {
        f32 volume = i_volume;
        MixConfig config = mMixConfig[i];
        if (config.parts.upper == 0) {
            i_volumeOut[i] = 0;
        } else {
            f32 scale;

            if (config.parts.lower0 != 0) {
                switch (config.parts.lower0) {
                case 1:
                    scale = i_pan;
                    break;
                case 2:
                    scale = i_fxmix;
                    break;
                case 3:
                    scale = i_dolby;
                    break;
                case 5:
                    scale = 1.0f - i_pan;
                    break;
                case 6:
                    scale = 1.0f - i_fxmix;
                    break;
                case 7:
                    scale = 1.0f - i_dolby;
                    break;
                }

                switch (config.parts.lower0) {
                case 2:
                case 6:
                    volume *= scale;
                    break;
                default:
                    if (JASDriver::getOutputMode() == JAS_OUTPUT_MONO) {
                        volume *= scale;
                    } else {
                        volume *= JMASinRadian(scale * JGeometry::TUtil<f32>::PI() * 0.5f);
                    }
                    break;
                }
            }

            if (config.parts.lower1 != 0) {
                switch (config.parts.lower1) {
                case 1:
                    scale = i_pan;
                    break;
                case 2:
                    scale = i_fxmix;
                    break;
                case 3:
                    scale = i_dolby;
                    break;
                case 5:
                    scale = 1.0f - i_pan;
                    break;
                case 6:
                    scale = 1.0f - i_fxmix;
                    break;
                case 7:
                    scale = 1.0f - i_dolby;
                    break;
                }

                switch (config.parts.lower1) {
                case 3:
                case 7:
                    volume *= JMASinRadian((scale * 0.34776f + 0.32612f)
                                           * JGeometry::TUtil<f32>::PI() * 0.5f);
                    break;
                case 2:
                case 6:
                    volume *= scale;
                    break;
                default: // 0, 1, 4, 5, 8-15
                    if (JASDriver::getOutputMode() == JAS_OUTPUT_MONO) {
                        volume *= scale;
                    } else {
                        volume *= JMASinRadian(scale * JGeometry::TUtil<f32>::PI() * 0.5f);
                    }
                    break;
                }
            }

            volume = JASCalc::clamp01(volume);
            i_volumeOut[i] = volume * JASDriver::getChannelLevel_dsp();
        }
    }
}

void JASChannel::free() {
    JUT_ASSERT(661, mStatus == STATUS_RELEASE || mStatus == STATUS_STOP);
    mCallback = NULL;
    mCallbackData = NULL;
}

void JASChannel::initBankDisposeMsgQueue() {
    OSInitMessageQueue(&sBankDisposeMsgQ, sBankDisposeMsg, 0x10);
    sBankDisposeListSize = 0;
}

void JASChannel::receiveBankDisposeMsg() {
    OSMessage msg;
    for (sBankDisposeListSize = 0;
         OSReceiveMessage(&sBankDisposeMsgQ, &msg, OS_MESSAGE_NOBLOCK);
         sBankDisposeListSize++)
    {
        sBankDisposeList[sBankDisposeListSize] = msg;
    }
}

bool JASChannel::checkBankDispose() const {
    if (mBankDisposeID == NULL) {
        return false;
    }
    for (int i = 0; i < sBankDisposeListSize; i++) {
        if (mBankDisposeID == sBankDisposeList[i]) {
            return true;
        }
    }
    return false;
}
