/*
 * Copyright (C) 2014 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 <string.h>
#include <audio_utils/channels.h>
#include "private/private.h"

/*
 * Clamps a 24-bit value from a 32-bit sample
 */
static inline int32_t clamp24(int32_t sample)
{
    if ((sample>>23) ^ (sample>>31)) {
        sample = 0x007FFFFF ^ (sample>>31);
    }
    return sample;
}

/*
 * Converts a uint8x3_t into an int32_t
 */
inline int32_t uint8x3_to_int32(uint8x3_t val) {
#ifdef HAVE_BIG_ENDIAN
    int32_t temp = (val.c[0] << 24 | val.c[1] << 16 | val.c[2] << 8) >> 8;
#else
    int32_t temp = (val.c[2] << 24 | val.c[1] << 16 | val.c[0] << 8) >> 8;
#endif
    return clamp24(temp);
}

/*
 * Converts an int32_t to a uint8x3_t
 */
inline uint8x3_t int32_to_uint8x3(int32_t in) {
    uint8x3_t out;
#ifdef HAVE_BIG_ENDIAN
    out.c[2] = in;
    out.c[1] = in >> 8;
    out.c[0] = in >> 16;
#else
    out.c[0] = in;
    out.c[1] = in >> 8;
    out.c[2] = in >> 16;
#endif
    return out;
}

/* Channel expands (adds zeroes to audio frame end) from an input buffer to an output buffer.
 * See expand_channels() function below for parameter definitions.
 *
 * Move from back to front so that the conversion can be done in-place
 * i.e. in_buff == out_buff
 * NOTE: num_in_bytes must be a multiple of in_buff_channels * in_buff_sample_size.
 */
#define EXPAND_CHANNELS(in_buff, in_buff_chans, out_buff, out_buff_chans, num_in_bytes, zero) \
{ \
    size_t num_in_samples = num_in_bytes / sizeof(*in_buff); \
    size_t num_out_samples = (num_in_samples * out_buff_chans) / in_buff_chans; \
    typeof(out_buff) dst_ptr = out_buff + num_out_samples - 1; \
    size_t src_index; \
    typeof(in_buff) src_ptr = in_buff + num_in_samples - 1; \
    size_t num_zero_chans = out_buff_chans - in_buff_chans; \
    for (src_index = 0; src_index < num_in_samples; src_index += in_buff_chans) { \
        size_t dst_offset; \
        for (dst_offset = 0; dst_offset < num_zero_chans; dst_offset++) { \
            *dst_ptr-- = zero; \
        } \
        for (; dst_offset < out_buff_chans; dst_offset++) { \
            *dst_ptr-- = *src_ptr--; \
        } \
    } \
    /* return number of *bytes* generated */ \
    return num_out_samples * sizeof(*out_buff); \
}

/* Channel expands from a MONO input buffer to a MULTICHANNEL output buffer by duplicating the
 * single input channel to the first 2 output channels and 0-filling the remaining.
 * See expand_channels() function below for parameter definitions.
 *
 * in_buff_chans MUST be 1 and out_buff_chans MUST be >= 2
 *
 * Move from back to front so that the conversion can be done in-place
 * i.e. in_buff == out_buff
 * NOTE: num_in_bytes must be a multiple of in_buff_channels * in_buff_sample_size.
 */
#define EXPAND_MONO_TO_MULTI(in_buff, in_buff_chans, out_buff, out_buff_chans, num_in_bytes, zero) \
{ \
    size_t num_in_samples = num_in_bytes / sizeof(*in_buff); \
    size_t num_out_samples = (num_in_samples * out_buff_chans) / in_buff_chans; \
    typeof(out_buff) dst_ptr = out_buff + num_out_samples - 1; \
    size_t src_index; \
    typeof(in_buff) src_ptr = in_buff + num_in_samples - 1; \
    size_t num_zero_chans = out_buff_chans - in_buff_chans - 1; \
    for (src_index = 0; src_index < num_in_samples; src_index += in_buff_chans) { \
        size_t dst_offset; \
        for (dst_offset = 0; dst_offset < num_zero_chans; dst_offset++) { \
            *dst_ptr-- = zero; \
        } \
        for (; dst_offset < out_buff_chans; dst_offset++) { \
            *dst_ptr-- = *src_ptr; \
        } \
        src_ptr--; \
    } \
    /* return number of *bytes* generated */ \
    return num_out_samples * sizeof(*out_buff); \
}

