/* example_cpp_encode_file - Simple FLAC file encoder using libFLAC
 * Copyright (C) 2007,2008,2009  Josh Coalson
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

/*
 * This example shows how to use libFLAC++ to encode a WAVE file to a FLAC
 * file.  It only supports 16-bit stereo files in canonical WAVE format.
 *
 * Complete API documentation can be found at:
 *   http://flac.sourceforge.net/api/
 */

#if HAVE_CONFIG_H
#  include <config.h>
#endif

#include <iostream>
#include <fstream>
#include <cstring>

#define __STDC_FORMAT_MACROS

#include "FLAC++/metadata.h"
#include "FLAC++/encoder.h"

class OurEncoder: public FLAC::Encoder::File {
	unsigned total_samples;
public:
	OurEncoder(unsigned samples): FLAC::Encoder::File(), total_samples(samples) { }
protected:
	virtual void progress_callback(FLAC__uint64 bytes_written, FLAC__uint64 samples_written, unsigned frames_written, unsigned total_frames_estimate);
};

#define READSIZE 1024

int main(int argc, char *argv[])
{
	if(argc != 3) {
		std::cerr << "usage: " << argv[0] << " infile.wav outfile.flac" << std::endl;
		return 1;
	}

	std::ifstream fin(argv[1]);
	if(!fin.is_open()) {
		std::cerr << "ERROR: opening " << argv[1] << " for output" << std::endl;
		return 1;
	}

	/* read wav header and validate it */
	FLAC__byte buffer[READSIZE/*samples*/ * 2/*bytes_per_sample*/ * 2/*channels*/]; /* we read the WAVE data into here */
	if(
		!fin.read(reinterpret_cast<char *>(buffer), 44) ||
		std::memcmp(buffer, "RIFF", 4) ||
		std::memcmp(buffer+8, "WAVEfmt \020\000\000\000\001\000\002\000", 16) ||
		std::memcmp(buffer+32, "\004\000\020\000data", 8)
	) {
		std::cerr << "ERROR: invalid/unsupported WAVE file, only 16bps stereo WAVE in canonical form allowed" << std::endl;
	return 1;
	}
	unsigned sample_rate = (((((static_cast<unsigned>(buffer[27]) << 8) | buffer[26]) << 8) | buffer[25]) << 8) | buffer[24];
	unsigned channels = 2;
	unsigned bps = 16;
	unsigned total_samples = ((((((static_cast<unsigned>(buffer[43]) << 8) | buffer[42]) << 8) | buffer[41]) << 8) | buffer[40]) / 4;

	/* check the encoder */
	OurEncoder encoder(total_samples);
	if(!encoder) {
		std::cerr << "ERROR: allocating encoder" << std::endl;
		return 1;
	}

	bool ok = encoder.set_verify(true);
	ok &= encoder.set_compression_level(5);
	ok &= encoder.set_channels(channels);
	ok &= encoder.set_bits_per_sample(bps);
	ok &= encoder.set_sample_rate(sample_rate);
	ok &= encoder.set_total_samples_estimate(total_samples);

	if(!ok) {
		std::cerr << "ERROR: setting encoder options" << std::endl;
		return 1;
	}

	/* now add some metadata; we'll add some tags and a padding block */
	FLAC::Metadata::VorbisComment vorbis_comment;
	FLAC::Metadata::Padding padding(1234);
	if(
		/* there are many tag (vorbiscomment) functions but these are convenient for this particular use: */
		!vorbis_comment.append_comment(FLAC::Metadata::VorbisComment::Entry("ARTIST", "Some Artist")) || /* copy=false: let metadata object take control of entry's allocated string */
		!vorbis_comment.append_comment(FLAC::Metadata::VorbisComment::Entry("YEAR", "1984")) ||
		!vorbis_comment.is_valid() ||
		!padding.is_valid()
	) {
		std::cerr << "ERROR: out of memory or tag error" << std::endl;
		return 1;
	}

	FLAC::Metadata::Prototype *metadata[2];
	metadata[0] = &vorbis_comment;
	metadata[1] = &padding;
	if (!encoder.set_metadata(metadata, 2)) {
		std::cerr << "ERROR: setting metadata failed" << std::endl;
		return 1;
	}

	/* initialize encoder */
	FLAC__StreamEncoderInitStatus init_status = encoder.init(argv[2]);
	if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
		std::cerr << "ERROR: initializing encoder: " << FLAC__StreamEncoderInitStatusString[init_status] << std::endl;
		return 1;
	}

	/* read blocks of samples from WAVE file and feed to encoder */
	size_t left = static_cast<size_t>(total_samples);
	FLAC__int32 pcm[READSIZE/*samples*/ * 2/*channels*/];
	while(ok && left) {
		size_t need = (left>READSIZE? static_cast<size_t>(READSIZE) :static_cast<size_t> (left));
		if (!fin.read(reinterpret_cast<char *>(buffer), need*channels*(bps/8))) {
			std::cerr << "ERROR: reading from WAVE file" << std::endl;
			ok = false;
		}
		else {
			/* convert the packed little-endian 16-bit PCM samples from WAVE into an interleaved FLAC__int32 buffer for libFLAC */
			for(size_t i = 0; i < need*channels; ++i) {
			/* inefficient but simple and works on big- or little-endian machines */
			pcm[i] = static_cast<FLAC__int32>((static_cast<FLAC__int16>(static_cast<FLAC__int8>(buffer[2*i+1])) << 8) |
			         static_cast<FLAC__int16>(buffer[2*i]));
			}
			/* feed samples to encoder */
			ok = encoder.process_interleaved(pcm, need);
		}
		left -= need;
	}

	ok &= encoder.finish();

	std::cerr << "encoding: " << (ok? "succeeded" : "FAILED") << std::endl;
	std::cerr << "   state: " << encoder.get_state().resolved_as_cstring(encoder) << std::endl;

	return 0;
}

void OurEncoder::progress_callback(FLAC__uint64 bytes_written, FLAC__uint64 samples_written, unsigned frames_written, unsigned total_frames_estimate)
{
	std::cerr << "wrote " << bytes_written << " bytes, " << samples_written << "/" << total_samples << " samples, "
	          << frames_written << "/" << total_frames_estimate << " frames" << std::endl;
}
