/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <syslog.h>

#include "audio_thread_log.h"
#include "byte_buffer.h"
#include "cras_fmt_conv.h"
#include "dev_stream.h"
#include "cras_audio_area.h"
#include "cras_mix.h"
#include "cras_shm.h"

/*
 * Sleep this much time past the buffer size to be sure at least
 * the buffer size is captured when the audio thread wakes up.
 */
static const unsigned int capture_extra_sleep_frames = 20;
/* Adjust device's sample rate by this step faster or slower. Used
 * to make sure multiple active device has stable buffer level.
 */
static const int coarse_rate_adjust_step = 3;

/*
 * Allow capture callback to fire this much earlier than the scheduled
 * next_cb_ts to avoid an extra wake of audio thread.
 */
static const struct timespec capture_callback_fuzz_ts = {
	.tv_sec = 0,
	.tv_nsec = 1000000, /* 1 ms. */
};

struct dev_stream *dev_stream_create(struct cras_rstream *stream,
				     unsigned int dev_id,
				     const struct cras_audio_format *dev_fmt,
				     void *dev_ptr,
				     struct timespec *cb_ts)
{
	struct dev_stream *out;
	struct cras_audio_format *stream_fmt = &stream->format;
	int rc = 0;
	unsigned int max_frames, dev_frames, buf_bytes;
	const struct cras_audio_format *ofmt;

	out = calloc(1, sizeof(*out));
	out->dev_id = dev_id;
	out->stream = stream;
	out->dev_rate = dev_fmt->frame_rate;

	if (stream->direction == CRAS_STREAM_OUTPUT) {
		max_frames = MAX(stream->buffer_frames,
				 cras_frames_at_rate(stream_fmt->frame_rate,
						     stream->buffer_frames,
						     dev_fmt->frame_rate));
		rc = config_format_converter(&out->conv,
					     stream->direction,
					     stream_fmt,
					     dev_fmt,
					     max_frames);
	} else {
		max_frames = MAX(stream->buffer_frames,
				 cras_frames_at_rate(dev_fmt->frame_rate,
						     stream->buffer_frames,
						     stream_fmt->frame_rate));
		rc = config_format_converter(&out->conv,
					     stream->direction,
					     dev_fmt,
					     stream_fmt,
					     max_frames);
	}
	if (rc) {
		free(out);
		return NULL;
	}

	ofmt = cras_fmt_conv_out_format(out->conv);

	dev_frames = (stream->direction == CRAS_STREAM_OUTPUT)
		? cras_fmt_conv_in_frames_to_out(out->conv,
						 stream->buffer_frames)
		: cras_fmt_conv_out_frames_to_in(out->conv,
						 stream->buffer_frames);

	out->conv_buffer_size_frames = 2 * MAX(dev_frames,
					       stream->buffer_frames);

	/* Create conversion buffer and area using the output format
	 * of the format converter. Note that this format might not be
	 * identical to stream_fmt for capture. */
	buf_bytes = out->conv_buffer_size_frames * cras_get_format_bytes(ofmt);
	out->conv_buffer = byte_buffer_create(buf_bytes);
	out->conv_area = cras_audio_area_create(ofmt->num_channels);

	cras_frames_to_time(cras_rstream_get_cb_threshold(stream),
			    stream_fmt->frame_rate,
			    &stream->sleep_interval_ts);
	stream->next_cb_ts = *cb_ts;

	if (stream->direction != CRAS_STREAM_OUTPUT) {
		struct timespec extra_sleep;

		cras_frames_to_time(capture_extra_sleep_frames,
				    stream->format.frame_rate, &extra_sleep);
		add_timespecs(&stream->next_cb_ts, &stream->sleep_interval_ts);
		add_timespecs(&stream->next_cb_ts, &extra_sleep);
	}

	cras_rstream_dev_attach(stream, dev_id, dev_ptr);

	return out;
}

void dev_stream_destroy(struct dev_stream *dev_stream)
{
	cras_rstream_dev_detach(dev_stream->stream, dev_stream->dev_id);
	if (dev_stream->conv) {
		cras_audio_area_destroy(dev_stream->conv_area);
		cras_fmt_conv_destroy(dev_stream->conv);
		byte_buffer_destroy(dev_stream->conv_buffer);
	}
	free(dev_stream);
}

