/* AY/YM emulator implementation */

#include "ayemu_8912.h"

#include <inttypes.h>
#include <libaudcore/runtime.h>

const char * ayemu_err;

/* To check whether ayemu_t structure is initialized */
const int MAGIC1 = 0xcdef;

enum
{
    /* Max amplitude value for a stereo signal to avoid clipping
     * during any later sample-rate conversion (SSRC) */
    AYEMU_MAX_AMP = 24575,
    AYEMU_DEFAULT_CHIP_FREQ = 1773400
};

/* Sound chip volume envelopes (will be calculated by gen_env()) */
static int bEnvGenInit = 0;
static int Envelope[16][128];

/* AY volume table (c) by V_Soft and Lion 17 */
static int Lion17_AY_table[16] = {
    0,     513,   828,   1239,  1923,  3238,  4926,  9110,
    10344, 17876, 24682, 30442, 38844, 47270, 56402, 65535,
};

/* YM volume table (c) by V_Soft and Lion 17 */
static int Lion17_YM_table[32] = {
    0,     0,     190,   286,   375,   470,   560,   664,
    866,   1130,  1515,  1803,  2253,  2848,  3351,  3862,
    4844,  6058,  7290,  8559,  10474, 12878, 15297, 17787,
    21500, 26172, 30866, 35676, 42664, 50986, 58842, 65535,
};

/* AY volume table (c) by Hacker KAY */
static int KAY_AY_table[16] = {
    0,     836,   1212,  1773,  2619,  3875,  5397,  8823,
    10392, 16706, 23339, 29292, 36969, 46421, 55195, 65535,
};

/* YM volume table (c) by Hacker KAY */
static int KAY_YM_table[32] = {
    0,     0,     248,   450,   670,   826,   1010,  1239,
    1552,  1919,  2314,  2626,  3131,  3778,  4407,  5031,
    5968,  7161,  8415,  9622,  11421, 13689, 15957, 18280,
    21759, 26148, 30523, 34879, 41434, 49404, 57492, 65535,
};

/* Default equalizer (layout) settings for AY and YM, 7 stereo types */
static const int default_layout[2][7][6] = {
    /* A_l, A_r, B_l, B_r, C_l, C_r */

    /* for AY */
    {
        {100, 100, 100, 100, 100, 100}, // _MONO
        {100, 33, 70, 70, 33, 100},     // _ABC
        {100, 33, 33, 100, 70, 70},     // _ACB
        {70, 70, 100, 33, 33, 100},     // _BAC
        {33, 100, 100, 33, 70, 70},     // _BCA
        {70, 70, 33, 100, 100, 33},     // _CAB
        {33, 100, 70, 70, 100, 33},     // _CBA
    },
    /* for YM */
    {
        {100, 100, 100, 100, 100, 100}, // _MONO
        {100, 5, 70, 70, 5, 100},       // _ABC
        {100, 5, 5, 100, 70, 70},       // _ACB
        {70, 70, 100, 5, 5, 100},       // _BAC
        {5, 100, 100, 5, 70, 70},       // _BCA
        {70, 70, 5, 100, 100, 5},       // _CAB
        {5, 100, 70, 70, 100, 5}        // _CBA
    }
};

static int check_magic(ayemu_ay_t * ay)
{
    if (ay->magic == MAGIC1)
        return 1;

    AUDERR("Passed pointer %p to uninitialized ayemu_ay_t structure\n", (void *)ay);
    return 0;
}

/* Generate the chip hardware envelope tables.
 * Will be executed once before first use. */
static void gen_env()
{
    int env;
    int pos;
    int hold;
    int dir;
    int vol;

    for (env = 0; env < 16; env++)
    {
        hold = 0;
        dir = (env & 4) ? 1 : -1;
        vol = (env & 4) ? -1 : 32;

        for (pos = 0; pos < 128; pos++)
        {
            if (!hold)
            {
                vol += dir;
                if (vol < 0 || vol >= 32)
                {
                    if (env & 8)
                    {
                        if (env & 2)
                            dir = -dir;
                        vol = (dir > 0) ? 0 : 31;
                        if (env & 1)
                        {
                            hold = 1;
                            vol = (dir > 0) ? 31 : 0;
                        }
                    }
                    else
                    {
                        vol = 0;
                        hold = 1;
                    }
                }
            }

            Envelope[env][pos] = vol;
        }
    }

    bEnvGenInit = 1;
}

/**
 * Initialize the ayemu_ay_t structure.
 * \arg \c ay - pointer to ayemu_ay_t structure.
 */
