/*
 * es8388.c — ES8388 codec driver (IDF 5.x, legacy I2C driver API).
 *
 * Register map reference: ES8388 datasheet rev 1.6 (Everest Semiconductor).
 * Init sequence derived from:
 *   - Espressif esp-adf es8388.c (Apache 2.0)
 *   - schreibfaul1/ESP32-audioI2S ES8388 init (MIT)
 *   - AI-Thinker SDK AudioKit driver
 *
 * The sequence configures the ES8388 for:
 *   - Master clock: MCLK from ESP32 GPIO0 at 12.288 MHz (or 256*Fs at 16kHz)
 *   - I2S format: I2S Philips, 16-bit, stereo
 *   - DAC: LOUT1/ROUT1 → headphone amp (GPIO21 PA_ENABLE must be HIGH)
 *   - ADC: LINPUT1/RINPUT1 mic (differential), gain 24 dB
 *   - Sample rate: 16 kHz (MCLKDIV = 256Fs)
 */

#include "es8388.h"
#include "board_config.h"

#include <string.h>

#include "driver/i2c.h"
#include "driver/gpio.h"
#include "esp_log.h"

#define TAG "es8388"

/* ── ES8388 register addresses ──────────────────────────────────────────── */
#define ES8388_CHIP_CTL1        0x00  /* CHIP_CTL1 */
#define ES8388_CHIP_CTL2        0x01  /* CHIP_CTL2 */
#define ES8388_CHIP_POWER       0x02  /* CHIP_POWER (ADCPD, DACPD, etc.) */
#define ES8388_ADC_POWER        0x03  /* ADCPOWER */
#define ES8388_DAC_POWER        0x04  /* DACPOWER */
#define ES8388_CHIP_LP          0x05  /* CHIP_LP */
#define ES8388_CHIP_CTL3        0x06  /* CHIP_CTL3 */
#define ES8388_ADC_CTL1         0x09  /* ADCCONTROL1 — PGA gain */
#define ES8388_ADC_CTL2         0x0A  /* ADCCONTROL2 — input select (LINSEL/RINSEL) */
#define ES8388_ADC_CTL3         0x0B  /* ADCCONTROL3 — DS/DS filter select */
#define ES8388_ADC_CTL4         0x0C  /* ADCCONTROL4 — I2S format / word length */
#define ES8388_ADC_CTL5         0x0D  /* ADCCONTROL5 — MCLK divider (RATIO=256) */
#define ES8388_ADC_CTL7         0x0F  /* ADCCONTROL7 — HPF enable/config */
#define ES8388_ADC_CTL8         0x10  /* ADCCONTROL8 — ADC L volume */
#define ES8388_ADC_CTL9         0x11  /* ADCCONTROL9 — ADC R volume */
#define ES8388_DAC_CTL1         0x17  /* DACCONTROL1 — I2S word len / format */
#define ES8388_DAC_CTL2         0x18  /* DACCONTROL2 — MCLK divider */
#define ES8388_DAC_CTL3         0x19  /* DACCONTROL3 — mute */
#define ES8388_DAC_CTL4         0x1A  /* DACCONTROL4 — LDACVOL */
#define ES8388_DAC_CTL5         0x1B  /* DACCONTROL5 — RDACVOL */
#define ES8388_DAC_CTL16        0x26  /* DACCONTROL16 — L/R mixer config */
#define ES8388_DAC_CTL17        0x27  /* DACCONTROL17 — L mixer gain */
#define ES8388_DAC_CTL20        0x2A  /* DACCONTROL20 — R mixer gain */
#define ES8388_DAC_CTL21        0x2B  /* DACCONTROL21 — ADC/DAC LRCK sync (bit7=1 for full-duplex) */
#define ES8388_DAC_CTL22        0x2C  /* DACCONTROL22 — (unused in this config) */
#define ES8388_DAC_CTL23        0x2D  /* DACCONTROL23 — (unused in this config) */
#define ES8388_DAC_CTL24        0x2E  /* DACCONTROL24 — LOUT1VOL (OUT1 left volume) */
#define ES8388_DAC_CTL25        0x2F  /* DACCONTROL25 — ROUT1VOL (OUT1 right volume) */
#define ES8388_DAC_CTL26        0x30  /* DACCONTROL26 — LOUT2VOL (OUT2 left volume) */