void dev_stream_set_dev_rate(struct dev_stream *dev_stream,
			     unsigned int dev_rate,
			     double dev_rate_ratio,
			     double master_rate_ratio,
			     int coarse_rate_adjust)
{
	if (dev_stream->dev_id == dev_stream->stream->master_dev.dev_id) {
		cras_fmt_conv_set_linear_resample_rates(
				dev_stream->conv,
				dev_rate,
				dev_rate);
		cras_frames_to_time_precise(
			cras_rstream_get_cb_threshold(dev_stream->stream),
			dev_stream->stream->format.frame_rate * dev_rate_ratio,
			&dev_stream->stream->sleep_interval_ts);
	} else {
		double new_rate = dev_rate * dev_rate_ratio /
				master_rate_ratio +
				coarse_rate_adjust_step * coarse_rate_adjust;
		cras_fmt_conv_set_linear_resample_rates(
				dev_stream->conv,
				dev_rate,
				new_rate);
	}

}

int dev_stream_mix(struct dev_stream *dev_stream,
		   const struct cras_audio_format *fmt,
		   uint8_t *dst,
		   unsigned int num_to_write)
{
	struct cras_rstream *rstream = dev_stream->stream;
	uint8_t *src;
	uint8_t *target = dst;
	unsigned int fr_written, fr_read;
	unsigned int buffer_offset;
	int fr_in_buf;
	unsigned int num_samples;
	size_t frames = 0;
	unsigned int dev_frames;
	float mix_vol;

	fr_in_buf = dev_stream_playback_frames(dev_stream);
	if (fr_in_buf <= 0)
		return fr_in_buf;
	if (fr_in_buf < num_to_write)
		num_to_write = fr_in_buf;

	buffer_offset = cras_rstream_dev_offset(rstream, dev_stream->dev_id);

	/* Stream volume scaler. */
	mix_vol = cras_rstream_get_volume_scaler(dev_stream->stream);

	fr_written = 0;
	fr_read = 0;
	while (fr_written < num_to_write) {
		unsigned int read_frames;
		src = cras_rstream_get_readable_frames(
				rstream, buffer_offset + fr_read, &frames);
		if (frames == 0)
			break;
		if (cras_fmt_conversion_needed(dev_stream->conv)) {
			read_frames = frames;
			dev_frames = cras_fmt_conv_convert_frames(
					dev_stream->conv,
					src,
					dev_stream->conv_buffer->bytes,
					&read_frames,
					num_to_write - fr_written);
			src = dev_stream->conv_buffer->bytes;
		} else {
			dev_frames = MIN(frames, num_to_write - fr_written);
			read_frames = dev_frames;
		}
		num_samples = dev_frames * fmt->num_channels;
		cras_mix_add(fmt->format, target, src, num_samples, 1,
			     cras_rstream_get_mute(rstream), mix_vol);
		target += dev_frames * cras_get_format_bytes(fmt);
		fr_written += dev_frames;
		fr_read += read_frames;
	}

	cras_rstream_dev_offset_update(rstream, fr_read, dev_stream->dev_id);
	ATLOG(atlog, AUDIO_THREAD_DEV_STREAM_MIX,
				    fr_written, fr_read, 0);

	return fr_written;
}

/* Copy from the captured buffer to the temporary format converted buffer. */
static unsigned int capture_with_fmt_conv(struct dev_stream *dev_stream,
					  const uint8_t *source_samples,
					  unsigned int num_frames)
{
	const struct cras_audio_format *source_format;
	const struct cras_audio_format *dst_format;
	uint8_t *buffer;
	unsigned int total_read = 0;
	unsigned int write_frames;
	unsigned int read_frames;
	unsigned int source_frame_bytes;
	unsigned int dst_frame_bytes;

	source_format = cras_fmt_conv_in_format(dev_stream->conv);
	source_frame_bytes = cras_get_format_bytes(source_format);
	dst_format = cras_fmt_conv_out_format(dev_stream->conv);
	dst_frame_bytes = cras_get_format_bytes(dst_format);

	dev_stream->conv_area->num_channels = dst_format->num_channels;

	while (total_read < num_frames) {
		buffer = buf_write_pointer_size(dev_stream->conv_buffer,
						&write_frames);
		write_frames /= dst_frame_bytes;
		if (write_frames == 0)
			break;

		read_frames = num_frames - total_read;
		write_frames = cras_fmt_conv_convert_frames(
				dev_stream->conv,
				source_samples,
				buffer,
				&read_frames,
				write_frames);
		total_read += read_frames;
		source_samples += read_frames * source_frame_bytes;
		buf_increment_write(dev_stream->conv_buffer,
				    write_frames * dst_frame_bytes);
	}

	return total_read;
}