void ayemu_init(ayemu_ay_t * ay)
{
    ay->default_chip_flag = 1;
    ay->ChipFreq = AYEMU_DEFAULT_CHIP_FREQ;
    ay->default_stereo_flag = 1;
    ay->default_sound_format_flag = 1;
    ay->dirty = 1;
    ay->magic = MAGIC1;

    ayemu_reset(ay);
}

/**
 * Reset AY/YM chip.
 * \arg \c ay - pointer to ayemu_ay_t structure.
 */
void ayemu_reset(ayemu_ay_t * ay)
{
    if (!check_magic(ay))
        return;

    ay->cnt_a = ay->cnt_b = ay->cnt_c = ay->cnt_n = ay->cnt_e = 0;
    ay->bit_a = ay->bit_b = ay->bit_c = ay->bit_n = 0;
    ay->env_pos = ay->EnvNum = 0;
    ay->Cur_Seed = 0xffff;
}

static void set_table_ay(ayemu_ay_t * ay, int tbl[16])
{
    for (int n = 0; n < 32; n++)
        ay->table[n] = tbl[n / 2];

    ay->type = AYEMU_AY;
}

static void set_table_ym(ayemu_ay_t * ay, int tbl[32])
{
    for (int n = 0; n < 32; n++)
        ay->table[n] = tbl[n];

    ay->type = AYEMU_YM;
}

/**
 * Set chip type.
 * \arg \c ay - pointer to ayemu_t structure.
 * \arg \c type - sound chip type.
 * \arg \c custom_table - nullptr or pointer to custom table.
 * \retval 1 on success, 0 if an error occurred.
 */
int ayemu_set_chip_type(ayemu_ay_t * ay, ayemu_chip_t type, int * custom_table)
{
    if (!check_magic(ay))
        return 0;

    if (!(type == AYEMU_AY_CUSTOM || type == AYEMU_YM_CUSTOM) && custom_table != nullptr)
    {
        ayemu_err = "For non-custom chip type 'custom_table' param must be nullptr";
        return 0;
    }

    switch (type)
    {
    case AYEMU_AY:
    case AYEMU_AY_LION17:
        set_table_ay(ay, Lion17_AY_table);
        break;
    case AYEMU_YM:
    case AYEMU_YM_LION17:
        set_table_ym(ay, Lion17_YM_table);
        break;
    case AYEMU_AY_KAY:
        set_table_ay(ay, KAY_AY_table);
        break;
    case AYEMU_YM_KAY:
        set_table_ym(ay, KAY_YM_table);
        break;
    case AYEMU_AY_CUSTOM:
        set_table_ay(ay, custom_table);
        break;
    case AYEMU_YM_CUSTOM:
        set_table_ym(ay, custom_table);
        break;
    default:
        ayemu_err = "Incorrect chip type";
        return 0;
    }

    ay->default_chip_flag = 0;
    ay->dirty = 1;
    return 1;
}

/**
 * Set chip frequency.
 * \arg \c ay - pointer to ayemu_t structure.
 * \arg \c chip_freq - frequency of the chip.
 */
void ayemu_set_chip_freq(ayemu_ay_t * ay, int32_t chip_freq)
{
    if (!check_magic(ay))
        return;

    ay->ChipFreq = chip_freq;
    ay->dirty = 1;
}

/**
 * Set output sound format.
 * \arg \c ay - pointer to ayemu_t structure.
 * \arg \c freq - sound frequency (44100 for example).
 * \arg \c chans - number of channels (1-mono, 2-stereo).
 * \arg \c bits - bit count (8 or 16 are supported).
 * \retval 1 on success, 0 if an error occurred.
 */
int ayemu_set_sound_format(ayemu_ay_t * ay, int freq, int chans, int bits)
{
    if (!check_magic(ay))
        return 0;

    if (!(bits == 8 || bits == 16))
    {
        ayemu_err = "Incorrect bits value";
        return 0;
    }
    else if (!(chans == 1 || chans == 2))
    {
        ayemu_err = "Incorrect number of channels";
        return 0;
    }
    else if (freq < 50)
    {
        ayemu_err = "Incorrect output sound freq";
        return 0;
    }
    else
    {
        ay->sndfmt.freq = freq;
        ay->sndfmt.channels = chans;
        ay->sndfmt.bpc = bits;
    }

    ay->default_sound_format_flag = 0;
    ay->dirty = 1;
    return 1;
}

/**
 * Set amplitude factor for each channel (A, B and C, tone and noise).
 * Factor's value must be from (-100) to (+100).
 * \arg \c ay - pointer to ayemu_t structure.
 * \arg \c stereo_type - type of stereo.
 * \arg \c custom_eq - nullptr or pointer to custom table of mixer layout.
 * \retval 1 on success, 0 if an error occurred.
 */