/* Volume register: 0x00=0dB (max), 0x21=-33dB step per 1.5dB, 0x24=mute. */
#define ES8388_VOL_MAX  0x00
#define ES8388_VOL_0DB  0x00
#define ES8388_VOL_MUTE 0x24

/* ── I2C helpers ─────────────────────────────────────────────────────────── */

static esp_err_t i2c_write_reg(uint8_t reg, uint8_t value)
{
    uint8_t buf[2] = { reg, value };
    esp_err_t ret = i2c_master_write_to_device(PLIP_I2C_PORT,
                                                PLIP_ES8388_ADDR,
                                                buf, sizeof(buf),
                                                pdMS_TO_TICKS(100));
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "write reg 0x%02X=0x%02X failed: %s",
                 reg, value, esp_err_to_name(ret));
    }
    return ret;
}

esp_err_t es8388_read_reg(uint8_t reg, uint8_t *value)
{
    return i2c_master_write_read_device(PLIP_I2C_PORT,
                                        PLIP_ES8388_ADDR,
                                        &reg, 1,
                                        value, 1,
                                        pdMS_TO_TICKS(100));
}

esp_err_t es8388_write_reg(uint8_t reg, uint8_t value)
{
    return i2c_write_reg(reg, value);
}

/* ── Public API ──────────────────────────────────────────────────────────── */