/* Copy from the converted buffer to the stream shm.  These have the same format
 * at this point. */
static unsigned int capture_copy_converted_to_stream(
		struct dev_stream *dev_stream,
		struct cras_rstream *rstream,
		float software_gain_scaler)
{
	struct cras_audio_shm *shm;
	uint8_t *stream_samples;
	uint8_t *converted_samples;
	unsigned int num_frames;
	unsigned int total_written = 0;
	unsigned int write_frames;
	unsigned int frame_bytes;
	unsigned int offset;
	const struct cras_audio_format *fmt;

	shm = cras_rstream_input_shm(rstream);

	fmt = cras_fmt_conv_out_format(dev_stream->conv);
	frame_bytes = cras_get_format_bytes(fmt);

	offset = cras_rstream_dev_offset(rstream, dev_stream->dev_id);

	stream_samples = cras_shm_get_writeable_frames(
			shm,
			cras_rstream_get_cb_threshold(rstream),
			&rstream->audio_area->frames);
	num_frames = MIN(rstream->audio_area->frames - offset,
			 buf_queued_bytes(dev_stream->conv_buffer) /
							frame_bytes);

	ATLOG(atlog, AUDIO_THREAD_CONV_COPY,
				    shm->area->write_buf_idx,
				    rstream->audio_area->frames,
				    offset);

	while (total_written < num_frames) {
		converted_samples =
			buf_read_pointer_size(dev_stream->conv_buffer,
					      &write_frames);
		write_frames /= frame_bytes;
		write_frames = MIN(write_frames, num_frames - total_written);

		cras_audio_area_config_buf_pointers(dev_stream->conv_area,
						    fmt,
						    converted_samples);
		cras_audio_area_config_channels(dev_stream->conv_area, fmt);
		dev_stream->conv_area->frames = write_frames;

		cras_audio_area_config_buf_pointers(rstream->audio_area,
						    &rstream->format,
						    stream_samples);

		cras_audio_area_copy(rstream->audio_area, offset,
				     &rstream->format,
				     dev_stream->conv_area, 0,
				     software_gain_scaler);

		buf_increment_read(dev_stream->conv_buffer,
				   write_frames * frame_bytes);
		total_written += write_frames;
		cras_rstream_dev_offset_update(rstream, write_frames,
					       dev_stream->dev_id);
		offset = cras_rstream_dev_offset(rstream, dev_stream->dev_id);
	}

	ATLOG(atlog, AUDIO_THREAD_CAPTURE_WRITE,
				    rstream->stream_id,
				    total_written,
				    cras_shm_frames_written(shm));
	return total_written;
}

