#include "3d.h"
#include "harness/trace.h"
#include "resource.h"
#include "s3.h"
#include "s3sound.h"
#include <math.h>
#include <stdio.h>
#include <string.h>

tS3_vector3 gS3_listener_position_old;
tS3_vector3 gS3_listener_position_now;
tS3_vector3 gS3_listener_vel_now;
tS3_vector3 gS3_listener_left_now;

void* gS3_listener_pos_ptr;
void* gS3_listener_vel_ptr;
void* gS3_listener_left_ptr;

int gS3_listener_pos_is_brender;
int gS3_listener_vel_is_brender;
int gS3_listener_left_is_brender;

float dword_531D9C;
float flt_531D7C;
float flt_531D98;

void S3Set3DSoundEnvironment(float pInverse_world_scale, float a2, float a3) {
    float tmp;

    if (pInverse_world_scale == -1.0f) {
        pInverse_world_scale = 0.25f;
    }
    if (a2 == -1.0f) {
        a2 = 1.2f;
    }
    if (a3 == -1.0f) {
        a3 = 130000.0f;
    }
    dword_531D9C = pInverse_world_scale;
    tmp = sqrtf(a3 / a2);
    flt_531D7C = tmp;
    flt_531D98 = tmp * dword_531D9C;
    gS3_listener_position_now.x = 0.0;
    gS3_listener_position_now.y = 0.0;
    gS3_listener_position_now.z = 0.0;
    gS3_listener_vel_now.x = 0.0;
    gS3_listener_vel_now.y = 0.0;
    gS3_listener_vel_now.z = 0.0;
    gS3_listener_left_now.x = 0.0;
    gS3_listener_left_now.y = 0.0;
    gS3_listener_left_now.z = 0.0;
}

void S3CopyVector3(void* a1, void* a2, int pBrender_vector) {
    if (pBrender_vector) {
        S3CopyBrVector3((tS3_vector3*)a1, (br_vector3*)a2);
    } else {
        S3CopyS3Vector3((tS3_vector3*)a1, (tS3_vector3*)a2);
    }
}

void S3CopyBrVector3(tS3_vector3* a1, br_vector3* a2) {
    a1->x = a2->v[0];
    a1->y = a2->v[1];
    a1->z = a2->v[2];
}

void S3CopyS3Vector3(tS3_vector3* a1, tS3_vector3* a2) {
    *a1 = *a2;
}

void S3BindListenerPositionBRender(br_vector3* pos) {
    gS3_listener_pos_ptr = pos;
    gS3_listener_pos_is_brender = 1;
    S3CopyBrVector3(&gS3_listener_position_old, pos);
}

void S3BindListenerVelocityBRender(br_vector3* vel) {
    gS3_listener_vel_ptr = vel;
    gS3_listener_vel_is_brender = 1;
}

void S3BindListenerLeftBRender(br_vector3* left) {
    gS3_listener_left_ptr = left;
    gS3_listener_left_is_brender = 1;
}

void S3UpdateListenerVectors(void) {
    if (gS3_listener_pos_ptr) {
        S3CopyVector3(&gS3_listener_position_now, gS3_listener_pos_ptr, gS3_listener_pos_is_brender);
    }
    if (gS3_listener_vel_ptr) {
        S3CopyVector3(&gS3_listener_vel_now, gS3_listener_vel_ptr, gS3_listener_vel_is_brender);
    } else {
        gS3_listener_vel_now.x = (gS3_listener_position_now.x - gS3_listener_position_old.x) / 1000.0 * gS3_service_time_delta;
        gS3_listener_vel_now.y = (gS3_listener_position_now.y - gS3_listener_position_old.y) / 1000.0 * gS3_service_time_delta;
        gS3_listener_vel_now.z = (gS3_listener_position_now.z - gS3_listener_position_old.z) / 1000.0 * gS3_service_time_delta;
        gS3_listener_position_old = gS3_listener_position_now;
    }
    if (gS3_listener_left_ptr) {
        S3CopyVector3(&gS3_listener_left_now, gS3_listener_left_ptr, gS3_listener_left_is_brender);
    } else {
        gS3_listener_left_now.x = gS3_listener_position_now.x + 1.0;
        gS3_listener_left_now.y = gS3_listener_position_now.y;
        gS3_listener_left_now.z = gS3_listener_position_now.z;
    }
}