esp_err_t es8388_init(void)
{
    /* Initialise I2C master on the A1S bus. */
    i2c_config_t conf = {
        .mode             = I2C_MODE_MASTER,
        .sda_io_num       = PLIP_I2C_SDA,
        .scl_io_num       = PLIP_I2C_SCL,
        .sda_pullup_en    = GPIO_PULLUP_ENABLE,
        .scl_pullup_en    = GPIO_PULLUP_ENABLE,
        .master.clk_speed = PLIP_I2C_FREQ_HZ,
    };
    esp_err_t ret = i2c_param_config(PLIP_I2C_PORT, &conf);
    if (ret != ESP_OK) return ret;
    ret = i2c_driver_install(PLIP_I2C_PORT, conf.mode, 0, 0, 0);
    if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
        /* ESP_ERR_INVALID_STATE means the driver is already installed — ok. */
        ESP_LOGE(TAG, "i2c_driver_install: %s", esp_err_to_name(ret));
        return ret;
    }

    /* Quick device presence check. */
    uint8_t chip_id = 0;
    ret = es8388_read_reg(ES8388_CHIP_CTL1, &chip_id);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "ES8388 not found on I2C bus (addr=0x%02X): %s",
                 PLIP_ES8388_ADDR, esp_err_to_name(ret));
        return ret;
    }
    ESP_LOGI(TAG, "ES8388 detected: CHIP_CTL1=0x%02X", chip_id);

    /* ── Full power-up + init sequence ─────────────────────────────────── */

    /* 1. Reset. */
    if (i2c_write_reg(ES8388_CHIP_CTL1, 0x80) != ESP_OK) return ESP_FAIL;
    vTaskDelay(pdMS_TO_TICKS(20));
    if (i2c_write_reg(ES8388_CHIP_CTL1, 0x00) != ESP_OK) return ESP_FAIL;

    /* 2. Power down all blocks first (prevents pop on line-up). */
    if (i2c_write_reg(ES8388_CHIP_POWER, 0xFF) != ESP_OK) return ESP_FAIL;

    /* 3. CONTROL1: WORK_MODE = record+playback (0x10), VMIDSEL=10 (0x02).
     * 0x12 = 0b00010010 — enables both ADC and DAC paths.
     * Previous value 0x05 only enabled playback (WORK_MODE=00). */
    if (i2c_write_reg(ES8388_CHIP_CTL1, 0x12) != ESP_OK) return ESP_FAIL;

    /* 3b. CONTROL2 (0x01): VROI=0, LPVrefBuf=0, normal operation. */
    if (i2c_write_reg(ES8388_CHIP_CTL2, 0x50) != ESP_OK) return ESP_FAIL;

    /* 3c. Internal DLL stabilisation for LOW sample rates (16 kHz). UNDOCUMENTED
     * registers 0x35/0x37/0x39 — the proven A252 driver (hardware/projects/
     * slic-phone Es8388Driver.cpp) sets these to "disable internal DLL for low
     * sample-rate stability". WITHOUT them the ADC clock domain is unstable at
     * 16 kHz and the ADC outputs a frozen DC value (capture = flat, no AC) even
     * though analog signal is present on the LIN pin — exactly our symptom. The
     * DAC tolerates it, which is why playback worked but capture didn't. They
     * must be set here in the boot sequence (a live poke can't re-lock the DLL). */
    if (i2c_write_reg(0x35, 0xA0) != ESP_OK) return ESP_FAIL;
    if (i2c_write_reg(0x37, 0xD0) != ESP_OK) return ESP_FAIL;
    if (i2c_write_reg(0x39, 0xD0) != ESP_OK) return ESP_FAIL;

    /* 4. Clock: MCLK divider = 256*Fs, DAC SRC = MCLK. */
    if (i2c_write_reg(0x08, 0x00) != ESP_OK) return ESP_FAIL; /* MASTERMODE = slave */

    /* 5. ADC power: power down to allow clean register writes. */
    if (i2c_write_reg(ES8388_ADC_POWER, 0xFF) != ESP_OK) return ESP_FAIL;

    /* 6. ADC config (follows Espressif esp-codec-dev reference sequence):
     * - ADCCONTROL1 (0x09) = 0xBB: MIC PGA +24dB L and R
     * - ADCCONTROL2 (0x0A) = 0x50: LINSEL=10 LIN2/RIN2 (telephone handset mic)
     *   Value 0x00 = LIN1 differential, 0x50 = LIN2 single-ended (A1S combiné)
     * - ADCCONTROL3 (0x0B) = 0x02: DS filter select (required by reference)
     * - ADCCONTROL4 (0x0C) = 0x0C: I2S Philips 16-bit word length
     * - ADCCONTROL5 (0x0D) = 0x02: ADCFsMode SINGLE SPEED RATIO=256 (16kHz@MCLK 4.096MHz)
     * - ADCCONTROL8/9 (0x10/0x11) = 0x00: ADC digital volume 0dB */
    if (i2c_write_reg(ES8388_ADC_CTL1, 0x88) != ESP_OK) return ESP_FAIL; /* MIC PGA +24dB L+R — the SLIC handset mic is QUIET (~2-5% FS at +12dB); +24dB lifts speech above the capture VAD onset. The old "+24dB too hot" note was an artifact of the broken ADC (pre-DLL-fix); measured clean at ~5% peak now. */
    /* ADCCONTROL2 (0x0A): input select. The K50835F SLIC handset transmit audio is
     * wired to LIN2/RIN2 on this bench — PROVEN: speech captured (ACrms 196, crest 10.3)
     * on 0x50, vs DC-only floating offset on 0x00 (LIN1). */
    if (i2c_write_reg(ES8388_ADC_CTL2, 0x50) != ESP_OK) return ESP_FAIL; /* LIN2/RIN2 — SLIC handset mic */
    if (i2c_write_reg(ES8388_ADC_CTL3, 0x02) != ESP_OK) return ESP_FAIL; /* DS filter sel */
    if (i2c_write_reg(ES8388_ADC_CTL4, 0x0C) != ESP_OK) return ESP_FAIL; /* I2S 16-bit */
    if (i2c_write_reg(ES8388_ADC_CTL5, 0x02) != ESP_OK) return ESP_FAIL; /* RATIO=256 */
    if (i2c_write_reg(0x0E, 0x00) != ESP_OK) return ESP_FAIL; /* ADCCONTROL6: clear ADCSMUTE bit5 (reset default 0x30 = ADC output muted) */
    if (i2c_write_reg(ES8388_ADC_CTL8, 0x00) != ESP_OK) return ESP_FAIL; /* ADC vol L 0dB */
    if (i2c_write_reg(ES8388_ADC_CTL9, 0x00) != ESP_OK) return ESP_FAIL; /* ADC vol R 0dB */

    /* 8. DAC I2S: 16-bit I2S Philips, MCLK/256, no softmute. */
    if (i2c_write_reg(ES8388_DAC_CTL1, 0x18) != ESP_OK) return ESP_FAIL;
    if (i2c_write_reg(ES8388_DAC_CTL2, 0x02) != ESP_OK) return ESP_FAIL; /* DACLRCKDIV=256 */
    if (i2c_write_reg(ES8388_DAC_CTL3, 0x00) != ESP_OK) return ESP_FAIL; /* mute off */

    /* 9. DAC volume: 0 dB on both channels. */
    if (i2c_write_reg(ES8388_DAC_CTL4, ES8388_VOL_0DB) != ESP_OK) return ESP_FAIL;
    if (i2c_write_reg(ES8388_DAC_CTL5, ES8388_VOL_0DB) != ESP_OK) return ESP_FAIL;

    /* 10. Mixer: L→LOUT, R→ROUT (straight through, no cross-mix). */
    if (i2c_write_reg(ES8388_DAC_CTL16, 0x1B) != ESP_OK) return ESP_FAIL;
    if (i2c_write_reg(ES8388_DAC_CTL17, 0x90) != ESP_OK) return ESP_FAIL;
    if (i2c_write_reg(ES8388_DAC_CTL20, 0x90) != ESP_OK) return ESP_FAIL;

    /* 11. DACCONTROL21 (0x2B): ADC+DAC LRCK sync.
     * Espressif reference es8388_start():
     *   0xC0 = LINE mode (analog bypass, not ADC digital path)
     *   0x80 = DAC+ADC digital record+playback mode (bit7 only)
     * We want digital ADC recording + DAC playback → use 0x80. */
    if (i2c_write_reg(ES8388_DAC_CTL21, 0x80) != ESP_OK) return ESP_FAIL;

    /* 11b. Restart internal state machine (required after DACCONTROL21 change).
     * Without this pulse, the ADC clock domain may not synchronise properly.
     * Espressif reference sequence: CHIPPOWER=0xF0 then 0x00 to restart FSM. */
    if (i2c_write_reg(ES8388_CHIP_POWER, 0xF0) != ESP_OK) return ESP_FAIL;
    vTaskDelay(pdMS_TO_TICKS(5));
    if (i2c_write_reg(ES8388_CHIP_POWER, 0x00) != ESP_OK) return ESP_FAIL;
    vTaskDelay(pdMS_TO_TICKS(10));

    /* DACCONTROL23 (0x2D): VROI=0, normal output. */
    if (i2c_write_reg(ES8388_DAC_CTL23, 0x00) != ESP_OK) return ESP_FAIL;

    /* 12. Volume: OUT1L/R = 0x1E (0dB), OUT2L = 0 (speaker vol set by PA separately). */
    if (i2c_write_reg(ES8388_DAC_CTL24, 0x1E) != ESP_OK) return ESP_FAIL;  /* LOUT1VOL */
    if (i2c_write_reg(ES8388_DAC_CTL25, 0x1E) != ESP_OK) return ESP_FAIL;  /* ROUT1VOL */
    if (i2c_write_reg(ES8388_DAC_CTL26, 0x00) != ESP_OK) return ESP_FAIL;  /* LOUT2VOL */

    /* 13. DAC power: power up DAC L+R. */
    if (i2c_write_reg(ES8388_DAC_POWER, 0x3C) != ESP_OK) return ESP_FAIL;

    /* 14. ADC power: full power-up (all Pdn bits cleared = 0x00).
     * 0x09 is the intermediate state (Espressif es8388_open end-state), but
     * the full ADC + analog input power-up requires 0x00 (Espressif es8388_start(ADC)).
     * Must come AFTER DACCONTROL21 state machine restart and ADCCONTROL6 unmute. */
    if (i2c_write_reg(ES8388_ADC_POWER, 0x00) != ESP_OK) return ESP_FAIL;
    vTaskDelay(pdMS_TO_TICKS(10));  /* let ADC analog settle after full power-up */

    /* 15. Enable PA (power amplifier for speaker). */
    gpio_set_direction(PLIP_PA_ENABLE, GPIO_MODE_OUTPUT);
    gpio_set_level(PLIP_PA_ENABLE, 1);

    /* Verify key register values to confirm ADC path is configured correctly. */
    uint8_t ctl1=0, adcpwr=0, adcinsel=0, dacctl21=0, chippower=0, adcctl3=0;
    es8388_read_reg(ES8388_CHIP_CTL1,    &ctl1);
    es8388_read_reg(ES8388_ADC_POWER,    &adcpwr);
    es8388_read_reg(ES8388_ADC_CTL2,     &adcinsel);  /* ADCCONTROL2 = input sel (0x50=LIN2) */
    es8388_read_reg(ES8388_ADC_CTL3,     &adcctl3);   /* ADCCONTROL3 = DS filter (0x02) */
    es8388_read_reg(ES8388_DAC_CTL21,    &dacctl21);  /* DACCONTROL21 = ADC+DAC LRCK sync (0xC0) */
    es8388_read_reg(ES8388_CHIP_POWER,   &chippower);
    ESP_LOGI(TAG, "ES8388 regs: CTL1=0x%02X ADCPWR=0x%02X ADCINSEL=0x%02X ADCCTL3=0x%02X DACCTL21=0x%02X CHIPPOWER=0x%02X",
             ctl1, adcpwr, adcinsel, adcctl3, dacctl21, chippower);
    ESP_LOGI(TAG, "ES8388 init OK — PA enabled, DAC @ 0dB, ADC PGA +12dB, input=LIN2/RIN2 (SLIC handset), DACCTL21=0x80");
    return ESP_OK;
}