/* Channel contracts (removes from audio frame end) from an input buffer to an output buffer.
 * See contract_channels() function below for parameter definitions.
 *
 * Move from front to back so that the conversion can be done in-place
 * i.e. in_buff == out_buff
 * NOTE: num_in_bytes must be a multiple of in_buff_channels * in_buff_sample_size.
 */
#define CONTRACT_CHANNELS(in_buff, in_buff_chans, out_buff, out_buff_chans, num_in_bytes) \
{ \
    size_t num_in_samples = num_in_bytes / sizeof(*in_buff); \
    size_t num_out_samples = (num_in_samples * out_buff_chans) / in_buff_chans; \
    size_t num_skip_samples = in_buff_chans - out_buff_chans; \
    typeof(out_buff) dst_ptr = out_buff; \
    typeof(in_buff) src_ptr = in_buff; \
    size_t src_index; \
    for (src_index = 0; src_index < num_in_samples; src_index += in_buff_chans) { \
        size_t dst_offset; \
        for (dst_offset = 0; dst_offset < out_buff_chans; dst_offset++) { \
            *dst_ptr++ = *src_ptr++; \
        } \
        src_ptr += num_skip_samples; \
    } \
    /* return number of *bytes* generated */ \
    return num_out_samples * sizeof(*out_buff); \
}

/* Channel contracts from a MULTICHANNEL input buffer to a MONO output buffer by mixing the
 * first two input channels into the single output channel (and skipping the rest).
 * See contract_channels() function below for parameter definitions.
 *
 * in_buff_chans MUST be >= 2 and out_buff_chans MUST be 1
 *
 * Move from front to back so that the conversion can be done in-place
 * i.e. in_buff == out_buff
 * NOTE: num_in_bytes must be a multiple of in_buff_channels * in_buff_sample_size.
 * NOTE: Overload of the summed channels is avoided by averaging the two input channels.
 * NOTE: Can not be used for uint8x3_t samples, see CONTRACT_TO_MONO_24() below.
 */
#define CONTRACT_TO_MONO(in_buff, out_buff, num_in_bytes) \
{ \
    size_t num_in_samples = num_in_bytes / sizeof(*in_buff); \
    size_t num_out_samples = (num_in_samples * out_buff_chans) / in_buff_chans; \
    size_t num_skip_samples = in_buff_chans - 2; \
    typeof(out_buff) dst_ptr = out_buff; \
    typeof(in_buff) src_ptr = in_buff; \
    int32_t temp0, temp1; \
    size_t src_index; \
    for (src_index = 0; src_index < num_in_samples; src_index += in_buff_chans) { \
        temp0 = *src_ptr++; \
        temp1 = *src_ptr++; \
        /* *dst_ptr++ = temp >> 1; */ \
        /* This bit of magic adds and normalizes without overflow (or so claims hunga@) */ \
        /* Bitwise half adder trick, see http://en.wikipedia.org/wiki/Adder_(electronics) */ \
        /* Hacker's delight, p. 19 http://www.hackersdelight.org/basics2.pdf */ \
        *dst_ptr++ = (temp0 & temp1) + ((temp0 ^ temp1) >> 1); \
        src_ptr += num_skip_samples; \
    } \
    /* return number of *bytes* generated */ \
    return num_out_samples * sizeof(*out_buff); \
}

/* Channel contracts from a MULTICHANNEL uint8x3_t input buffer to a MONO uint8x3_t output buffer
 * by mixing the first two input channels into the single output channel (and skipping the rest).
 * See contract_channels() function below for parameter definitions.
 *
 * Move from front to back so that the conversion can be done in-place
 * i.e. in_buff == out_buff
 * NOTE: num_in_bytes must be a multiple of in_buff_channels * in_buff_sample_size.
 * NOTE: Overload of the summed channels is avoided by averaging the two input channels.
 * NOTE: Can not be used for normal, scalar samples, see CONTRACT_TO_MONO() above.
 */