int ayemu_set_stereo(ayemu_ay_t * ay, ayemu_stereo_t stereo_type, int * custom_eq)
{
    if (!check_magic(ay))
        return 0;

    if (stereo_type != AYEMU_STEREO_CUSTOM && custom_eq != nullptr)
    {
        ayemu_err = "Stereo type not custom, 'custom_eq' parameter must be nullptr";
        return 0;
    }

    int chip = (ay->type == AYEMU_AY) ? 0 : 1;

    switch (stereo_type)
    {
    case AYEMU_MONO:
    case AYEMU_ABC:
    case AYEMU_ACB:
    case AYEMU_BAC:
    case AYEMU_BCA:
    case AYEMU_CAB:
    case AYEMU_CBA:
        for (int i = 0; i < 6; i++)
            ay->eq[i] = default_layout[chip][stereo_type][i];
        break;
    case AYEMU_STEREO_CUSTOM:
        for (int i = 0; i < 6; i++)
            ay->eq[i] = custom_eq[i];
        break;
    default:
        ayemu_err = "Incorrect stereo type";
        return 0;
    }

    ay->default_stereo_flag = 0;
    ay->dirty = 1;
    return 1;
}

#define WARN_IF_REGISTER_GREATER_THAN(r, m)                                    \
    if (*(regs + r) > m)                                                       \
        AUDWARN("Possible bad register data- R%d > %d\n", r, m)

/**
 * Assign values for AY registers.
 * \arg \c ay - pointer to ayemu_t structure.
 * \arg \c regs - the register values to assign (must be of length char[14]).
 */
void ayemu_set_regs(ayemu_ay_t * ay, unsigned char * regs)
{
    if (!check_magic(ay))
        return;

    WARN_IF_REGISTER_GREATER_THAN(1, 15);
    WARN_IF_REGISTER_GREATER_THAN(3, 15);
    WARN_IF_REGISTER_GREATER_THAN(5, 15);
    WARN_IF_REGISTER_GREATER_THAN(8, 31);
    WARN_IF_REGISTER_GREATER_THAN(9, 31);
    WARN_IF_REGISTER_GREATER_THAN(10, 31);

    ay->regs.tone_a = regs[0] + ((regs[1] & 0x0f) << 8);
    ay->regs.tone_b = regs[2] + ((regs[3] & 0x0f) << 8);
    ay->regs.tone_c = regs[4] + ((regs[5] & 0x0f) << 8);

    ay->regs.noise = regs[6] & 0x1f;

    ay->regs.R7_tone_a = !(regs[7] & 0x01);
    ay->regs.R7_tone_b = !(regs[7] & 0x02);
    ay->regs.R7_tone_c = !(regs[7] & 0x04);

    ay->regs.R7_noise_a = !(regs[7] & 0x08);
    ay->regs.R7_noise_b = !(regs[7] & 0x10);
    ay->regs.R7_noise_c = !(regs[7] & 0x20);

    ay->regs.vol_a = regs[8] & 0x0f;
    ay->regs.vol_b = regs[9] & 0x0f;
    ay->regs.vol_c = regs[10] & 0x0f;
    ay->regs.env_a = regs[8] & 0x10;
    ay->regs.env_b = regs[9] & 0x10;
    ay->regs.env_c = regs[10] & 0x10;
    ay->regs.env_freq = regs[11] + (regs[12] << 8);

    if (regs[13] != 0xff)
    {
        /* R13 = 255 means continue current envelope */
        ay->regs.env_style = regs[13] & 0x0f;
        ay->env_pos = ay->cnt_e = 0;
    }
}

static void prepare_generation(ayemu_ay_t * ay)
{
    int vol, max_l, max_r;

    if (!ay->dirty)
        return;

    if (!bEnvGenInit)
        gen_env();

    if (ay->default_chip_flag)
        ayemu_set_chip_type(ay, AYEMU_AY, nullptr);

    if (ay->default_stereo_flag)
        ayemu_set_stereo(ay, AYEMU_ABC, nullptr);

    if (ay->default_sound_format_flag)
        ayemu_set_sound_format(ay, 44100, 2, 16);

    ay->ChipTacts_per_outcount = ay->ChipFreq / ay->sndfmt.freq / 8;

    {
        /* GenVols */
        int n, m;
        int vol;
        for (n = 0; n < 32; n++)
        {
            vol = ay->table[n];
            for (m = 0; m < 6; m++)
                ay->vols[m][n] = (int)(((double)vol * ay->eq[m]) / 100);
        }
    }

    /* ������ ����������������� ����
     * �������� ���vols [x][31] ���������������
     * TODO: �������� � �� ;-)
     */
    max_l = ay->vols[0][31] + ay->vols[2][31] + ay->vols[3][31];
    max_r = ay->vols[1][31] + ay->vols[3][31] + ay->vols[5][31];
    vol = (max_l > max_r) ? max_l : max_r; /* =157283 on all defaults */
    ay->Amp_Global = ay->ChipTacts_per_outcount * vol / AYEMU_MAX_AMP;
    ay->dirty = 0;
}