void S3ServiceAmbientSoundSources(void) {
    tS3_sound_source* s; // [esp+Ch] [ebp-4h]

    for (s = gS3_sound_sources; s; s = s->next) {
        if (s->ambient == 0) {
            continue;
        }
        if (s->period > 0) {
            s->time_since_last_played += gS3_service_time_delta;
        }
        if (s->channel && s->channel->tag != s->tag) {
            S3StopChannel(s->channel);
            s->channel = NULL;
            s->tag = 0;
        }

        if (s->channel != NULL) {
            continue;
        }
        if (s->time_since_last_played <= s->period || !s->period || s->tag) {
            if ((s->ambient_repeats == 0 || s->period == 0) && s->tag == 0) {
                if (s->volume > 0 && S3ServiceSoundSource(s) == 0) {
                    s->channel = NULL;
                    s->tag = 0;
                }
                s->time_since_last_played = 0;
            }
        } else {
            if (s->volume > 0 && S3ServiceSoundSource(s) == 0) {
                s->channel = NULL;
                s->tag = 0;
            }
            s->time_since_last_played = 0;
        }
    }
}

int S3UpdateSpatialSound(tS3_channel* chan) {
    int close_enough_to_play; // [esp+10h] [ebp-4h]

    if (chan->sound_source_ptr && chan->sound_source_ptr->ambient) {
        close_enough_to_play = S3Calculate3D(chan, 1);
    } else {
        close_enough_to_play = S3Calculate3D(chan, 0);
    }
    if (close_enough_to_play) {
        S3SyncSampleVolumeAndPan(chan);
        S3SyncSampleRate(chan);
    }
    return close_enough_to_play;
}

int S3BindAmbientSoundToOutlet(tS3_outlet* pOutlet, int pSound, tS3_sound_source* source, float pMax_distance, int pPeriod, int pRepeats, int pVolume, int pPitch, int pSpeed) {
    tS3_descriptor* desc; // [esp+Ch] [ebp-4h]

    if (!gS3_enabled) {
        return 0;
    }
    if (!source) {
        return eS3_error_nonexistant_source;
    }
    desc = S3GetDescriptorByID(pSound);
    if (!desc) {
        return eS3_error_bad_id;
    }
    if (desc->type != eS3_ST_sample) {
        return 0;
    }
    if ((!desc->sound_data || (desc->flags & 2) != 0) && !S3LoadSample(pSound)) {
        return eS3_error_load_sound;
    }
    if (pVolume > 255) {
        pVolume = 255;
    }
    if (pVolume < 0) {
        pVolume = 128;
    }
    if (pPitch < 0) {
        pPitch = 0x10000;
    }
    if (pSpeed < 0) {
        pSpeed = 0x10000;
    }
    source->bound_outlet = pOutlet;
    source->sound_id = pSound;
    source->volume = pVolume;
    source->max_distance_sq = pMax_distance;
    source->period = pPeriod;
    source->pitch = pPitch;
    source->speed = pSpeed;
    source->ambient = 1;

    source->ambient_repeats = pRepeats < 0 ? 0 : pRepeats;
    source->time_since_last_played = pPeriod;
    source->channel = NULL;
    source->tag = 0;
    return eS3_error_none;
}

void S3UpdateSoundSource(tS3_outlet* outlet, tS3_sound_tag tag, tS3_sound_source* src, float pMax_distance_squared, int pPeriod, tS3_repeats pAmbient_repeats, tS3_volume pVolume, int pPitch, tS3_speed pSpeed) {
    tS3_channel* chan;    // [esp+28h] [ebp-Ch]
    tS3_descriptor* desc; // [esp+2Ch] [ebp-8h]
    int is_sample;        // [esp+30h] [ebp-4h]

    is_sample = 0;
    if (!gS3_enabled || !src) {
        return;
    }
    chan = src->channel;
    if (tag != -1 && src->sound_id != tag) {
        desc = S3GetDescriptorByID(tag);
        if (desc == NULL) {
            return;
        }
        if (desc->type == eS3_ST_sample) {
            src->sound_id = tag;
            if ((desc->sound_data == NULL || (desc->flags & 2) != 0) && !S3LoadSample(tag)) {
                return;
            }
            is_sample = 1;
        }
    }
    if (pSpeed != -1) {
        if (pSpeed < 1) {
            pSpeed = 0x10000;
        }
        src->speed = pSpeed;
    }
    if (pPitch != -1) {
        if (pPitch < 1) {
            pPitch = 0x10000;
        }
        src->pitch = pPitch;
        if (chan && chan->descriptor && chan->descriptor->type == eS3_ST_sample) {
            chan->initial_pitch = S3IRandomBetweenLog(chan->descriptor->min_pitch, chan->descriptor->max_pitch, ((tS3_sample*)chan->descriptor->sound_data)->rate);
            chan->initial_pitch *= ldexpf(src->pitch, -16);
            chan->initial_pitch *= ldexpf(src->speed, -16);
        }
    }
    if (pAmbient_repeats != -1) {
        src->ambient_repeats = MAX(0, pAmbient_repeats);
        if (chan) {
            chan->repetitions = src->ambient_repeats;
        }
    }
    if (pMax_distance_squared != -1.0f) {
        src->max_distance_sq = pMax_distance_squared;
        if (chan) {
            chan->pMax_distance_squared = pMax_distance_squared;
        }
    }
    if (pPeriod != -1) {
        src->period = pPeriod;
    }
    if (pVolume != -1) {
        if (pVolume > 255) {
            pVolume = 255;
        }
        if (pVolume < 0) {
            pVolume = 128;
        }
        if (src->volume != pVolume) {
            src->volume = pVolume;
        }
        if (chan) {
            chan->initial_volume = pVolume;
        }
    }
    if (chan) {
        if (chan->sound_source_ptr != src) {
            S3StopChannel(chan);
        }
        if (!pVolume || is_sample) {
            chan->spatial_sound = 0;
            chan->needs_service = 1;
            S3StopChannel(chan);
            src->channel = 0;
            src->tag = 0;
        }
    }
}

