/* ResidualVM - A 3D game interpreter
 *
 * ResidualVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the AUTHORS
 * file distributed with this source distribution.
 *
 * 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.
 *
 * 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

// Based on xoreos' (I)RDFT code which is in turn
// Based upon the (I)MDCT code in FFmpeg
// Copyright (c) 2002 Fabrice Bellard

/** @file common/mdct.cpp
 *  (Inverse) Modified Discrete Cosine Transforms.
 */

#include "common/math.h"
#include "common/util.h"
#include "common/fft.h"
#include "common/mdct.h"

namespace Common {

MDCT::MDCT(int bits, bool inverse, double scale) : _bits(bits), _fft(0) {
	_size = 1 << bits;

	_fft = new FFT(_bits - 2, inverse);

	const int size2 = _size >> 1;
	const int size4 = _size >> 2;

	_tCos = new float[size2];
	_tSin = _tCos + size4;

	const double theta = 1.0 / 8.0 + (scale < 0 ? size4 : 0);

	scale = sqrt(ABS(scale));
	for (int i = 0; i < size4; i++) {
		const double alpha = 2 * M_PI * (i + theta) / _size;

		_tCos[i] = -cos(alpha) * scale;
		_tSin[i] = -sin(alpha) * scale;
	}
}

MDCT::~MDCT() {
	delete[] _tCos;
	delete _fft;
}

#define CMUL(dre, dim, are, aim, bre, bim) do { \
		(dre) = (are) * (bre) - (aim) * (bim);  \
		(dim) = (are) * (bim) + (aim) * (bre);  \
	} while (0)

void MDCT::calcMDCT(float *output, const float *input) {
	Complex *x = (Complex *) output;

	const int size2 = _size >> 1;
	const int size4 = _size >> 2;
	const int size8 = _size >> 3;
	const int size3 = _size * 3;

	const uint16 *revTab = _fft->getRevTab();

	// Pre rotation
	for (int i = 0; i < size8; i++) {
		float re = -input[2 * i + size3] - input[size3 - 1 - 2 * i];
		float im = -input[2 * i + size4] + input[size4 - 1 - 2 * i];
		int    j = revTab[i];

		CMUL(x[j].re, x[j].im, re, im, -_tCos[i], _tSin[i]);

		re =  input[2 * i        ] - input[size2 - 1 - 2 * i];
		im = -input[2 * i + size2] - input[_size - 1 - 2 * i];
		 j = revTab[size8 + i];

		CMUL(x[j].re, x[j].im, re, im, -_tCos[size8 + i], _tSin[size8 + i]);
	}

	_fft->calc(x);

	// Post rotation
	for (int i = 0; i < size8; i++) {
		float r0, i0, r1, i1;

		CMUL(i1, r0, x[size8-i-1].re, x[size8-i-1].im, -_tSin[size8-i-1], -_tCos[size8-i-1]);
		CMUL(i0, r1, x[size8+i  ].re, x[size8+i  ].im, -_tSin[size8+i  ], -_tCos[size8+i  ]);

		x[size8 - i - 1].re = r0;
		x[size8 - i - 1].im = i0;
		x[size8 + i    ].re = r1;
		x[size8 + i    ].im = i1;
	}
}

void MDCT::calcIMDCT(float *output, const float *input) {
	const int size2 = _size >> 1;
	const int size4 = _size >> 2;

	calcHalfIMDCT(output + size4, input);

	for (int k = 0; k < size4; k++) {
		output[        k    ] = -output[size2 - k - 1];
		output[_size - k - 1] =  output[size2 + k    ];
	}
}

void MDCT::calcHalfIMDCT(float *output, const float *input) {
	Complex *z = (Complex *) output;

	const int size2 = _size >> 1;
	const int size4 = _size >> 2;
	const int size8 = _size >> 3;

	const uint16 *revTab = _fft->getRevTab();

	// Pre rotation
	const float *in1 = input;
	const float *in2 = input + size2 - 1;
	for (int k = 0; k < size4; k++) {
		const int j = revTab[k];

		CMUL(z[j].re, z[j].im, *in2, *in1, _tCos[k], _tSin[k]);

		in1 += 2;
		in2 -= 2;
	}

	_fft->calc(z);

	// Post rotation + reordering
	for (int k = 0; k < size8; k++) {
		float r0, i0, r1, i1;

		CMUL(r0, i1, z[size8-k-1].im, z[size8-k-1].re, _tSin[size8-k-1], _tCos[size8-k-1]);
		CMUL(r1, i0, z[size8+k  ].im, z[size8+k  ].re, _tSin[size8+k  ], _tCos[size8+k  ]);

		z[size8 - k - 1].re = r0;
		z[size8 - k - 1].im = i0;
		z[size8 + k    ].re = r1;
		z[size8 + k    ].im = i1;
	}
}

} // End of namespace Common