/**
 * Generate sound: Fill sound buffer with current register data.
 * \arg \c ay - pointer to ayemu_t structure.
 * \arg \c buf - pointer to sound buffer.
 * \arg \c sound_bufsize - size of buffer.
 * \return pointer to next data in output sound buffer.
 */
void * ayemu_gen_sound(ayemu_ay_t * ay, void * buf, size_t sound_bufsize)
{
    int mix_l, mix_r;
    int tmpvol;
    int m;
    int snd_numcount;
    unsigned char * sound_buf = (unsigned char *)buf;

    if (!check_magic(ay))
        return 0;

    prepare_generation(ay);

    snd_numcount = sound_bufsize / (ay->sndfmt.channels * (ay->sndfmt.bpc >> 3));

    while (snd_numcount-- > 0)
    {
        mix_l = mix_r = 0;

        for (m = 0; m < ay->ChipTacts_per_outcount; m++)
        {
            if (++ay->cnt_a >= ay->regs.tone_a)
            {
                ay->cnt_a = 0;
                ay->bit_a = !ay->bit_a;
            }

            if (++ay->cnt_b >= ay->regs.tone_b)
            {
                ay->cnt_b = 0;
                ay->bit_b = !ay->bit_b;
            }

            if (++ay->cnt_c >= ay->regs.tone_c)
            {
                ay->cnt_c = 0;
                ay->bit_c = !ay->bit_c;
            }

            /* GenNoise (c) Hacker KAY & Sergey Bulba */
            if (++ay->cnt_n >= (ay->regs.noise * 2))
            {
                ay->cnt_n = 0;
                ay->Cur_Seed =
                    (ay->Cur_Seed * 2 + 1) ^
                    (((ay->Cur_Seed >> 16) ^ (ay->Cur_Seed >> 13)) & 1);
                ay->bit_n = ((ay->Cur_Seed >> 16) & 1);
            }

            if (++ay->cnt_e >= ay->regs.env_freq)
            {
                ay->cnt_e = 0;
                if (++ay->env_pos > 127)
                    ay->env_pos = 64;
            }

#define ENVVOL Envelope[ay->regs.env_style][ay->env_pos]

            if ((ay->bit_a | !ay->regs.R7_tone_a) &
                (ay->bit_n | !ay->regs.R7_noise_a))
            {
                tmpvol = (ay->regs.env_a) ? ENVVOL : ay->regs.vol_a * 2 + 1;
                mix_l += ay->vols[0][tmpvol];
                mix_r += ay->vols[1][tmpvol];
            }

            if ((ay->bit_b | !ay->regs.R7_tone_b) &
                (ay->bit_n | !ay->regs.R7_noise_b))
            {
                tmpvol = (ay->regs.env_b) ? ENVVOL : ay->regs.vol_b * 2 + 1;
                mix_l += ay->vols[2][tmpvol];
                mix_r += ay->vols[3][tmpvol];
            }

            if ((ay->bit_c | !ay->regs.R7_tone_c) &
                (ay->bit_n | !ay->regs.R7_noise_c))
            {
                tmpvol = (ay->regs.env_c) ? ENVVOL : ay->regs.vol_c * 2 + 1;
                mix_l += ay->vols[4][tmpvol];
                mix_r += ay->vols[5][tmpvol];
            }
        }

        mix_l /= ay->Amp_Global;
        mix_r /= ay->Amp_Global;

        if (ay->sndfmt.bpc == 8)
        {
            /* 8 bit sound */
            mix_l = (mix_l >> 8) | 128;
            mix_r = (mix_r >> 8) | 128;
            *sound_buf++ = mix_l;

            if (ay->sndfmt.channels != 1)
                *sound_buf++ = mix_r;
        }
        else
        {
            /* 16 bit sound */
            *sound_buf++ = mix_l & 0x00ff;
            *sound_buf++ = (mix_l >> 8);

            if (ay->sndfmt.channels != 1)
            {
                *sound_buf++ = mix_r & 0x00ff;
                *sound_buf++ = (mix_r >> 8);
            }
        }
    }

    return sound_buf;
}
