/**
 * Volume normalizer.
 * For GNU/Linux 64 bit version.
 * Version: 1.
 * Written on C (https://en.wikipedia.org/wiki/C_(programming_language))
 * Copyright (C) 2025  Artyomov Alexander
 * Used https://chat.deepseek.com/
 * http://self-made-free.ru/
 * aralni@mail.ru
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <math.h>

// WAV file header structure (PCM format)
#pragma pack(push, 1)
typedef struct {
    char     riff_id[4];        // "RIFF"
    uint32_t riff_size;         // File size - 8
    char     wave_id[4];        // "WAVE"
    char     fmt_id[4];         // "fmt "
    uint32_t fmt_size;          // Size of fmt chunk (16 for PCM)
    uint16_t audio_format;      // Audio format (1 for PCM)
    uint16_t num_channels;      // Number of channels
    uint32_t sample_rate;       // Sample rate
    uint32_t byte_rate;         // Sample rate * block_align
    uint16_t block_align;       // Num channels * bits_per_sample / 8
    uint16_t bits_per_sample;   // Bits per sample
    char     data_id[4];        // "data"
    uint32_t data_size;         // Size of data chunk
} WAVHeader;
#pragma pack(pop)

// Function to read WAV header
int read_wav_header(FILE* file, WAVHeader* header) {
    fseek(file, 0, SEEK_SET);
    size_t bytes_read = fread(header, sizeof(WAVHeader), 1, file);
    
    if (bytes_read != 1) {
        fprintf(stderr, "Error: Cannot read WAV header\n");
        return 0;
    }
    
    // Verify WAV file signature
    if (memcmp(header->riff_id, "RIFF", 4) != 0 ||
        memcmp(header->wave_id, "WAVE", 4) != 0 ||
        memcmp(header->fmt_id, "fmt ", 4) != 0 ||
        memcmp(header->data_id, "data", 4) != 0) {
        fprintf(stderr, "Error: Invalid WAV file format\n");
        return 0;
    }
    
    // Check if PCM format
    if (header->audio_format != 1) {
        fprintf(stderr, "Error: Only PCM format is supported (format: %d)\n", 
                header->audio_format);
        return 0;
    }
    
    // Check if 16-bit
    if (header->bits_per_sample != 16) {
        fprintf(stderr, "Error: Only 16-bit audio is supported (bits: %d)\n",
                header->bits_per_sample);
        return 0;
    }
    
    return 1;
}

// Function to write WAV header
void write_wav_header(FILE* file, const WAVHeader* header) {
    fseek(file, 0, SEEK_SET);
    fwrite(header, sizeof(WAVHeader), 1, file);
}

// Function to read audio data from WAV file
int16_t* read_audio_data(FILE* file, const WAVHeader* header, int* num_samples) {
    int16_t* data;
    int total_samples;
    
    // Calculate number of samples
    total_samples = header->data_size / (header->num_channels * (header->bits_per_sample / 8));
    *num_samples = total_samples;
    
    // Allocate memory for audio data
    data = (int16_t*)malloc(header->data_size);
    if (!data) {
        fprintf(stderr, "Error: Cannot allocate memory for audio data\n");
        return NULL;
    }
    
    // Read audio data
    fseek(file, sizeof(WAVHeader), SEEK_SET);
    size_t bytes_read = fread(data, 1, header->data_size, file);
    
    if (bytes_read != header->data_size) {
        fprintf(stderr, "Error: Cannot read audio data (read %zu of %u bytes)\n",
                bytes_read, header->data_size);
        free(data);
        return NULL;
    }
    
    return data;
}

// Function to write audio data to WAV file
int write_audio_data(FILE* file, const int16_t* data, const WAVHeader* header) {
    fseek(file, sizeof(WAVHeader), SEEK_SET);
    size_t bytes_written = fwrite(data, 1, header->data_size, file);
    
    if (bytes_written != header->data_size) {
        fprintf(stderr, "Error: Cannot write audio data (wrote %zu of %u bytes)\n",
                bytes_written, header->data_size);
        return 0;
    }
    
    return 1;
}

// Function to find maximum amplitude
double find_max_amplitude(const int16_t* data, int num_samples, int num_channels) {
    double max_amplitude = 0.0;
    
    for (int i = 0; i < num_samples; i++) {
        for (int ch = 0; ch < num_channels; ch++) {
            int idx = i * num_channels + ch;
            double amplitude = fabs(data[idx] / 32768.0);
            if (amplitude > max_amplitude) {
                max_amplitude = amplitude;
            }
        }
    }
    
    return max_amplitude;
}

// Function to normalize audio data
void normalize_audio_data(int16_t* data, int num_samples, int num_channels, double scale_factor) {
    for (int i = 0; i < num_samples; i++) {
        for (int ch = 0; ch < num_channels; ch++) {
            int idx = i * num_channels + ch;
            double normalized = data[idx] * scale_factor;
            
            // Clip to prevent overflow
            if (normalized > 32767.0) {
                data[idx] = 32767;
            } else if (normalized < -32768.0) {
                data[idx] = -32768;
            } else {
                data[idx] = (int16_t)normalized;
            }
        }
    }
}

// Main normalization function
int normalize_wav(const char* input_file, const char* output_file) {
    FILE* infile = NULL;
    FILE* outfile = NULL;
    WAVHeader header;
    int16_t* audio_data = NULL;
    int num_samples = 0;
    double max_amplitude;
    double scale_factor;
    int result = 0;
    
    // Open input file
    infile = fopen(input_file, "rb");
    if (!infile) {
        fprintf(stderr, "Error: Cannot open input file '%s'\n", input_file);
        return 0;
    }
    
    // Check if file is empty
    fseek(infile, 0, SEEK_END);
    long file_size = ftell(infile);
    if (file_size == 0) {
        fprintf(stderr, "Error: File '%s' is empty\n", input_file);
        fclose(infile);
        return 0;
    }
    fseek(infile, 0, SEEK_SET);
    
    // Read WAV header
    if (!read_wav_header(infile, &header)) {
        fclose(infile);
        return 0;
    }
    
    // Display file information
    printf("Loading WAV file: %s\n", input_file);
    printf("Sample rate: %u Hz\n", header.sample_rate);
    printf("Number of channels: %u\n", header.num_channels);
    printf("Data chunk size: %u bytes\n", header.data_size);
    printf("Bits per sample: %u\n", header.bits_per_sample);
    
    // Check if there is data
    if (header.data_size == 0) {
        fprintf(stderr, "Error: File contains no data or has invalid structure\n");
        fclose(infile);
        return 0;
    }
    
    // Read audio data
    audio_data = read_audio_data(infile, &header, &num_samples);
    if (!audio_data) {
        fclose(infile);
        return 0;
    }
    fclose(infile);
    infile = NULL;
    
    // Find maximum amplitude
    max_amplitude = find_max_amplitude(audio_data, num_samples, header.num_channels);
    
    // Calculate scale factor
    if (max_amplitude > 0) {
        scale_factor = 1.0 / max_amplitude;
    } else {
        scale_factor = 1.0;
    }
    
    printf("Maximum amplitude: %.4f\n", max_amplitude);
    printf("Scale factor: %.4f\n", scale_factor);
    
    // Normalize audio data
    normalize_audio_data(audio_data, num_samples, header.num_channels, scale_factor);
    
    // Open output file
    outfile = fopen(output_file, "wb");
    if (!outfile) {
        fprintf(stderr, "Error: Cannot create output file '%s'\n", output_file);
        free(audio_data);
        return 0;
    }
    
    // Write WAV header
    write_wav_header(outfile, &header);
    
    // Write normalized audio data
    if (!write_audio_data(outfile, audio_data, &header)) {
        fclose(outfile);
        free(audio_data);
        return 0;
    }
    
    printf("Saved normalized WAV file: %s\n", output_file);
    printf("Normalization completed successfully.\n");
    
    result = 1;
    
    // Cleanup
    if (outfile) fclose(outfile);
    if (audio_data) free(audio_data);
    
    return result;
}

// Print usage information
void print_usage(const char* program_name) {
    printf("Usage: %s <input.wav> <output.wav>\n", program_name);
    printf("  Normalizes the volume of a WAV file to maximum amplitude.\n");
    printf("  Supports 16-bit PCM WAV files only.\n");
}

int main(int argc, char* argv[]) {
    const char* input_file;
    const char* output_file;
    
    // Parse command line arguments
    if (argc < 3) {
        print_usage(argv[0]);
        return 1;
    }
    
    input_file = argv[1];
    output_file = argv[2];
    
    // Normalize WAV file
    if (!normalize_wav(input_file, output_file)) {
        fprintf(stderr, "Normalization failed.\n");
        return 1;
    }
    
    return 0;
}