void S3StopSoundSource(tS3_sound_source* src) {
    if (!gS3_enabled) {
        return;
    }
    if (src) {
        if (src->channel && src->tag && src->channel->tag == src->tag) {
            src->channel->termination_reason = eS3_tr_stopped;
            src->channel->spatial_sound = 0;
            S3StopChannel(src->channel);
            src->channel->sound_source_ptr = NULL;
        }
        src->ambient = 0;
    }
}

tS3_sound_tag S3ServiceSoundSource(tS3_sound_source* src) {
    tS3_channel* chan;    // [esp+30h] [ebp-10h]
    tS3_outlet* outlet;   // [esp+34h] [ebp-Ch]
    tS3_descriptor* desc; // [esp+3Ch] [ebp-4h]

    if (!src) {
        return 0;
    }
    if (!gS3_enabled || !src->ambient) {
        src->tag = 0;
        src->channel = 0;
        return 0;
    }
    outlet = src->bound_outlet;
    desc = S3GetDescriptorByID(src->sound_id);
    if (desc == NULL) {
        gS3_last_error = eS3_error_bad_id;
        return 0;
    }
    if (desc->type) {
        return 0;
    }
    memset(&gS3_channel_template, 0, sizeof(gS3_channel_template));
    gS3_channel_template.initial_volume = src->volume;
    gS3_channel_template.rate = S3IRandomBetweenLog(desc->min_pitch, desc->max_pitch, ((tS3_sample*)desc->sound_data)->rate);
    if (src->pitch < 0) {
        src->pitch = 0x10000;
    }
    if (src->speed < 0) {
        src->speed = 0x10000;
    }

    gS3_channel_template.rate = ldexp(src->pitch, -16) * gS3_channel_template.rate;
    if (!outlet->independent_pitch) {
        gS3_channel_template.rate = ldexp(src->speed, -16) * gS3_channel_template.rate;
    }
    gS3_channel_template.initial_pitch = gS3_channel_template.rate;
    gS3_channel_template.sound_source_ptr = src;
    if (!src->velocity_ptr) {
        S3CopyVector3(&gS3_channel_template.lastpos, src->position_ptr, src->brender_vector);
    }
    gS3_channel_template.pMax_distance_squared = src->max_distance_sq;
    if (S3Calculate3D(&gS3_channel_template, 1) == 0) {
        src->tag = 0;
        src->channel = NULL;
        return 0;
    }

    chan = S3AllocateChannel(outlet, desc->priority * (gS3_channel_template.right_volume + gS3_channel_template.left_volume + 1));
    if (chan == NULL) {
        gS3_last_error = eS3_error_channel_alloc;
        src->tag = 0;
        src->channel = NULL;
        return 0;
    }

    if ((desc->sound_data && (desc->flags & 2) == 0) || S3LoadSample(src->sound_id)) {
        chan->left_volume = gS3_channel_template.left_volume * chan->volume_multiplier;
        chan->right_volume = gS3_channel_template.right_volume * chan->volume_multiplier;
        chan->rate = gS3_channel_template.rate;
        chan->position.x = gS3_channel_template.position.x;
        chan->position.y = gS3_channel_template.position.y;
        chan->position.z = gS3_channel_template.position.z;
        chan->velocity.x = gS3_channel_template.velocity.x;
        chan->velocity.y = gS3_channel_template.velocity.y;
        chan->velocity.z = gS3_channel_template.velocity.z;
        chan->lastpos.x = gS3_channel_template.lastpos.x;
        chan->lastpos.y = gS3_channel_template.lastpos.y;
        chan->lastpos.z = gS3_channel_template.lastpos.z;
        chan->initial_volume = gS3_channel_template.initial_volume;
        chan->initial_pitch = gS3_channel_template.initial_pitch;
        chan->pMax_distance_squared = gS3_channel_template.pMax_distance_squared;
        chan->spatial_sound = 2;
        chan->sound_source_ptr = src;
        chan->descriptor = desc;
        chan->tag = S3GenerateTag(outlet);
        chan->repetitions = src->ambient_repeats;
        chan->needs_service = 0;
        chan->termination_reason = 0;
        S3ExecuteSampleFilterFuncs(chan);
        if (S3PlaySample(chan)) {
            src->tag = chan->tag;
            src->channel = chan;
            return chan->tag;
        } else {
            chan->needs_service = 1;
            gS3_last_error = eS3_error_start_sound;
            return 0;
        }
    } else {
        gS3_last_error = eS3_error_load_sound;
        chan->needs_service = 1;
        return 0;
    }
}