#define CONTRACT_TO_MONO_24(in_buff, out_buff, num_in_bytes) \
{ \
    size_t num_in_samples = num_in_bytes / sizeof(*in_buff); \
    size_t num_out_samples = (num_in_samples * out_buff_chans) / in_buff_chans; \
    size_t num_skip_samples = in_buff_chans - 2; \
    typeof(out_buff) dst_ptr = out_buff; \
    typeof(in_buff) src_ptr = in_buff; \
    int32_t temp; \
    size_t src_index; \
    for (src_index = 0; src_index < num_in_samples; src_index += in_buff_chans) { \
        temp = uint8x3_to_int32(*src_ptr++); \
        temp += uint8x3_to_int32(*src_ptr++); \
        *dst_ptr = int32_to_uint8x3(temp >> 1); \
        src_ptr += num_skip_samples; \
    } \
    /* return number of *bytes* generated */ \
    return num_out_samples * sizeof(*out_buff); \
}

/*
 * Convert a buffer of N-channel, interleaved samples to M-channel
 * (where N > M).
 *   in_buff points to the buffer of samples
 *   in_buff_channels Specifies the number of channels in the input buffer.
 *   out_buff points to the buffer to receive converted samples.
 *   out_buff_channels Specifies the number of channels in the output buffer.
 *   sample_size_in_bytes Specifies the number of bytes per sample.
 *   num_in_bytes size of input buffer in BYTES
 * returns
 *   the number of BYTES of output data.
 * NOTE
 *   channels > M are thrown away.
 *   The out and sums buffers must either be completely separate (non-overlapping), or
 *   they must both start at the same address. Partially overlapping buffers are not supported.
 */
static size_t contract_channels(const void* in_buff, size_t in_buff_chans,
                                void* out_buff, size_t out_buff_chans,
                                unsigned sample_size_in_bytes, size_t num_in_bytes)
{
    switch (sample_size_in_bytes) {
    case 1:
        if (out_buff_chans == 1) {
            /* Special case Multi to Mono */
            CONTRACT_TO_MONO((const uint8_t*)in_buff, (uint8_t*)out_buff, num_in_bytes);
            // returns in macro
        } else {
            CONTRACT_CHANNELS((const uint8_t*)in_buff, in_buff_chans,
                              (uint8_t*)out_buff, out_buff_chans,
                              num_in_bytes);
            // returns in macro
        }
    case 2:
        if (out_buff_chans == 1) {
            /* Special case Multi to Mono */
            CONTRACT_TO_MONO((const int16_t*)in_buff, (int16_t*)out_buff, num_in_bytes);
            // returns in macro
        } else {
            CONTRACT_CHANNELS((const int16_t*)in_buff, in_buff_chans,
                              (int16_t*)out_buff, out_buff_chans,
                              num_in_bytes);
            // returns in macro
        }
    case 3:
        if (out_buff_chans == 1) {
            /* Special case Multi to Mono */
            CONTRACT_TO_MONO_24((const uint8x3_t*)in_buff,
                                       (uint8x3_t*)out_buff, num_in_bytes);
            // returns in macro
        } else {
            CONTRACT_CHANNELS((const uint8x3_t*)in_buff, in_buff_chans,
                              (uint8x3_t*)out_buff, out_buff_chans,
                              num_in_bytes);
            // returns in macro
        }
    case 4:
        if (out_buff_chans == 1) {
            /* Special case Multi to Mono */
            CONTRACT_TO_MONO((const int32_t*)in_buff, (int32_t*)out_buff, num_in_bytes);
            // returns in macro
        } else {
            CONTRACT_CHANNELS((const int32_t*)in_buff, in_buff_chans,
                              (int32_t*)out_buff, out_buff_chans,
                              num_in_bytes);
            // returns in macro
        }
    default:
        return 0;
    }
}

