/*
 * MOC - music on console
 * Copyright (C) 2024 Takashi Yano <takashi.yano@nifty.ne.jp>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <strings.h>
#include <assert.h>

#define DEBUG

#include "common.h"
#include "decoder.h"
#include "io.h"
#include "log.h"
#include "files.h"

#include "../undefined.h"

#define SECTOR_SIZE 2352

struct cdda_data
{
	struct io_stream *stream;
	char rbuf[SECTOR_SIZE];
	int rbuf_len;
	int rbuf_pos;

	int ok; /* was this stream successfully opened? */
	struct decoder_error error;

	int duration;
};

static int buffer_length (const struct cdda_data *data)
{
	return data->rbuf_len - data->rbuf_pos;
}

static void *buffer_data (struct cdda_data *data)
{
	return data->rbuf + data->rbuf_pos;
}

static int buffer_fill (struct cdda_data *data)
{
	ssize_t n;

	if (data->rbuf_pos > 0) {
		data->rbuf_len = buffer_length (data);
		memmove (data->rbuf, data->rbuf + data->rbuf_pos, data->rbuf_len);
		data->rbuf_pos = 0;
	}

	if (data->rbuf_len == SECTOR_SIZE)
		return 1;

	n = io_read (data->stream, data->rbuf + data->rbuf_len, SECTOR_SIZE - data->rbuf_len);
	if (n == -1)
		return -1;
	if (n == 0)
		return 0;

	data->rbuf_len += n;
	return 1;
}

static inline void buffer_flush (struct cdda_data *data)
{
	data->rbuf_len = 0;
	data->rbuf_pos = 0;
}

static inline void buffer_consume (struct cdda_data *data, int n)
{
	assert (n <= buffer_length(data));

	data->rbuf_pos += n;
}

static struct cdda_data *cdda_open_internal (const char *fname)
{
	struct cdda_data *data;

	/* init private struct */
	data = xcalloc (1, sizeof *data);
	data->stream = io_open (fname, 1);
	
	if (!io_ok(data->stream)) {
		decoder_error (&data->error, ERROR_FATAL, 0,
				"Can't open CDDA file: %s", io_strerror(data->stream));
		return data;
	}

	/* find a frame */
	if (buffer_fill(data) <= 0) {
		decoder_error (&data->error, ERROR_FATAL, 0,
				"Not a valid (or unsupported) CDDA file");
		return data;
	}

	data->ok = 1;
	return data;
}

static void cdda_close (void *prv_data)
{
	struct cdda_data *data = (struct cdda_data *)prv_data;

	io_close (data->stream);
	decoder_error_clear (&data->error);
	free (data);
}

static void *cdda_open (const char *file)
{
	struct cdda_data *data;

	data = cdda_open_internal (file);

	if (data->ok) {
		off_t file_size = io_file_size (data->stream);
		data->duration = file_size / (SECTOR_SIZE * 75);
	}

	return data;
}

/* Fill info structure with data. No comments, only time. */
static void cdda_info (const char *file_name,
		struct file_tags *info,
		const int tags_sel)
{
	if (tags_sel & TAGS_TIME) {
		struct cdda_data *data = cdda_open_internal (file_name);
		off_t file_size = io_file_size (data->stream);
		info->time = file_size / (SECTOR_SIZE * 75);
		cdda_close (data);
	}
}

static int cdda_seek (void *void_data, int sec)
{
	struct cdda_data *data = (struct cdda_data *)void_data;
	off_t new_position = SECTOR_SIZE * 75L * sec;

	assert (sec >= 0);
	if (io_seek (data->stream, new_position, SEEK_SET) == -1) {
		logit ("seek to %"PRId64" failed", new_position);
		return -1;
	}
	return sec;
}

static int cdda_decode (void *prv_data, char *buf, int buf_len,
		struct sound_params *sound_params)
{
	struct cdda_data *data = (struct cdda_data *)prv_data;
	int count;

	decoder_error_clear (&data->error);
	buffer_fill (data);

	sound_params->channels = 2;
	sound_params->rate = 44100;
	sound_params->fmt = SFMT_S16 | SFMT_LE;

	count = MIN(buf_len, buffer_length (data));
	memcpy (buf, buffer_data (data), count);
	buffer_consume (data, count);

	return count;
}

static int cdda_get_bitrate (void *unused ATTR_UNUSED)
{
	return 44100 * 16 * 2 / 1000;
}

static int cdda_get_avg_bitrate (void *unused ATTR_UNUSED)
{
	return 44100 * 16 * 2 / 1000;
}

static int cdda_get_duration (void *prv_data)
{
	struct cdda_data *data = (struct cdda_data *)prv_data;

	return data->duration;
}

static void cdda_get_name (const char *unused ATTR_UNUSED, char buf[4])
{
	strcpy (buf, "CDA");
}

static int cdda_our_format_ext (const char *ext)
{
	return !strcasecmp (ext, "img") || !strcasecmp (ext, "bin");
}

static void cdda_get_error (void *prv_data, struct decoder_error *error)
{
	struct cdda_data *data = (struct cdda_data *)prv_data;

	decoder_error_copy (error, &data->error);
}

static struct decoder cdda_decoder = {
	DECODER_API_VERSION,
	NULL,
	NULL,
	cdda_open,
	NULL,
	NULL,
	cdda_close,
	cdda_decode,
	cdda_seek,
	cdda_info,
	cdda_get_bitrate,
	cdda_get_duration,
	cdda_get_error,
	cdda_our_format_ext,
	NULL,
	cdda_get_name,
	NULL,
	NULL,
	cdda_get_avg_bitrate
};

struct decoder *plugin_init (const struct decoder_undefs_t *undefs)
{
	copy_undefs (undefs);
	return &cdda_decoder;
}
