/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <assert.h>
#include <getopt.h>
#include <inttypes.h>
#include <iterator>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <vector>

#include <audio_utils/channels.h>
#include <audio_utils/primitives.h>
#include <log/log.h>
#include <system/audio.h>

#include "EffectReverb.h"

// This is the only symbol that needs to be exported
extern audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM;

// Global Variables
enum ReverbParams {
    ARG_HELP = 1,
    ARG_INPUT,
    ARG_OUTPUT,
    ARG_FS,
    ARG_CH_MASK,
    ARG_PRESET,
    ARG_AUX,
    ARG_MONO_MODE,
    ARG_FILE_CH,
};

const effect_uuid_t kReverbUuids[] = {
        {0x172cdf00,
         0xa3bc,
         0x11df,
         0xa72f,
         {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},  // preset-insert mode
        {0xf29a1400,
         0xa3bb,
         0x11df,
         0x8ddc,
         {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b}},  // preset-aux mode
};

// structures
struct reverbConfigParams_t {
    int fChannels = 2;
    int monoMode = false;
    int frameLength = 256;
    int preset = 0;
    int nrChannels = 2;
    int sampleRate = 48000;
    int auxiliary = 0;
    audio_channel_mask_t chMask = AUDIO_CHANNEL_OUT_STEREO;
};

constexpr audio_channel_mask_t kReverbConfigChMask[] = {
        AUDIO_CHANNEL_OUT_MONO,
        AUDIO_CHANNEL_OUT_STEREO,
        AUDIO_CHANNEL_OUT_2POINT1,
        AUDIO_CHANNEL_OUT_2POINT0POINT2,
        AUDIO_CHANNEL_OUT_QUAD,
        AUDIO_CHANNEL_OUT_QUAD_BACK,
        AUDIO_CHANNEL_OUT_QUAD_SIDE,
        AUDIO_CHANNEL_OUT_SURROUND,
        AUDIO_CHANNEL_INDEX_MASK_4,
        AUDIO_CHANNEL_OUT_2POINT1POINT2,
        AUDIO_CHANNEL_OUT_3POINT0POINT2,
        AUDIO_CHANNEL_OUT_PENTA,
        AUDIO_CHANNEL_INDEX_MASK_5,
        AUDIO_CHANNEL_OUT_3POINT1POINT2,
        AUDIO_CHANNEL_OUT_5POINT1,
        AUDIO_CHANNEL_OUT_5POINT1_BACK,
        AUDIO_CHANNEL_OUT_5POINT1_SIDE,
        AUDIO_CHANNEL_INDEX_MASK_6,
        AUDIO_CHANNEL_OUT_6POINT1,
        AUDIO_CHANNEL_INDEX_MASK_7,
        AUDIO_CHANNEL_OUT_5POINT1POINT2,
        AUDIO_CHANNEL_OUT_7POINT1,
        AUDIO_CHANNEL_INDEX_MASK_8,
        AUDIO_CHANNEL_INDEX_MASK_9,
        AUDIO_CHANNEL_INDEX_MASK_10,
        AUDIO_CHANNEL_INDEX_MASK_11,
        AUDIO_CHANNEL_INDEX_MASK_12,
        AUDIO_CHANNEL_INDEX_MASK_13,
        AUDIO_CHANNEL_INDEX_MASK_14,
        AUDIO_CHANNEL_INDEX_MASK_15,
        AUDIO_CHANNEL_INDEX_MASK_16,
        AUDIO_CHANNEL_INDEX_MASK_17,
        AUDIO_CHANNEL_INDEX_MASK_18,
        AUDIO_CHANNEL_INDEX_MASK_19,
        AUDIO_CHANNEL_INDEX_MASK_20,
        AUDIO_CHANNEL_INDEX_MASK_21,
        AUDIO_CHANNEL_INDEX_MASK_22,
        AUDIO_CHANNEL_INDEX_MASK_23,
        AUDIO_CHANNEL_INDEX_MASK_24,
};

constexpr int kReverbConfigChMaskCount = std::size(kReverbConfigChMask);

int reverbCreateEffect(effect_handle_t* pEffectHandle, effect_config_t* pConfig, int sessionId,
                       int ioId, int auxFlag) {
    if (int status = AUDIO_EFFECT_LIBRARY_INFO_SYM.create_effect(&kReverbUuids[auxFlag], sessionId,
                                                                 ioId, pEffectHandle);
        status != 0) {
        ALOGE("Reverb create returned an error = %d\n", status);
        return EXIT_FAILURE;
    }
    int reply = 0;
    uint32_t replySize = sizeof(reply);
    (**pEffectHandle)
            ->command(*pEffectHandle, EFFECT_CMD_SET_CONFIG, sizeof(effect_config_t), pConfig,
                      &replySize, &reply);
    return reply;
}

int reverbSetConfigParam(uint32_t paramType, uint32_t paramValue, effect_handle_t effectHandle) {
    int reply = 0;
    uint32_t replySize = sizeof(reply);
    uint32_t paramData[2] = {paramType, paramValue};
    effect_param_t* effectParam = (effect_param_t*)malloc(sizeof(*effectParam) + sizeof(paramData));
    memcpy(&effectParam->data[0], &paramData[0], sizeof(paramData));
    effectParam->psize = sizeof(paramData[0]);
    effectParam->vsize = sizeof(paramData[1]);
    int status = (*effectHandle)
                         ->command(effectHandle, EFFECT_CMD_SET_PARAM,
                                   sizeof(effect_param_t) + sizeof(paramData), effectParam,
                                   &replySize, &reply);
    free(effectParam);
    if (status != 0) {
        ALOGE("Reverb set config returned an error = %d\n", status);
        return status;
    }
    return reply;
}

void printUsage() {
    printf("\nUsage: ");
    printf("\n     <executable> [options]\n");
    printf("\nwhere options are, ");
    printf("\n     --input <inputfile>");
    printf("\n           path to the input file");
    printf("\n     --output <outputfile>");
    printf("\n           path to the output file");
    printf("\n     --help");
    printf("\n           prints this usage information");
    printf("\n     --chMask <channel_mask>\n");
    printf("\n           0  - AUDIO_CHANNEL_OUT_MONO");
    printf("\n           1  - AUDIO_CHANNEL_OUT_STEREO");
    printf("\n           2  - AUDIO_CHANNEL_OUT_2POINT1");
    printf("\n           3  - AUDIO_CHANNEL_OUT_2POINT0POINT2");
    printf("\n           4  - AUDIO_CHANNEL_OUT_QUAD");
    printf("\n           5  - AUDIO_CHANNEL_OUT_QUAD_BACK");
    printf("\n           6  - AUDIO_CHANNEL_OUT_QUAD_SIDE");
    printf("\n           7  - AUDIO_CHANNEL_OUT_SURROUND");
    printf("\n           8  - canonical channel index mask for 4 ch: (1 << 4) - 1");
    printf("\n           9  - AUDIO_CHANNEL_OUT_2POINT1POINT2");
    printf("\n           10 - AUDIO_CHANNEL_OUT_3POINT0POINT2");
    printf("\n           11 - AUDIO_CHANNEL_OUT_PENTA");
    printf("\n           12 - canonical channel index mask for 5 ch: (1 << 5) - 1");
    printf("\n           13 - AUDIO_CHANNEL_OUT_3POINT1POINT2");
    printf("\n           14 - AUDIO_CHANNEL_OUT_5POINT1");
    printf("\n           15 - AUDIO_CHANNEL_OUT_5POINT1_BACK");
    printf("\n           16 - AUDIO_CHANNEL_OUT_5POINT1_SIDE");
    printf("\n           17 - canonical channel index mask for 6 ch: (1 << 6) - 1");
    printf("\n           18 - AUDIO_CHANNEL_OUT_6POINT1");
    printf("\n           19 - canonical channel index mask for 7 ch: (1 << 7) - 1");
    printf("\n           20 - AUDIO_CHANNEL_OUT_5POINT1POINT2");
    printf("\n           21 - AUDIO_CHANNEL_OUT_7POINT1");
    printf("\n           22 - canonical channel index mask for 8 ch: (1 << 8) - 1");
    printf("\n           default 0");
    printf("\n     --fs <sampling_freq>");
    printf("\n           Sampling frequency in Hz, default 48000.");
    printf("\n     --preset <preset_value>");
    printf("\n           0 - None");
    printf("\n           1 - Small Room");
    printf("\n           2 - Medium Room");
    printf("\n           3 - Large Room");
    printf("\n           4 - Medium Hall");
    printf("\n           5 - Large Hall");
    printf("\n           6 - Plate");
    printf("\n           default 0");
    printf("\n     --fch <file_channels>");
    printf("\n           number of channels in input file (1 through 8), default 1");
    printf("\n     --M");
    printf("\n           Mono mode (force all input audio channels to be identical)");
    printf("\n     --aux <auxiliary_flag> ");
    printf("\n           0 - Insert Mode on");
    printf("\n           1 - auxiliary Mode on");
    printf("\n           default 0");
    printf("\n");
}

int main(int argc, const char* argv[]) {
    if (argc == 1) {
        printUsage();
        return EXIT_FAILURE;
    }
    for (int i = 1; i < argc; i++) {
        printf("%s ", argv[i]);
    }
    reverbConfigParams_t revConfigParams{};  // default initialize
    const char* inputFile = nullptr;
    const char* outputFile = nullptr;

    const option long_opts[] = {
            {"help", no_argument, nullptr, ARG_HELP},
            {"input", required_argument, nullptr, ARG_INPUT},
            {"output", required_argument, nullptr, ARG_OUTPUT},
            {"fs", required_argument, nullptr, ARG_FS},
            {"chMask", required_argument, nullptr, ARG_CH_MASK},
            {"preset", required_argument, nullptr, ARG_PRESET},
            {"aux", required_argument, nullptr, ARG_AUX},
            {"M", no_argument, &revConfigParams.monoMode, true},
            {"fch", required_argument, nullptr, ARG_FILE_CH},
            {nullptr, 0, nullptr, 0},
    };

    while (true) {
        const int opt = getopt_long(argc, (char* const*)argv, "i:o:", long_opts, nullptr);
        if (opt == -1) {
            break;
        }
        switch (opt) {
            case ARG_HELP:
                printUsage();
                return EXIT_SUCCESS;
            case ARG_INPUT: {
                inputFile = (char*)optarg;
                break;
            }
            case ARG_OUTPUT: {
                outputFile = (char*)optarg;
                break;
            }
            case ARG_FS: {
                revConfigParams.sampleRate = atoi(optarg);
                break;
            }
            case ARG_CH_MASK: {
                int chMaskIdx = atoi(optarg);
                if (chMaskIdx < 0 or chMaskIdx > kReverbConfigChMaskCount) {
                    ALOGE("Channel Mask index not in correct range\n");
                    printUsage();
                    return EXIT_FAILURE;
                }
                revConfigParams.chMask = kReverbConfigChMask[chMaskIdx];
                break;
            }
            case ARG_PRESET: {
                revConfigParams.preset = atoi(optarg);
                break;
            }
            case ARG_AUX: {
                revConfigParams.auxiliary = atoi(optarg);
                break;
            }
            case ARG_MONO_MODE: {
                break;
            }
            case ARG_FILE_CH: {
                revConfigParams.fChannels = atoi(optarg);
                break;
            }
            default:
                break;
        }
    }

    if (inputFile == nullptr) {
        ALOGE("Error: missing input files\n");
        printUsage();
        return EXIT_FAILURE;
    }
    std::unique_ptr<FILE, decltype(&fclose)> inputFp(fopen(inputFile, "rb"), &fclose);

    if (inputFp == nullptr) {
        ALOGE("Cannot open input file %s\n", inputFile);
        return EXIT_FAILURE;
    }

    if (outputFile == nullptr) {
        ALOGE("Error: missing output files\n");
        printUsage();
        return EXIT_FAILURE;
    }
    std::unique_ptr<FILE, decltype(&fclose)> outputFp(fopen(outputFile, "wb"), &fclose);

    if (outputFp == nullptr) {
        ALOGE("Cannot open output file %s\n", outputFile);
        return EXIT_FAILURE;
    }

    int32_t sessionId = 1;
    int32_t ioId = 1;
    effect_handle_t effectHandle = nullptr;
    effect_config_t config;
    config.inputCfg.samplingRate = config.outputCfg.samplingRate = revConfigParams.sampleRate;
    config.inputCfg.channels = config.outputCfg.channels = revConfigParams.chMask;
    config.inputCfg.format = config.outputCfg.format = AUDIO_FORMAT_PCM_FLOAT;
    if (int status = reverbCreateEffect(&effectHandle, &config, sessionId, ioId,
                                        revConfigParams.auxiliary);
        status != 0) {
        ALOGE("Create effect call returned error %i", status);
        return EXIT_FAILURE;
    }

    int reply = 0;
    uint32_t replySize = sizeof(reply);
    (*effectHandle)->command(effectHandle, EFFECT_CMD_ENABLE, 0, nullptr, &replySize, &reply);
    if (reply != 0) {
        ALOGE("Command enable call returned error %d\n", reply);
        return EXIT_FAILURE;
    }

    if (int status = reverbSetConfigParam(REVERB_PARAM_PRESET, (uint32_t)revConfigParams.preset,
                                          effectHandle);
        status != 0) {
        ALOGE("Invalid reverb preset. Error %d\n", status);
        return EXIT_FAILURE;
    }

    revConfigParams.nrChannels = audio_channel_count_from_out_mask(revConfigParams.chMask);
    const int channelCount = revConfigParams.nrChannels;
    const int frameLength = revConfigParams.frameLength;
#ifdef BYPASS_EXEC
    const int frameSize = (int)channelCount * sizeof(float);
#endif
    const int ioChannelCount = revConfigParams.fChannels;
    const int ioFrameSize = ioChannelCount * sizeof(short);
    const int maxChannelCount = std::max(channelCount, ioChannelCount);

    std::vector<short> in(frameLength * maxChannelCount);
    std::vector<short> out(frameLength * maxChannelCount);
    std::vector<float> floatIn(frameLength * channelCount);
    std::vector<float> floatOut(frameLength * channelCount);

    int frameCounter = 0;

    while (fread(in.data(), ioFrameSize, frameLength, inputFp.get()) == (size_t)frameLength) {
        if (ioChannelCount != channelCount) {
            adjust_channels(in.data(), ioChannelCount, in.data(), channelCount, sizeof(short),
                            frameLength * ioFrameSize);
        }
        memcpy_to_float_from_i16(floatIn.data(), in.data(), frameLength * channelCount);

        // Mono mode will replicate the first channel to all other channels.
        // This ensures all audio channels are identical. This is useful for testing
        // Bass Boost, which extracts a mono signal for processing.
        if (revConfigParams.monoMode && channelCount > 1) {
            for (int i = 0; i < frameLength; ++i) {
                auto* fp = &floatIn[i * channelCount];
                std::fill(fp + 1, fp + channelCount, *fp);  // replicate ch 0
            }
        }

        audio_buffer_t inputBuffer, outputBuffer;
        inputBuffer.frameCount = outputBuffer.frameCount = frameLength;
        inputBuffer.f32 = floatIn.data();
        outputBuffer.f32 = floatOut.data();
#ifndef BYPASS_EXEC
        if (int status = (*effectHandle)->process(effectHandle, &inputBuffer, &outputBuffer);
            status != 0) {
            ALOGE("\nError: Process returned with error %d\n", status);
            return EXIT_FAILURE;
        }
#else
        memcpy(floatOut.data(), floatIn.data(), frameLength * frameSize);
#endif
        memcpy_to_i16_from_float(out.data(), floatOut.data(), frameLength * channelCount);

        if (ioChannelCount != channelCount) {
            adjust_channels(out.data(), channelCount, out.data(), ioChannelCount, sizeof(short),
                            frameLength * channelCount * sizeof(short));
        }
        (void)fwrite(out.data(), ioFrameSize, frameLength, outputFp.get());
        frameCounter += frameLength;
    }

    if (int status = AUDIO_EFFECT_LIBRARY_INFO_SYM.release_effect(effectHandle); status != 0) {
        ALOGE("Audio Preprocessing release returned an error = %d\n", status);
        return EXIT_FAILURE;
    }
    printf("frameCounter: [%d]\n", frameCounter);

    return EXIT_SUCCESS;
}