/*
 * Convert a buffer of N-channel, interleaved samples to M-channel
 * (where N < M).
 *   in_buff points to the buffer of samples
 *   in_buff_channels Specifies the number of channels in the input buffer.
 *   out_buff points to the buffer to receive converted samples.
 *   out_buff_channels Specifies the number of channels in the output buffer.
 *   sample_size_in_bytes Specifies the number of bytes per sample.
 *   num_in_bytes size of input buffer in BYTES
 * returns
 *   the number of BYTES of output data.
 * NOTE
 *   channels > N are filled with silence.
 *   The out and sums buffers must either be completely separate (non-overlapping), or
 *   they must both start at the same address. Partially overlapping buffers are not supported.
 */
static size_t expand_channels(const void* in_buff, size_t in_buff_chans,
                              void* out_buff, size_t out_buff_chans,
                              unsigned sample_size_in_bytes, size_t num_in_bytes)
{
    static const uint8x3_t packed24_zero; /* zero 24 bit sample */

    switch (sample_size_in_bytes) {
    case 1:
        if (in_buff_chans == 1) {
            /* special case of mono source to multi-channel */
            EXPAND_MONO_TO_MULTI((const uint8_t*)in_buff, in_buff_chans,
                            (uint8_t*)out_buff, out_buff_chans,
                            num_in_bytes, 0);
            // returns in macro
        } else {
            EXPAND_CHANNELS((const uint8_t*)in_buff, in_buff_chans,
                            (uint8_t*)out_buff, out_buff_chans,
                            num_in_bytes, 0);
            // returns in macro
        }
    case 2:
        if (in_buff_chans == 1) {
            /* special case of mono source to multi-channel */
            EXPAND_MONO_TO_MULTI((const int16_t*)in_buff, in_buff_chans,
                            (int16_t*)out_buff, out_buff_chans,
                            num_in_bytes, 0);
            // returns in macro
        } else {
            EXPAND_CHANNELS((const int16_t*)in_buff, in_buff_chans,
                            (int16_t*)out_buff, out_buff_chans,
                            num_in_bytes, 0);
            // returns in macro
        }
    case 3:
        if (in_buff_chans == 1) {
            /* special case of mono source to multi-channel */
            EXPAND_MONO_TO_MULTI((const uint8x3_t*)in_buff, in_buff_chans,
                            (uint8x3_t*)out_buff, out_buff_chans,
                            num_in_bytes, packed24_zero);
            // returns in macro
        } else {
            EXPAND_CHANNELS((const uint8x3_t*)in_buff, in_buff_chans,
                            (uint8x3_t*)out_buff, out_buff_chans,
                            num_in_bytes, packed24_zero);
            // returns in macro
        }
    case 4:
        if (in_buff_chans == 1) {
            /* special case of mono source to multi-channel */
            EXPAND_MONO_TO_MULTI((const int32_t*)in_buff, in_buff_chans,
                            (int32_t*)out_buff, out_buff_chans,
                            num_in_bytes, 0);
            // returns in macro
        } else {
           EXPAND_CHANNELS((const int32_t*)in_buff, in_buff_chans,
                            (int32_t*)out_buff, out_buff_chans,
                            num_in_bytes, 0);
            // returns in macro
        }
    default:
        return 0;
    }
}

size_t adjust_channels(const void* in_buff, size_t in_buff_chans,
                       void* out_buff, size_t out_buff_chans,
                       unsigned sample_size_in_bytes, size_t num_in_bytes)
{
    if (out_buff_chans > in_buff_chans) {
        return expand_channels(in_buff, in_buff_chans, out_buff,  out_buff_chans,
                               sample_size_in_bytes, num_in_bytes);
    } else if (out_buff_chans < in_buff_chans) {
        return contract_channels(in_buff, in_buff_chans, out_buff,  out_buff_chans,
                                 sample_size_in_bytes, num_in_bytes);
    } else if (in_buff != out_buff) {
        memcpy(out_buff, in_buff, num_in_bytes);
    }

    return num_in_bytes;
}