unsigned int dev_stream_capture(struct dev_stream *dev_stream,
			const struct cras_audio_area *area,
			unsigned int area_offset,
			float software_gain_scaler)
{
	struct cras_rstream *rstream = dev_stream->stream;
	struct cras_audio_shm *shm;
	uint8_t *stream_samples;
	unsigned int nread;

	/* Check if format conversion is needed. */
	if (cras_fmt_conversion_needed(dev_stream->conv)) {
		unsigned int format_bytes;

		format_bytes = cras_get_format_bytes(
				cras_fmt_conv_in_format(dev_stream->conv));
		nread = capture_with_fmt_conv(
			dev_stream,
			area->channels[0].buf + area_offset * format_bytes,
			area->frames - area_offset);
		capture_copy_converted_to_stream(dev_stream, rstream,
						 software_gain_scaler);
	} else {
		unsigned int offset =
			cras_rstream_dev_offset(rstream, dev_stream->dev_id);

		/* Set up the shm area and copy to it. */
		shm = cras_rstream_input_shm(rstream);
		stream_samples = cras_shm_get_writeable_frames(
				shm,
				cras_rstream_get_cb_threshold(rstream),
				&rstream->audio_area->frames);
		cras_audio_area_config_buf_pointers(rstream->audio_area,
						    &rstream->format,
						    stream_samples);

		nread = cras_audio_area_copy(rstream->audio_area, offset,
					     &rstream->format, area,
					     area_offset,
					     software_gain_scaler);

		ATLOG(atlog, AUDIO_THREAD_CAPTURE_WRITE,
					    rstream->stream_id,
					    nread,
					    cras_shm_frames_written(shm));
		cras_rstream_dev_offset_update(rstream, nread,
					       dev_stream->dev_id);
	}

	return nread;
}

int dev_stream_attached_devs(const struct dev_stream *dev_stream)
{
	return dev_stream->stream->num_attached_devs;
}

void dev_stream_update_frames(const struct dev_stream *dev_stream)
{
	cras_rstream_update_queued_frames(dev_stream->stream);
}

int dev_stream_playback_frames(const struct dev_stream *dev_stream)
{
	int frames;

	frames = cras_rstream_playable_frames(dev_stream->stream,
					      dev_stream->dev_id);
	if (frames < 0)
		return frames;

	if (!dev_stream->conv)
		return frames;

	return cras_fmt_conv_in_frames_to_out(dev_stream->conv, frames);
}

unsigned int dev_stream_cb_threshold(const struct dev_stream *dev_stream)
{
	const struct cras_rstream *rstream = dev_stream->stream;
	unsigned int cb_threshold = cras_rstream_get_cb_threshold(rstream);

	if (rstream->direction == CRAS_STREAM_OUTPUT)
		return cras_fmt_conv_in_frames_to_out(dev_stream->conv,
						      cb_threshold);
	else
		return cras_fmt_conv_out_frames_to_in(dev_stream->conv,
						      cb_threshold);
}

unsigned int dev_stream_capture_avail(const struct dev_stream *dev_stream)
{
	struct cras_audio_shm *shm;
	struct cras_rstream *rstream = dev_stream->stream;
	unsigned int frames_avail;
	unsigned int conv_buf_level;
	unsigned int format_bytes;
	unsigned int wlimit;
	unsigned int dev_offset =
		cras_rstream_dev_offset(rstream, dev_stream->dev_id);

	shm = cras_rstream_input_shm(rstream);

	wlimit = cras_rstream_get_max_write_frames(rstream);
	wlimit -= dev_offset;
	cras_shm_get_writeable_frames(shm, wlimit, &frames_avail);

	if (!dev_stream->conv)
		return frames_avail;

	format_bytes = cras_get_format_bytes(
			cras_fmt_conv_out_format(dev_stream->conv));

	/* Sample rate conversion may cause some sample left in conv_buffer
	 * take this buffer into account. */
	conv_buf_level = buf_queued_bytes(dev_stream->conv_buffer) /
			format_bytes;
	if (frames_avail < conv_buf_level)
		return 0;
	else
		frames_avail -= conv_buf_level;

	frames_avail = MIN(frames_avail,
			   buf_available_bytes(dev_stream->conv_buffer) /
					format_bytes);

	return cras_fmt_conv_out_frames_to_in(dev_stream->conv, frames_avail);
}

/* TODO(dgreid) remove this hack to reset the time if needed. */
static void check_next_wake_time(struct dev_stream *dev_stream)
{
	struct cras_rstream *rstream = dev_stream->stream;
	struct timespec now;

	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
	if (timespec_after(&now, &rstream->next_cb_ts)) {
		rstream->next_cb_ts = now;
		add_timespecs(&rstream->next_cb_ts,
			      &rstream->sleep_interval_ts);
	}
}

int dev_stream_playback_update_rstream(struct dev_stream *dev_stream)
{
	cras_rstream_update_output_read_pointer(dev_stream->stream);
	return 0;
}