esp_err_t es8388_set_volume(uint8_t vol)
{
    /* ES8388 OUTx volume registers are GAIN, not attenuation: 0x00 = -45 dB
     * (min) .. 0x21 = 0 dB (max); higher value = louder (>0x21 = mute/reserved).
     * Map 0..100 → 0x00..0x21. (The previous code inverted this, so vol=100
     * produced 0x00 = quietest — confirmed at the bench.)
     * DACCONTROL24 (0x2E)=OUT1L, 25 (0x2F)=OUT1R, 26 (0x30)=OUT2L, 27 (0x31)=OUT2R.
     * DACCONTROL21 (0x2B) is the ADC/DAC LRCK sync register — DO NOT touch here. */
    if (vol > 100) vol = 100;
    uint8_t reg_val = (uint8_t)((int)vol * 0x21 / 100);
    if (reg_val > 0x21) reg_val = 0x21;
    ESP_LOGI(TAG, "set_volume: %d%% -> reg=0x%02X (0x21=max,0dB)", vol, reg_val);
    esp_err_t r = ESP_OK;
    r |= i2c_write_reg(ES8388_DAC_CTL24, reg_val);  /* OUT1 L volume */
    r |= i2c_write_reg(ES8388_DAC_CTL25, reg_val);  /* OUT1 R volume */
    r |= i2c_write_reg(ES8388_DAC_CTL26, reg_val);  /* OUT2 L volume */
    r |= i2c_write_reg(0x31,             reg_val);  /* OUT2 R volume (DACCONTROL27) */
    return r;
}

esp_err_t es8388_set_dac_volume(uint8_t atten)
{
    /* DACCONTROL4 (0x04) = LDACVOL, DACCONTROL5 (0x05) = RDACVOL: DIGITAL DAC
     * volume, applied BEFORE the analog output stages. 0x00 = 0 dB, each step
     * = -0.5 dB, up to 0xC0 = -96 dB (mute). Lowering this gives analog
     * headroom while keeping the output-stage volume (es8388_set_volume) high. */
    if (atten > 0xC0) atten = 0xC0;
    ESP_LOGI(TAG, "set_dac_volume: atten=0x%02X (-%.1f dB)", atten, atten * 0.5f);
    esp_err_t r = i2c_write_reg(ES8388_DAC_CTL4, atten);
    r |= i2c_write_reg(ES8388_DAC_CTL5, atten);
    return r;
}

esp_err_t es8388_mute(bool mute)
{
    uint8_t val = mute ? 0x04 : 0x00;  /* bit2 = DACMUTE */
    ESP_LOGI(TAG, "mute: %s", mute ? "on" : "off");
    return i2c_write_reg(ES8388_DAC_CTL3, val);
}
