/*
 * Load_667.cpp
 * ------------
 * Purpose: Composer 667 module loader
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#include "stdafx.h"
#include "Loaders.h"

OPENMPT_NAMESPACE_BEGIN

struct _667FileHeader
{
	using InstrName = std::array<char, 8>;

	char      magic[2];  // 'gf' (0x6667, ha ha)
	InstrName names[64];
	uint8     speed;
	uint8     numOrders;
	uint16le  patOffsets[128];  // Relative to end of instrument definitions

	bool IsValid() const
	{
		if(memcmp(magic, "gf", 2) || speed < 1 || speed > 15 || numOrders > 128)
			return false;
		for(const auto &name : names)
		{
			for(const char c : name)
			{
				if(static_cast<unsigned char>(c) <= 31)
					return false;
			}
		}
		int32 prevOffset = -1;
		for(const int32 offset : patOffsets)
		{
			if(offset <= prevOffset)
				return false;
			prevOffset = offset;
		}
		return true;
	}

	uint32 GetHeaderMinimumAdditionalSize() const
	{
		return numOrders + 64 * 11;
	}
};

MPT_BINARY_STRUCT(_667FileHeader, 772)


CSoundFile::ProbeResult CSoundFile::ProbeFileHeader667(MemoryFileReader file, const uint64 *pfilesize)
{
	_667FileHeader fileHeader;
	if(!file.ReadStruct(fileHeader))
		return ProbeWantMoreData;
	if(!fileHeader.IsValid())
		return ProbeFailure;
	return ProbeAdditionalSize(file, pfilesize, fileHeader.GetHeaderMinimumAdditionalSize());
}


bool CSoundFile::Read667(FileReader &file, ModLoadingFlags loadFlags)
{
	_667FileHeader fileHeader;

	file.Rewind();
	if(!file.ReadStruct(fileHeader) || !fileHeader.IsValid())
		return false;
	if(!file.CanRead(fileHeader.GetHeaderMinimumAdditionalSize()))
		return false;
	if(loadFlags == onlyVerifyHeader)
		return true;

	InitializeGlobals(MOD_TYPE_S3M);
	m_SongFlags.set(SONG_IMPORTED);
	m_nDefaultTempo.Set(150);
	m_nDefaultSpeed = fileHeader.speed;
	m_nChannels = 18;
	m_nSamples = 64;

	ReadOrderFromFile<uint8>(Order(), file, fileHeader.numOrders);
	for(PATTERNINDEX pat : Order())
	{
		if(pat >= 128)
			return false;
	}

	InitializeChannels();

	for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++)
	{
		// Reorder OPL patch bytes (interleave modulator and carrier)
		const auto fm = file.ReadArray<uint8, 11>();
		OPLPatch patch{{}};
		patch[0] = fm[1]; patch[1] = fm[6];
		patch[2] = fm[2]; patch[3] = fm[7];
		patch[4] = fm[3]; patch[5] = fm[8];
		patch[6] = fm[4]; patch[7] = fm[9];
		patch[8] = fm[5]; patch[9] = fm[10];
		patch[10] = fm[0];

		ModSample &mptSmp = Samples[smp];
		mptSmp.Initialize(MOD_TYPE_S3M);
		mptSmp.SetAdlib(true, patch);

		m_szNames[smp] = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.names[smp - 1]);
	}

	if(loadFlags & loadPatternData)
	{
		const auto patternOffset = file.GetPosition();
		bool leftChn = false, rightChn = false;
		Patterns.ResizeArray(128);
		for(PATTERNINDEX pat = 0; pat < 128; pat++)
		{
			// 4674 bytes is the largest sensible pattern size (every cell is written to exactly once + jump at end of pattern)
			FileReader patData = file.GetChunkAt(patternOffset + fileHeader.patOffsets[pat], 4674);
			if(!patData.IsValid() || !Patterns.Insert(pat, 32))
				break;
			ROWINDEX row = 0;
			auto rowData = Patterns[pat].GetRow(row);
			while(patData.CanRead(1))
			{
				uint8 b = patData.ReadUint8();
				if(b == 0xFF)
				{
					// End of row
					uint8 skip = patData.ReadUint8();
					row += skip;
					if(row >= 32 || skip == 0)
						break;
					rowData = Patterns[pat].GetRow(row);
				} else if(b == 0xFE)
				{
					// Instrument
					auto instr = patData.ReadArray<uint8, 2>();
					if(instr[0] >= m_nChannels || instr[1] > 63)
						return false;
					rowData[instr[0]].instr = instr[1] + 1;
				} else if(b == 0xFD)
				{
					// Volume
					auto vol = patData.ReadArray<uint8, 2>();
					if(vol[0] >= m_nChannels || vol[1] > 63)
						return false;
					rowData[vol[0]].SetVolumeCommand(VOLCMD_VOLUME, 63u - vol[1]);
				} else if(b == 0xFC)
				{
					// Jump to pattern
					uint8 target = patData.ReadUint8();
					rowData[0].SetEffectCommand(CMD_POSITIONJUMP, target);
				} else if(b == 0xFB)
				{
					// Pattern break
					rowData[0].SetEffectCommand(CMD_PATTERNBREAK, 0);
				} else if(b < m_nChannels)
				{
					// Note data
					uint8 note = patData.ReadUint8();
					if(note >= 0x7C)
						return false;
					rowData[b].note = NOTE_MIN + 12 + (note & 0x0F) + (note >> 4) * 12;
					if(b % 2u)
						rightChn = true;
					else
						leftChn = true;
				} else
				{
					return false;
				}
			}
		}
		if(leftChn && rightChn)
		{
			for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
			{
				ChnSettings[chn].nPan = (chn % 2u) ? 256 : 0;
			}
		}
	}

	m_modFormat.formatName = UL_("Composer 667");
	m_modFormat.type = UL_("667");
	m_modFormat.madeWithTracker = UL_("Composer 667");
	m_modFormat.charset = mpt::Charset::CP437;

	return true;
}


OPENMPT_NAMESPACE_END