static int late_enough_for_capture_callback(struct dev_stream *dev_stream)
{
	struct timespec now;
	struct cras_rstream *rstream = dev_stream->stream;
	clock_gettime(CLOCK_MONOTONIC_RAW, &now);
	add_timespecs(&now, &capture_callback_fuzz_ts);
	return timespec_after(&now, &rstream->next_cb_ts);
}

int dev_stream_capture_update_rstream(struct dev_stream *dev_stream)
{
	struct cras_rstream *rstream = dev_stream->stream;
	unsigned int frames_ready = cras_rstream_get_cb_threshold(rstream);
	int rc;

	cras_rstream_update_input_write_pointer(rstream);

	/*
	 * For stream without BULK_AUDIO_OK flag, if it isn't time for
	 * this stream then skip it.
	 */
	if (!(rstream->flags & BULK_AUDIO_OK) &&
	    !late_enough_for_capture_callback(dev_stream))
		return 0;

	/* If there is not enough data for one callback, skip it. */
	if (!cras_rstream_input_level_met(rstream))
		return 0;

	/* Enough data for this stream. */
	if (rstream->flags & BULK_AUDIO_OK)
		frames_ready = cras_rstream_level(rstream);

	ATLOG(atlog, AUDIO_THREAD_CAPTURE_POST,
				    rstream->stream_id,
				    frames_ready,
				    rstream->shm.area->read_buf_idx);

	rc = cras_rstream_audio_ready(rstream, frames_ready);

	if (rc < 0)
		return rc;

	/* Update next callback time according to perfect schedule. */
	add_timespecs(&rstream->next_cb_ts,
		      &rstream->sleep_interval_ts);
	/* Reset schedule if the schedule is missed. */
	check_next_wake_time(dev_stream);

	return 0;
}

void cras_set_playback_timestamp(size_t frame_rate,
				 size_t frames,
				 struct cras_timespec *ts)
{
	cras_clock_gettime(CLOCK_MONOTONIC_RAW, ts);

	/* For playback, want now + samples left to be played.
	 * ts = time next written sample will be played to DAC,
	 */
	ts->tv_nsec += frames * 1000000000ULL / frame_rate;
	while (ts->tv_nsec > 1000000000ULL) {
		ts->tv_sec++;
		ts->tv_nsec -= 1000000000ULL;
	}
}

void cras_set_capture_timestamp(size_t frame_rate,
				size_t frames,
				struct cras_timespec *ts)
{
	long tmp;

	cras_clock_gettime(CLOCK_MONOTONIC_RAW, ts);

	/* For capture, now - samples left to be read.
	 * ts = time next sample to be read was captured at ADC.
	 */
	tmp = frames * (1000000000L / frame_rate);
	while (tmp > 1000000000L) {
		tmp -= 1000000000L;
		ts->tv_sec--;
	}
	if (ts->tv_nsec >= tmp)
		ts->tv_nsec -= tmp;
	else {
		tmp -= ts->tv_nsec;
		ts->tv_nsec = 1000000000L - tmp;
		ts->tv_sec--;
	}
}

void dev_stream_set_delay(const struct dev_stream *dev_stream,
			  unsigned int delay_frames)
{
	struct cras_rstream *rstream = dev_stream->stream;
	struct cras_audio_shm *shm;
	unsigned int stream_frames;

	if (rstream->direction == CRAS_STREAM_OUTPUT) {
		shm = cras_rstream_output_shm(rstream);
		stream_frames = cras_fmt_conv_out_frames_to_in(dev_stream->conv,
							       delay_frames);
		cras_set_playback_timestamp(rstream->format.frame_rate,
					    stream_frames +
						cras_shm_get_frames(shm),
					    &shm->area->ts);
	} else {
		shm = cras_rstream_input_shm(rstream);
		stream_frames = cras_fmt_conv_in_frames_to_out(dev_stream->conv,
							       delay_frames);
		if (cras_shm_frames_written(shm) == 0)
			cras_set_capture_timestamp(
					rstream->format.frame_rate,
					stream_frames,
					&shm->area->ts);
	}
}

