/***************************************************************************
 *   Copyright (C) 2007 by Sindre Aamås                                    *
 *   aamas@stud.ntnu.no                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License version 2 as     *
 *   published by the Free Software Foundation.                            *
 *                                                                         *
 *   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 General Public License version 2 for more details.                *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   version 2 along with this program; if not, write to the               *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "ossengine.h"

#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>

#include <algorithm>
#include <cstdio>
#include <cmath>

OssEngine::OssEngine() :
AudioEngine("OSS"),
conf("Custom DSP device:", "/dev/dsp", "ossengine"),
audio_fd(-1),
bufSize(0),
fragSize(0),
prevbytes(0)
{}

OssEngine::~OssEngine() {
	uninit();
}

int OssEngine::doInit(int speed, const unsigned latency) {
	if ((audio_fd = open(conf.device(), O_WRONLY, 0)) == -1) {
		std::perror(conf.device());
		goto fail;
	}
	
	{
		int channels = 2;
		
		if (ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &channels) == -1) {
			std::perror("SNDCTL_DSP_CHANNELS");
			goto fail;
		}
		
		if (channels != 2) {
			std::fprintf(stderr, "oss: unsupported number of channels\n");
			goto fail;
		}
	}
	
	{
		int format = AFMT_S16_NE;
		
		if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &format) == -1) {
			std::perror("SNDCTL_DSP_SETFMT");
			goto fail;
		}
		
		if (format != AFMT_S16_NE) {
			std::fprintf(stderr, "oss: unsupported format\n");
			goto fail;
		}
	}
	
	if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &speed) == -1) {
		std::perror("SNDCTL_DSP_SPEED");
		goto fail;
	}
	
	{
		int arg = 0x60000 | static_cast<int>(std::log(speed * latency * 4 / 6000.0) / std::log(2.0) + 0.5);
		
		if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &arg) == -1) {
			std::perror("SNDCTL_DSP_SETFRAGMENT");
// 			goto fail;
		}
	}
	
	{
		audio_buf_info info;
		
		if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info) == -1) {
			std::perror("SNDCTL_DSP_GETOSPACE");
			goto fail;
		}
		
		bufSize = info.bytes >> 2;
		fragSize = info.fragsize >> 2;
	}
	
	prevbytes = 0;
	est.init(speed, speed, bufSize);
	
	return speed;
	
fail:
	uninit();
	return -1;
}

void OssEngine::uninit() {
	if (audio_fd != -1)
		close(audio_fd);
	
	audio_fd = -1;
}

int OssEngine::write(void *const buffer, const unsigned samples, const BufferState &bstate) {
	if (bstate.fromUnderrun != BufferState::NOT_SUPPORTED) {
		count_info ci;
		
		if (ioctl(audio_fd, SNDCTL_DSP_GETOPTR, &ci) != -1) {
			if (static_cast<unsigned>(ci.bytes) > prevbytes) {
				if (bstate.fromUnderrun > fragSize)
					est.feed((ci.bytes - prevbytes) >> 2);
				else
					est.reset();
			}
			
			prevbytes = ci.bytes;
		}
	}
	
	if (::write(audio_fd, buffer, samples * 4) != static_cast<int>(samples * 4))
		return -1;
	
	return 0;
}

int OssEngine::write(void *const buffer, const unsigned samples) {
	return write(buffer, samples, bufferState());
}

int OssEngine::write(void *const buffer, const unsigned samples, BufferState &preBufState, long &rate) {
	const int ret = write(buffer, samples, preBufState = bufferState());
	rate = est.result();
	return ret;
}

const AudioEngine::BufferState OssEngine::bufferState() const {
	BufferState s;
	audio_buf_info info;
	
	if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info) == -1) {
		s.fromOverflow = s.fromUnderrun = BufferState::NOT_SUPPORTED;
	} else {
		if (info.bytes < 0)
			info.bytes = 0;
		else if (static_cast<unsigned>(info.bytes >> 2) > bufSize)
			info.bytes = bufSize << 2;
		
		s.fromUnderrun = bufSize - (info.bytes >> 2);
		s.fromOverflow = info.bytes >> 2;
	}
	
	return s;
}