tS3_sound_tag S3StartSound3D(tS3_outlet* pOutlet, tS3_sound_id pSound, tS3_vector3* pInitial_position, tS3_vector3* pInitial_velocity, tS3_repeats pRepeats, tS3_volume pVolume, tS3_pitch pPitch, tS3_speed pSpeed) {
    tS3_channel* chan;    // [esp+30h] [ebp-Ch]
    tS3_descriptor* desc; // [esp+38h] [ebp-4h]

    if (!gS3_enabled) {
        return 0;
    }
    desc = S3GetDescriptorByID(pSound);
    if (!desc) {
        gS3_last_error = eS3_error_bad_id;
        return 0;
    }
    if (desc->type != eS3_ST_sample) {
        return 0;
    }

    if ((desc->sound_data == NULL || (desc->flags & 2) != 0) && S3LoadSample(pSound) == 0) {
        gS3_last_error = eS3_error_load_sound;
        return 0;
    }
    if (pVolume > 255) {
        pVolume = 255;
    }
    if (pVolume < 0) {
        pVolume = S3IRandomBetween(desc->min_volume, desc->max_volume, 128);
    }
    memset(&gS3_channel_template, 0, sizeof(gS3_channel_template));
    gS3_channel_template.volume_multiplier = 1.0;
    gS3_channel_template.sound_source_ptr = 0;
    gS3_channel_template.pMax_distance_squared = 150.0;
    gS3_channel_template.right_volume = pVolume;
    gS3_channel_template.left_volume = pVolume;
    gS3_channel_template.initial_volume = pVolume;
    gS3_channel_template.rate = S3IRandomBetweenLog(desc->min_pitch, desc->max_pitch, ((tS3_sample*)desc->sound_data)->rate);
    if (pPitch == -1) {
        pPitch = 0x10000;
    }
    if (pSpeed == -1) {
        pSpeed = 0x10000;
    }
    gS3_channel_template.rate = ldexpf(pPitch, -16) * gS3_channel_template.rate;
    if (!pOutlet->independent_pitch) {
        gS3_channel_template.rate = ldexpf(pSpeed, -16) * gS3_channel_template.rate;
    }
    gS3_channel_template.initial_pitch = gS3_channel_template.rate;
    gS3_channel_template.position = *pInitial_position;
    gS3_channel_template.velocity = *pInitial_velocity;

    if (S3Calculate3D(&gS3_channel_template, 0) == 0) {
        return 0;
    }
    chan = S3AllocateChannel(pOutlet, desc->priority * (gS3_channel_template.right_volume + gS3_channel_template.left_volume + 1));
    if (chan) {
        chan->left_volume = gS3_channel_template.left_volume * chan->volume_multiplier;
        chan->right_volume = gS3_channel_template.right_volume * chan->volume_multiplier;
        chan->rate = gS3_channel_template.rate;
        chan->spatial_sound = 1;
        chan->sound_source_ptr = 0;
        chan->descriptor = desc;
        chan->needs_service = 0;
        chan->termination_reason = 0;
        chan->position = gS3_channel_template.position;
        chan->lastpos = gS3_channel_template.lastpos;
        chan->velocity = gS3_channel_template.velocity;
        chan->repetitions = MAX(pRepeats, 0);
        chan->tag = S3GenerateTag(pOutlet);
        chan->initial_volume = gS3_channel_template.initial_volume;
        chan->initial_pitch = gS3_channel_template.initial_pitch;
        chan->pMax_distance_squared = gS3_channel_template.pMax_distance_squared;
        S3ExecuteSampleFilterFuncs(chan);
        if (S3PlaySample(chan) != 0) {
            return chan->tag;
        } else {
            chan->needs_service = 1;
            gS3_last_error = eS3_error_start_sound;
            return 0;
        }
    } else {
        gS3_last_error = eS3_error_channel_alloc;
        return 0;
    }
}