int dev_stream_can_fetch(struct dev_stream *dev_stream)
{
	struct cras_rstream *rstream = dev_stream->stream;
	struct cras_audio_shm *shm;

	shm = cras_rstream_output_shm(rstream);

	/* Don't fetch if the previous request hasn't got response. */
	return !cras_shm_callback_pending(shm) &&
	       cras_shm_is_buffer_available(shm);
}

int dev_stream_request_playback_samples(struct dev_stream *dev_stream,
					const struct timespec *now)
{
	struct cras_rstream *rstream = dev_stream->stream;
	int rc;

	rc = cras_rstream_request_audio(dev_stream->stream, now);
	if (rc < 0)
		return rc;

	add_timespecs(&rstream->next_cb_ts,
		      &rstream->sleep_interval_ts);
	check_next_wake_time(dev_stream);
	cras_shm_set_callback_pending(cras_rstream_output_shm(rstream), 1);

	return 0;
}

int dev_stream_poll_stream_fd(const struct dev_stream *dev_stream)
{
	const struct cras_rstream *stream = dev_stream->stream;

	if (!stream_uses_output(stream) ||
	    !cras_shm_callback_pending(&stream->shm) ||
	    cras_rstream_get_is_draining(stream))
		return -1;

	return stream->fd;
}

/*
 * Needed frames from this device such that written frames in shm meets
 * cb_threshold.
 */
static int get_input_needed_frames(struct dev_stream *dev_stream,
				   unsigned int curr_level)
{
	struct cras_rstream *rstream = dev_stream->stream;
	unsigned int rstream_level = cras_rstream_level(rstream);
	unsigned int dev_offset = cras_rstream_dev_offset(
			rstream, dev_stream->dev_id);
	unsigned int needed_for_stream;

	/*
	 * rstream_level + def_offset is the number of frames written to shm
	 * from this device.
	 */
	if (rstream_level + dev_offset > rstream->cb_threshold) {
		/* Enough frames from this device for this stream. */
		return 0;
	}

	/*
	 * Needed number of frames in shm such that written frames in shm meets
	 * cb_threshold.
	 */
	needed_for_stream = rstream->cb_threshold - rstream_level - dev_offset;

	/* Convert the number of frames from stream format to device format. */
	return cras_fmt_conv_out_frames_to_in(dev_stream->conv,
					      needed_for_stream);

}

/*
 * Gets proper wake up time for an input stream. It considers both
 * time for samples to reach one callback level, and the time for next callback.
 */
static int get_input_wake_time(struct dev_stream *dev_stream,
			       unsigned int curr_level,
			       struct timespec *level_tstamp,
			       struct timespec *wake_time_out)
{
	struct cras_rstream *rstream = dev_stream->stream;
	struct timespec time_for_sample;
	int needed_frames_from_device;

	needed_frames_from_device = get_input_needed_frames(
			dev_stream, curr_level);

	/*
	 * If current frames in the device can provide needed amount for stream,
	 * there is no need to wait.
	 */
	if (curr_level >= needed_frames_from_device)
		needed_frames_from_device = 0;

	else
		needed_frames_from_device -= curr_level;

	cras_frames_to_time(needed_frames_from_device,
			    dev_stream->dev_rate,
			    &time_for_sample);

	add_timespecs(&time_for_sample, level_tstamp);

	/* Select the time that is later so both sample and time conditions
	 * are met. */
	if (timespec_after(&time_for_sample, &rstream->next_cb_ts)) {
		*wake_time_out =  time_for_sample;
	} else {
		*wake_time_out =  rstream->next_cb_ts;
	}

	return 0;
}

int dev_stream_wake_time(struct dev_stream *dev_stream,
			 unsigned int curr_level,
			 struct timespec *level_tstamp,
			 struct timespec *wake_time_out)
{
	if (dev_stream->stream->direction == CRAS_STREAM_OUTPUT) {
		/*
                 * TODO(cychiang) Implement the method for output stream.
		 * The logic should be similar to what
		 * get_next_stream_wake_from_list in audio_thread.c is doing.
		 */
		return -EINVAL;
	}

	return get_input_wake_time(dev_stream, curr_level, level_tstamp,
				   wake_time_out);
}