/**
 * Audio recorder.
 * For GNU/Linux 64 bit version.
 * Version: 1.
 * Written on C (https://en.wikipedia.org/wiki/C_(programming_language))
 * Copyright (C) 2025-2026  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/>.
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>  // Добавлен заголовочный файл для pthread
#include <alsa/asoundlib.h>

#define SAMPLE_RATE 44100       // Sampling rate
#define NUM_CHANNELS 2          // Number of channels (stereo)
#define BITS_PER_SAMPLE 16      // Bit depth
#define SILENCE_THRESHOLD 0.01  // Silence threshold
#define SILENCE_DURATION 1.0    // Silence duration to stop recording
#define INDICATOR_WIDTH 20      // Level indicator width
#define BUFFER_FRAMES 4096      // Buffer size in frames

volatile int should_stop = 0;

// Signal handler
void handle_signal(int signal) {
    switch (signal) {
        case SIGINT:
        case SIGTERM:
            printf("\nReceived termination signal. Stopping recording...\n");
            should_stop = 1;
            break;
    }
}

// Calculate RMS (Root Mean Square) of the signal
double calculate_rms(const int16_t* buffer, int num_samples) {
    double sum = 0.0;
    for (int i = 0; i < num_samples; i++) {
        double normalized = buffer[i] / 32768.0;
        sum += normalized * normalized;
    }
    return sqrt(sum / num_samples);
}

// Thread for level indicator display
typedef struct {
    int16_t* buffer;
    int buffer_size;
    double level_left;
    double level_right;
    pthread_mutex_t lock;
    int running;
    pthread_t thread;
} LevelIndicatorThread;

// Thread function for level indicator
void* level_indicator_thread_func(void* arg) {
    LevelIndicatorThread* indicator = (LevelIndicatorThread*)arg;
    
    while (indicator->running) {
        pthread_mutex_lock(&indicator->lock);
        
        double left_sum = 0.0, right_sum = 0.0;
        int left_count = 0, right_count = 0;
        
        for (int i = 0; i < indicator->buffer_size; i++) {
            double normalized = indicator->buffer[i] / 32768.0;
            if (i % 2 == 0) {
                left_sum += fabs(normalized);
                left_count++;
            } else {
                right_sum += fabs(normalized);
                right_count++;
            }
        }
        
        indicator->level_left = left_count > 0 ? left_sum / left_count : 0;
        indicator->level_right = right_count > 0 ? right_sum / right_count : 0;
        
        pthread_mutex_unlock(&indicator->lock);
        
        printf("\rSignal level: L=%.2f%% R=%.2f%%",
               indicator->level_left * 100, indicator->level_right * 100);
        fflush(stdout);
        
        usleep(100000); // Update every 100 ms
    }
    
    return NULL;
}

// Initialize level indicator thread
LevelIndicatorThread* init_level_indicator(int buffer_size) {
    LevelIndicatorThread* indicator = (LevelIndicatorThread*)malloc(sizeof(LevelIndicatorThread));
    if (!indicator) return NULL;
    
    indicator->buffer = (int16_t*)malloc(buffer_size * sizeof(int16_t));
    if (!indicator->buffer) {
        free(indicator);
        return NULL;
    }
    
    indicator->buffer_size = buffer_size;
    indicator->level_left = 0.0;
    indicator->level_right = 0.0;
    indicator->running = 1;
    pthread_mutex_init(&indicator->lock, NULL);
    
    if (pthread_create(&indicator->thread, NULL, level_indicator_thread_func, indicator) != 0) {
        free(indicator->buffer);
        free(indicator);
        return NULL;
    }
    
    return indicator;
}

// Update level indicator buffer
void update_level_indicator(LevelIndicatorThread* indicator, const int16_t* buffer, int buffer_size) {
    pthread_mutex_lock(&indicator->lock);
    memcpy(indicator->buffer, buffer, buffer_size * sizeof(int16_t));
    pthread_mutex_unlock(&indicator->lock);
}

// Stop and destroy level indicator thread
void destroy_level_indicator(LevelIndicatorThread* indicator) {
    if (indicator) {
        indicator->running = 0;
        pthread_join(indicator->thread, NULL);
        pthread_mutex_destroy(&indicator->lock);
        free(indicator->buffer);
        free(indicator);
    }
}

// Write WAV header
void write_wav_header(FILE* file, unsigned int data_size, int sample_rate, 
                      int num_channels, int bits_per_sample) {
    unsigned int chunk_size = data_size + 36;
    unsigned short audio_format = 1; // PCM
    unsigned short block_align = num_channels * bits_per_sample / 8;
    unsigned int byte_rate = sample_rate * block_align;
    
    // RIFF header
    fwrite("RIFF", 4, 1, file);
    fwrite(&chunk_size, 4, 1, file);
    fwrite("WAVE", 4, 1, file);
    
    // fmt chunk
    fwrite("fmt ", 4, 1, file);
    unsigned int fmt_size = 16;
    fwrite(&fmt_size, 4, 1, file);
    fwrite(&audio_format, 2, 1, file);
    fwrite(&num_channels, 2, 1, file);
    fwrite(&sample_rate, 4, 1, file);
    fwrite(&byte_rate, 4, 1, file);
    fwrite(&block_align, 2, 1, file);
    fwrite(&bits_per_sample, 2, 1, file);
    
    // data chunk header
    fwrite("data", 4, 1, file);
    fwrite(&data_size, 4, 1, file);
}

// Update WAV header with final data size
void update_wav_header(FILE* file, unsigned int data_size) {
    unsigned int chunk_size = data_size + 36;
    
    fseek(file, 4, SEEK_SET);
    fwrite(&chunk_size, 4, 1, file);
    
    fseek(file, 40, SEEK_SET);
    fwrite(&data_size, 4, 1, file);
}

// Main recording function
void record_wav(const char* output_file, double silence_threshold, 
                int discard_silence, int num_channels) {
    snd_pcm_t* alsa_handle = NULL;
    snd_pcm_hw_params_t* alsa_params = NULL;
    int err;
    unsigned int rate = SAMPLE_RATE;
    snd_pcm_uframes_t frames = BUFFER_FRAMES;
    int buffer_size;
    int16_t* buffer = NULL;
    FILE* file = NULL;
    LevelIndicatorThread* indicator = NULL;
    time_t silence_start_time = 0;
    int is_silent = 0;
    int paused = 0;
    unsigned int data_size = 0;
    int bytes_written;
    
    // Open ALSA device for capture
    err = snd_pcm_open(&alsa_handle, "default", SND_PCM_STREAM_CAPTURE, 0);
    if (err < 0) {
        fprintf(stderr, "Error opening ALSA device: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    // Allocate hardware parameters
    snd_pcm_hw_params_malloc(&alsa_params);
    if (!alsa_params) {
        fprintf(stderr, "Error allocating memory for ALSA parameters\n");
        goto cleanup;
    }
    
    // Initialize hardware parameters
    err = snd_pcm_hw_params_any(alsa_handle, alsa_params);
    if (err < 0) {
        fprintf(stderr, "Error initializing ALSA parameters: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    // Set parameters
    err = snd_pcm_hw_params_set_access(alsa_handle, alsa_params, 
                                        SND_PCM_ACCESS_RW_INTERLEAVED);
    if (err < 0) {
        fprintf(stderr, "Error setting access type: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    err = snd_pcm_hw_params_set_format(alsa_handle, alsa_params, 
                                        SND_PCM_FORMAT_S16_LE);
    if (err < 0) {
        fprintf(stderr, "Error setting format: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    err = snd_pcm_hw_params_set_channels(alsa_handle, alsa_params, num_channels);
    if (err < 0) {
        fprintf(stderr, "Error setting channels: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    err = snd_pcm_hw_params_set_rate_near(alsa_handle, alsa_params, &rate, NULL);
    if (err < 0) {
        fprintf(stderr, "Error setting sample rate: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    err = snd_pcm_hw_params_set_buffer_size_near(alsa_handle, alsa_params, &frames);
    if (err < 0) {
        fprintf(stderr, "Error setting buffer size: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    // Apply parameters
    err = snd_pcm_hw_params(alsa_handle, alsa_params);
    if (err < 0) {
        fprintf(stderr, "Error applying parameters: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    printf("Used sample rate: %u Hz\n", rate);
    printf("Number of channels: %d\n", num_channels);
    printf("Buffer frames: %lu\n", frames);
    
    // Calculate buffer size
    buffer_size = frames * num_channels;
    buffer = (int16_t*)malloc(buffer_size * sizeof(int16_t));
    if (!buffer) {
        fprintf(stderr, "Error allocating buffer\n");
        goto cleanup;
    }
    
    // Create output file
    file = fopen(output_file, "wb");
    if (!file) {
        fprintf(stderr, "Error creating output file\n");
        goto cleanup;
    }
    
    // Write initial WAV header
    write_wav_header(file, 0, rate, num_channels, BITS_PER_SAMPLE);
    data_size = 0;
    
    // Initialize level indicator
    indicator = init_level_indicator(buffer_size);
    if (!indicator) {
        fprintf(stderr, "Warning: Could not start level indicator\n");
    }
    
    printf("Recording started. Press Ctrl+C to stop.\n");
    
    // Main recording loop
    while (!should_stop) {
        // Read data from ALSA
        err = snd_pcm_readi(alsa_handle, buffer, frames);
        if (err < 0) {
            fprintf(stderr, "Error reading from ALSA: %s\n", snd_strerror(err));
            break;
        }
        
        // Update level indicator
        if (indicator) {
            update_level_indicator(indicator, buffer, buffer_size);
        }
        
        // Check signal level
        if (discard_silence) {
            double rms = calculate_rms(buffer, buffer_size);
            is_silent = (rms < silence_threshold);
            
            if (is_silent) {
                if (silence_start_time == 0) {
                    silence_start_time = time(NULL);
                } else if (difftime(time(NULL), silence_start_time) >= SILENCE_DURATION) {
                    if (!paused) {
                        printf("\nSilence detected, pausing...\n");
                        paused = 1;
                    }
                }
            } else {
                if (paused) {
                    printf("\nSound detected, resuming recording...\n");
                    paused = 0;
                }
                silence_start_time = 0;
            }
        }
        
        // Write data to file if not paused
        if (!paused) {
            bytes_written = fwrite(buffer, sizeof(int16_t), buffer_size, file);
            data_size += bytes_written * sizeof(int16_t);
        }
    }
    
    printf("\nStopping recording...\n");
    
    // Update WAV header
    update_wav_header(file, data_size);
    
    // Truncate file if necessary
    long expected_size = data_size + 44;
    fseek(file, 0, SEEK_END);
    long actual_size = ftell(file);
    
    if (actual_size != expected_size) {
        fprintf(stderr, "Warning: File size mismatch. Truncating...\n");
        ftruncate(fileno(file), expected_size);
    }
    
cleanup:
    // Clean up resources
    if (indicator) {
        destroy_level_indicator(indicator);
    }
    if (file) {
        fclose(file);
    }
    if (buffer) {
        free(buffer);
    }
    if (alsa_params) {
        snd_pcm_hw_params_free(alsa_params);
    }
    if (alsa_handle) {
        snd_pcm_close(alsa_handle);
    }
}

// Print usage information
void print_usage(const char* program_name) {
    printf("Usage: %s <outputfile.wav> [silence_threshold] [discard_silence] [channels]\n",
           program_name);
    printf("  silence_threshold: silence threshold (default: 0.01)\n");
    printf("  discard_silence: discard silence (1 or 0, default: 1)\n");
    printf("  channels: number of channels (1 for mono, 2 for stereo, default: 2)\n");
}

int main(int argc, char* argv[]) {
    const char* output_file;
    double silence_threshold = SILENCE_THRESHOLD;
    int discard_silence = 1;
    int channels = NUM_CHANNELS;
    
    // Parse command line arguments
    if (argc < 2) {
        print_usage(argv[0]);
        return 1;
    }
    
    output_file = argv[1];
    
    if (argc >= 3) {
        silence_threshold = atof(argv[2]);
    }
    if (argc >= 4) {
        discard_silence = atoi(argv[3]);
    }
    if (argc >= 5) {
        channels = atoi(argv[4]);
        if (channels != 1 && channels != 2) {
            fprintf(stderr, "Error: channels must be 1 or 2\n");
            return 1;
        }
    }
    
    // Set up signal handlers
    signal(SIGINT, handle_signal);
    signal(SIGTERM, handle_signal);
    
    // Start recording
    record_wav(output_file, silence_threshold, discard_silence, channels);
    
    printf("Recording finished successfully.\n");
    return 0;
}