
#include "track.h"
#include "log.h"

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void tracklist_init(TrackList *tl) {
    tl->num_tracks = 0;
    pthread_mutex_init(&tl->mutex, NULL);
}

int tracklist_add_track(TrackList *tl, const char *name, int16_t *samples,
                         size_t num_samples, uint32_t sample_rate) {
    pthread_mutex_lock(&tl->mutex);

    if (tl->num_tracks >= MAX_TRACKS) {
        pthread_mutex_unlock(&tl->mutex);
        LOG_WARN("track list full (max %d), dropping new track '%s'", MAX_TRACKS, name);
        return -1;
    }

    int idx = tl->num_tracks;
    Track *t = &tl->tracks[idx];
    snprintf(t->name, sizeof(t->name), "%s", name);
    t->clip.samples = samples;
    t->clip.num_samples = num_samples;
    t->clip.sample_rate = sample_rate;
    atomic_init(&t->muted, false);
    tl->num_tracks++;

    pthread_mutex_unlock(&tl->mutex);
    return idx;
}

void tracklist_lock(TrackList *tl) {
    pthread_mutex_lock(&tl->mutex);
}

void tracklist_unlock(TrackList *tl) {
    pthread_mutex_unlock(&tl->mutex);
}

int tracklist_delete_track(TrackList *tl, int idx) {
    pthread_mutex_lock(&tl->mutex);
    if (idx < 0 || idx >= tl->num_tracks) {
        pthread_mutex_unlock(&tl->mutex);
        return -1;
    }
    free(tl->tracks[idx].clip.samples);
    for (int i = idx; i < tl->num_tracks - 1; i++)
        tl->tracks[i] = tl->tracks[i + 1];
    tl->num_tracks--;
    pthread_mutex_unlock(&tl->mutex);
    return 0;
}

int tracklist_replace_clip(TrackList *tl, int idx, int16_t *samples,
                            size_t num_samples, uint32_t sample_rate) {
    pthread_mutex_lock(&tl->mutex);
    if (idx < 0 || idx >= tl->num_tracks) {
        pthread_mutex_unlock(&tl->mutex);
        free(samples); /* caller handed us ownership; nowhere to put it, so free it here */
        return -1;
    }
    free(tl->tracks[idx].clip.samples);
    tl->tracks[idx].clip.samples = samples;
    tl->tracks[idx].clip.num_samples = num_samples;
    tl->tracks[idx].clip.sample_rate = sample_rate;
    pthread_mutex_unlock(&tl->mutex);
    return idx;
}

void tracklist_clear(TrackList *tl) {
    pthread_mutex_lock(&tl->mutex);
    for (int i = 0; i < tl->num_tracks; i++)
        free(tl->tracks[i].clip.samples);
    tl->num_tracks = 0;
    pthread_mutex_unlock(&tl->mutex);
}

int tracklist_move_track(TrackList *tl, int idx, int direction) {
    pthread_mutex_lock(&tl->mutex);
    int target = idx + direction;
    if (idx < 0 || idx >= tl->num_tracks || target < 0 || target >= tl->num_tracks) {
        pthread_mutex_unlock(&tl->mutex);
        return -1;
    }
    Track tmp = tl->tracks[idx];
    tl->tracks[idx] = tl->tracks[target];
    tl->tracks[target] = tmp;
    pthread_mutex_unlock(&tl->mutex);
    return target;
}

int tracklist_move_block(TrackList *tl, int *lo, int *hi, int direction) {
    pthread_mutex_lock(&tl->mutex);
    int l = *lo, h = *hi;
    if (l < 0 || h < l || h >= tl->num_tracks) {
        pthread_mutex_unlock(&tl->mutex);
        return -1;
    }
    if (direction < 0) {
        if (l - 1 < 0) {
            pthread_mutex_unlock(&tl->mutex);
            return -1;
        }
        Track tmp = tl->tracks[l - 1];
        for (int i = l - 1; i < h; i++)
            tl->tracks[i] = tl->tracks[i + 1];
        tl->tracks[h] = tmp;
        *lo = l - 1;
        *hi = h - 1;
    } else if (direction > 0) {
        if (h + 1 >= tl->num_tracks) {
            pthread_mutex_unlock(&tl->mutex);
            return -1;
        }
        Track tmp = tl->tracks[h + 1];
        for (int i = h + 1; i > l; i--)
            tl->tracks[i] = tl->tracks[i - 1];
        tl->tracks[l] = tmp;
        *lo = l + 1;
        *hi = h + 1;
    } else {
        pthread_mutex_unlock(&tl->mutex);
        return -1; /* direction == 0 -- nothing to do */
    }
    pthread_mutex_unlock(&tl->mutex);
    return 0;
}

