//
//  G64.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 10/07/2016.
//  Copyright © 2016 Thomas Harte. All rights reserved.
//

#include "G64.hpp"

#include <vector>
#include "../PCMTrack.hpp"
#include "../Encodings/CommodoreGCR.hpp"

using namespace Storage::Disk;

G64::G64(const char *file_name) :
		Storage::FileHolder(file_name) {
	// read and check the file signature
	if(!check_signature("GCR-1541", 8)) throw ErrorNotG64;

	// check the version number
	int version = fgetc(file_);
	if(version != 0) throw ErrorUnknownVersion;

	// get the number of tracks and track size
	number_of_tracks_ = (uint8_t)fgetc(file_);
	maximum_track_size_ = fgetc16le();
}

unsigned int G64::get_head_position_count() {
	// give at least 84 tracks, to yield the normal geometry but,
	// if there are more, shove them in
	return number_of_tracks_ > 84 ? number_of_tracks_ : 84;
}

std::shared_ptr<Track> G64::get_track_at_position(unsigned int head, unsigned int position) {
	std::shared_ptr<Track> resulting_track;

	// if there's definitely no track here, return the empty track
	// (TODO: should be supplying one with an index hole?)
	if(position >= number_of_tracks_) return resulting_track;
	if(head >= 1) return resulting_track;

	// seek to this track's entry in the track table
	fseek(file_, (long)((position * 4) + 0xc), SEEK_SET);

	// read the track offset
	uint32_t track_offset;
	track_offset = fgetc32le();

	// if the track offset is zero, this track doesn't exist, so...
	if(!track_offset) return resulting_track;

	// seek to the track start
	fseek(file_, (int)track_offset, SEEK_SET);

	// get the real track length
	uint16_t track_length;
	track_length = fgetc16le();

	// grab the byte contents of this track
	std::vector<uint8_t> track_contents(track_length);
	fread(&track_contents[0], 1, track_length, file_);

	// seek to this track's entry in the speed zone table
	fseek(file_, (long)((position * 4) + 0x15c), SEEK_SET);

	// read the speed zone offsrt
	uint32_t speed_zone_offset;
	speed_zone_offset = fgetc32le();

	// if the speed zone is not constant, create a track based on the whole table; otherwise create one that's constant
	if(speed_zone_offset > 3) {
		// seek to start of speed zone
		fseek(file_, (int)speed_zone_offset, SEEK_SET);

		uint16_t speed_zone_length = (track_length + 3) >> 2;

		// read the speed zone bytes
		uint8_t speed_zone_contents[speed_zone_length];
		fread(speed_zone_contents, 1, speed_zone_length, file_);

		// divide track into appropriately timed PCMSegments
		std::vector<PCMSegment> segments;
		unsigned int current_speed = speed_zone_contents[0] >> 6;
		unsigned int start_byte_in_current_speed = 0;
		for(unsigned int byte = 0; byte < track_length; byte ++) {
			unsigned int byte_speed = speed_zone_contents[byte >> 2] >> (6 - (byte&3)*2);
			if(byte_speed != current_speed || byte == (track_length-1)) {
				unsigned int number_of_bytes = byte - start_byte_in_current_speed;

				PCMSegment segment;
				segment.number_of_bits = number_of_bytes * 8;
				segment.length_of_a_bit = Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(current_speed);
				segment.data.resize(number_of_bytes);
				memcpy(&segment.data[0], &track_contents[start_byte_in_current_speed], number_of_bytes);
				segments.push_back(std::move(segment));

				current_speed = byte_speed;
				start_byte_in_current_speed = byte;
			}
		}

		resulting_track.reset(new PCMTrack(std::move(segments)));
	} else {
		PCMSegment segment;
		segment.number_of_bits = track_length * 8;
		segment.length_of_a_bit = Encodings::CommodoreGCR::length_of_a_bit_in_time_zone((unsigned int)speed_zone_offset);
		segment.data = std::move(track_contents);

		resulting_track.reset(new PCMTrack(std::move(segment)));
	}

	// TODO: find out whether it's possible for a G64 to supply only a partial track. I don't think it is, which would make the
	// above correct but supposing I'm wrong, the above would produce some incorrectly clocked tracks

	return resulting_track;
}
