/* -*- Mode: c++ -*- */
/***************************************************************************
 *            oss.cc
 *
 *  Tue Apr 11 19:42:45 CET 2017
 *  Copyright 2017 Goran Mekić
 *  meka@tilda.center
 ****************************************************************************/

/*
 *  This file is part of DrumGizmo.
 *
 *  DrumGizmo is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  DrumGizmo 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with DrumGizmo; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#include "oss.h"
#include <sys/soundcard.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <cstring>

#include <config.h>

OSSOutputEngine::OSSOutputEngine()
	: dev{"/dev/dsp"}
	, num_channels{NUM_CHANNELS}
	, srate{44100}
	, format{AFMT_S32_NE}
	, data{}
	, max_fragments{4}
	, fragment_size{8}
	, buffer_size{1024}
{
	data.clear();
	data.resize(1024 * num_channels);
}

bool OSSOutputEngine::init(const Channels& channels)
{
	std::size_t tmp, mode = O_WRONLY, fragments;
	num_channels = channels.size();

	if((fd = open(dev.data(), mode, 0)) == -1)
	{
		std::cerr << dev.data() << ' ' << std::strerror(errno) << std::endl;
		return false;
	}

	fragments = max_fragments << 16 | fragment_size;
	tmp = fragments;
	if(ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
	{
		std::cerr << "Can not set buffer size: ";
		std::cerr << std::strerror(errno) << std::endl;
		return false;
	}
	if(tmp != fragments)
	{
		std::size_t real_fragment_size = fragments & 0xffff;
		std::size_t real_max_fragments = (fragments - real_fragment_size) >> 16;
		std::cerr << "Values corrected to:" << std::endl;
		std::cerr << "  fragment_size: " << real_fragment_size << std::endl;
		std::cerr << "  max_fragments: " << real_max_fragments << std::endl;
	}

	audio_buf_info info;
	if(ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) == -1)
	{
		std::cerr << "Can not get buffer info: ";
		std::cerr << std::strerror(errno) << std::endl;
		return false;
	}
	buffer_size = info.bytes / sizeof(decltype (data)::value_type);

	tmp = format;
	if(ioctl(fd, SNDCTL_DSP_SETFMT, &tmp) == -1 || tmp != format)
	{
		std::cerr << "Setting audio format failed " << std::strerror(errno);
		std::cerr << std::endl;
		return false;
	}

	tmp = num_channels;
	if(ioctl(fd, SNDCTL_DSP_CHANNELS, &tmp) == -1 || tmp != num_channels)
	{
		std::cerr << "Can not set number of channels to " << num_channels;
		std::cerr << ": " << std::strerror(errno) << std::endl;
		return false;
	}

	tmp = srate;
	if(ioctl(fd, SNDCTL_DSP_SPEED, &tmp) == -1 || tmp != srate)
	{
		std::cerr << "Can not set sampling frequency to " << srate << ": ";
		std::cerr << std::strerror(errno) << std::endl;
		return false;
	}

	return true;
}

void OSSOutputEngine::setParm(const std::string& parm, const std::string& value)
{
	if(parm == "dev")
	{
		dev = value;
	}
	else if(parm == "srate")
	{
		try
		{
			srate = std::stoi(value);
		}
		catch(...)
		{
			std::cerr << "[OSSOutputEngine] Invalid samplerate ";
			std::cerr << value << std::endl;
		}
	}
	else if(parm == "max_fragments")
	{
		try
		{
			max_fragments = std::stoi(value);
		}
		catch(...)
		{
			std::cerr << "[OSSOutputEngine] Invalid max_fragments ";
			std::cerr << value << std::endl;
		}
		if(max_fragments < 2)
		{
			std::cerr << "[OSSoutputEngine] max_fragments must be at least 2\n";
			std::cerr << "Setting max_fragments to 2" << std::endl;
			max_fragments = 2;
		}
	}
	else if(parm == "fragment_size")
	{
		try
		{
			fragment_size = std::stoi(value);
		}
		catch(...)
		{
			std::cerr << "[OSSOutputEngine] Invalid fragment_size ";
			std::cerr << value << std::endl;
		}
		if(fragment_size < 4)
		{
			fragment_size = 4;
			std::cerr << "[OSSoutputEngine] fragment_size must be at least ";
			std::cerr << fragment_size << std::endl;
			std::cerr << "Setting fragment_size to ";
			std::cerr << fragment_size << std::endl;
		}
		else if(fragment_size > 0xffff)
		{
			fragment_size = 0xffff;
			std::cerr << "[OSSoutputEngine] fragment_size must be at most ";
			std::cerr << fragment_size << std::endl;
			std::cerr << "Setting fragment_size to ";
			std::cerr << fragment_size << std::endl;
		}
	}
	else
	{
		std::cerr << "[OSSOutputEngine] Unsupported parameter '";
		std::cerr << parm << std::endl;
	}
}

void OSSOutputEngine::pre(size_t nsamples)
{
	data.resize(nsamples * num_channels);
}

void OSSOutputEngine::run(int ch, sample_t* samples, size_t nsamples)
{
	// Convert to int format and
	// write channel data in interleaved buffer
	std::int32_t sample;
	for(size_t i = 0; i < nsamples; ++i)
	{
		// Hard clip if needed
		if(samples[i] > 1)
		{
			sample = INT32_MAX;
		}
		else if(samples[i] < -1)
		{
			sample = -INT32_MAX;
		}
		else
		{
			sample = samples[i] * INT32_MAX;
		}
		data[i * num_channels + ch] = sample;
	}
}

void OSSOutputEngine::post(size_t nsamples)
{
	auto data_size = data.size() * sizeof(*data.data());
	auto size_written = write(fd, data.data(), data_size);
	if(size_written != data_size)
	{
		std::cerr << "Audio write: " << std::strerror(errno) << std::endl;
	}
}

std::size_t OSSOutputEngine::getBufferSize() const
{
	return buffer_size;
}

std::size_t OSSOutputEngine::getSamplerate() const
{
	return srate;
}