int tracklist_rename_track(TrackList *tl, int idx, const char *new_name) {
    pthread_mutex_lock(&tl->mutex);
    if (idx < 0 || idx >= tl->num_tracks) {
        pthread_mutex_unlock(&tl->mutex);
        return -1;
    }
    snprintf(tl->tracks[idx].name, sizeof(tl->tracks[idx].name), "%s", new_name);
    pthread_mutex_unlock(&tl->mutex);
    return 0;
}

void tracklist_free(TrackList *tl) {
    pthread_mutex_lock(&tl->mutex);
    for (int i = 0; i < tl->num_tracks; i++)
        free(tl->tracks[i].clip.samples);
    tl->num_tracks = 0;
    pthread_mutex_unlock(&tl->mutex);
    pthread_mutex_destroy(&tl->mutex);
}

/* Shared clamping: a region selection's [start, end) can legitimately
 * run past a shorter track's actual length (e.g. a whole-project
 * region copy against tracks of different lengths) -- clamp rather
 * than treat that as an error. Returns 0 (and leaves *start==*end) if
 * the clamped region is empty. */
static int clamp_region(const Clip *clip, size_t *start, size_t *end) {
    size_t n = clip->num_samples;
    if (*start > n) *start = n;
    if (*end > n) *end = n;
    if (*end < *start) *end = *start;
    return (*end > *start);
}

int track_clip_copy_region(const Clip *clip, size_t start, size_t end,
                            int16_t **out_samples, size_t *out_count) {
    *out_samples = NULL;
    *out_count = 0;
    if (!clamp_region(clip, &start, &end))
        return 0; /* empty region -- not an error, just nothing to copy */

    size_t count = end - start;
    int16_t *buf = malloc(count * sizeof(int16_t));
    if (!buf)
        return -1;
    memcpy(buf, clip->samples + start, count * sizeof(int16_t));
    *out_samples = buf;
    *out_count = count;
    return 0;
}

int track_clip_cut_region(Clip *clip, size_t start, size_t end) {
    if (!clamp_region(clip, &start, &end))
        return 0; /* empty region -- nothing to cut */

    size_t count = end - start;
    size_t tail = clip->num_samples - end;
    if (tail > 0)
        memmove(clip->samples + start, clip->samples + end, tail * sizeof(int16_t));
    size_t new_count = clip->num_samples - count;

    if (new_count == 0) {
        free(clip->samples);
        clip->samples = NULL;
        clip->num_samples = 0;
        return 0;
    }
    int16_t *shrunk = realloc(clip->samples, new_count * sizeof(int16_t));
    if (!shrunk && new_count > 0)
        return -1; /* realloc to a smaller size failing is essentially never observed in practice,
                    * but handle it rather than silently corrupting num_samples */
    clip->samples = shrunk;
    clip->num_samples = new_count;
    return 0;
}

int track_clip_insert_region(Clip *clip, size_t at, const int16_t *samples, size_t count) {
    if (count == 0)
        return 0;

    /* `at` past the clip's current length used to clamp back to the
     * true end -- that made it impossible to paste/insert anywhere
     * except immediately past wherever a track happened to end. Now
     * it auto-extends with real silence out to `at` first, so content
     * (or an explicit whitespace-insertion region) can land anywhere
     * the cursor sits, arbitrarily far past the current end. */
    size_t pad = (at > clip->num_samples) ? (at - clip->num_samples) : 0;
    size_t real_at = pad ? clip->num_samples : at;
    size_t new_count = clip->num_samples + pad + count;

    int16_t *grown = realloc(clip->samples, new_count * sizeof(int16_t));
    if (!grown)
        return -1;
    clip->samples = grown;

    if (pad > 0) {
        memset(clip->samples + clip->num_samples, 0, pad * sizeof(int16_t));
    } else {
        size_t tail = clip->num_samples - real_at;
        if (tail > 0)
            memmove(clip->samples + real_at + count, clip->samples + real_at, tail * sizeof(int16_t));
    }
    memcpy(clip->samples + real_at + pad, samples, count * sizeof(int16_t));
    clip->num_samples = new_count;
    return 0;
}

int track_clip_swap_adjacent(Clip *clip, size_t a0, size_t mid, size_t b1) {
    if (a0 >= mid || mid >= b1 || b1 > clip->num_samples)
        return -1;

    size_t len_a = mid - a0;
    size_t len_b = b1 - mid;
    size_t total = len_a + len_b;
    int16_t *tmp = malloc(total * sizeof(int16_t));
    if (!tmp)
        return -1;

    /* New order: the old second region first, then the old first
     * region -- same total length, just reordered. */
    memcpy(tmp, clip->samples + mid, len_b * sizeof(int16_t));
    memcpy(tmp + len_b, clip->samples + a0, len_a * sizeof(int16_t));
    memcpy(clip->samples + a0, tmp, total * sizeof(int16_t));

    free(tmp);
    return 0;
}