int S3Calculate3D(tS3_channel* chan, int pIs_ambient) {
    float attenuation;                  // [esp+2Ch] [ebp-1Ch]
    float doppler_shift;                // [esp+30h] [ebp-18h]
    float vol_multiplier;               // [esp+38h] [ebp-10h]
    tS3_sound_source* sound_source_ptr; // [esp+3Ch] [ebp-Ch]
    float dist_squared;                 // [esp+40h] [ebp-8h]
    float dist;                         // [esp+44h] [ebp-4h]

    sound_source_ptr = chan->sound_source_ptr;
    if (sound_source_ptr) {
        if (sound_source_ptr->position_ptr) {
            S3CopyVector3(&chan->position, sound_source_ptr->position_ptr, sound_source_ptr->brender_vector);
        }
        if (sound_source_ptr->velocity_ptr) {
            S3CopyVector3(&chan->velocity, sound_source_ptr->velocity_ptr, sound_source_ptr->brender_vector);
        } else {
            chan->velocity.x = (chan->position.x - chan->lastpos.x) / 1000.0f * gS3_service_time_delta;
            chan->velocity.y = (chan->position.y - chan->lastpos.y) / 1000.0f * gS3_service_time_delta;
            chan->velocity.z = (chan->position.z - chan->lastpos.z) / 1000.0f * gS3_service_time_delta;
            chan->lastpos = chan->position;
        }
    }
    dist_squared = (chan->position.z - gS3_listener_position_now.z) * (chan->position.z - gS3_listener_position_now.z)
        + (chan->position.x - gS3_listener_position_now.x) * (chan->position.x - gS3_listener_position_now.x)
        + (chan->position.y - gS3_listener_position_now.y) * (chan->position.y - gS3_listener_position_now.y);
    if (dist_squared < 0) {
        dist_squared = dist_squared * -1.0;
    }

    if (chan->pMax_distance_squared < dist_squared) {
        return 0;
    }
    if (dist_squared == 0.0f) {
        dist = 0.0f;
    } else {
        dist = sqrtf(dist_squared);
    }
    if (pIs_ambient) {
        doppler_shift = 1.0f - ((chan->position.z - gS3_listener_position_now.z) * (chan->velocity.z - gS3_listener_vel_now.z) + (chan->velocity.y - gS3_listener_vel_now.y) * (chan->position.y - gS3_listener_position_now.y) + (chan->position.x - gS3_listener_position_now.x) * (chan->velocity.x - gS3_listener_vel_now.x)) / dist / flt_531D98;
        if (doppler_shift > 2.0f) {
            doppler_shift = 2.0f;
        } else if (doppler_shift < 0.5f) {
            doppler_shift = 0.5;
        }
        chan->rate = chan->initial_pitch * doppler_shift;
    } else {
        chan->rate = chan->initial_pitch;
    }

    vol_multiplier = 1.0f / (dist / 6.0f + 1.0f);
    if (!gS3_inside_cockpit) {
        vol_multiplier = vol_multiplier * 1.3f;
    }
    attenuation = (chan->position.z - gS3_listener_position_now.z) * gS3_listener_left_now.z
        + (chan->position.y - gS3_listener_position_now.y) * gS3_listener_left_now.y
        + (chan->position.x - gS3_listener_position_now.x) * gS3_listener_left_now.x;
    if (attenuation < -1.0f) {
        attenuation -= ceil(attenuation);
    }
    if (attenuation > 1.0f) {
        attenuation -= floor(attenuation);
    }
    chan->left_volume = (attenuation + 1.0f) / 2.0f * ((double)chan->initial_volume * vol_multiplier) * chan->volume_multiplier;
    if (chan->left_volume < 0) {
        chan->left_volume = 0;
    }
    chan->right_volume = (1.0f - attenuation) / 2.0f * ((double)chan->initial_volume * vol_multiplier) * chan->volume_multiplier;
    if (chan->right_volume < 0) {
        chan->right_volume = 0;
    }
    if (chan->left_volume > 255) {
        chan->left_volume = 255;
    }
    if (chan->right_volume > 255) {
        chan->right_volume = 255;
    }
    return 1;
}
