#ifndef ATRIMAGE_H
#define ATRIMAGE_H

/*
   AtrImage.h - common base class for handling ATR (Atari 8bit disk-) images

   Copyright (C) 2002 Matthias Reichl <hias@horus.com>

   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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <unistd.h>

#include "DiskImage.h"

typedef enum { eNoDisk=0, e90kDisk=1, e130kDisk=2, e180kDisk=3, e360kDisk=4, eUserDefDisk=5} EDiskFormat;
typedef enum { e128BytesPerSector=128, e256BytesPerSector=256} ESectorLength;

class AtrImageConfig {
public:
	AtrImageConfig();
	~AtrImageConfig();

	AtrImageConfig(const AtrImageConfig&other);

	AtrImageConfig& operator=(const AtrImageConfig& other);
	bool operator==(const AtrImageConfig& other) const;
	bool operator!=(const AtrImageConfig& other) const;

	void DetermineDiskFormatFromLayout();
	void CalculateImageSize();


	size_t GetSectorLength(size_t sector) const;

	EDiskFormat fDiskFormat;

	ESectorLength fSectorLength;

	size_t fSectorsPerTrack;
	size_t fTracksPerSide;
	size_t fSides;

	size_t fNumberOfSectors;
	size_t fImageSize;
};

class AtrImage : public DiskImage {
public:

	AtrImage();

	virtual ~AtrImage();

	const AtrImageConfig& GetImageConfig() { return fImageConfig; }

	EDiskFormat GetDiskFormat() const { return fImageConfig.fDiskFormat; }
	ESectorLength GetSectorLength() const { return fImageConfig.fSectorLength; }
	size_t GetBytesPerSector() const;
	size_t GetSectorLength(size_t sectorNumber) const { return fImageConfig.GetSectorLength(sectorNumber); }
	size_t GetNumberOfSectors() const { return fImageConfig.fNumberOfSectors; }

	size_t GetSectorsPerTrack() const { return fImageConfig.fSectorsPerTrack; }
	size_t GetTracksPerSide() const { return fImageConfig.fTracksPerSide; }
	size_t GetSides() const { return fImageConfig.fSides; }

	size_t GetImageSize() const { return fImageConfig.fImageSize; }

	virtual int ReadSector(size_t sector,
		       unsigned char* buffer,
		       size_t buffer_length) const = 0;

	virtual int WriteSector(size_t sector,
		       const unsigned char* buffer,
		       size_t buffer_length) = 0;

	void SetWriteProtect(bool on);
	bool IsWriteProtected() const;

	virtual int CreateImage(EDiskFormat format) = 0;
	virtual int CreateImage(ESectorLength density, size_t sectors) = 0;
	virtual int CreateImage(ESectorLength density, size_t sectorsPerTrack, size_t tracks, size_t sides) = 0;

	virtual bool IsAtrMemoryImage() const;
	virtual bool IsAtrDiskImage() const;

protected:
	int SetFormat(EDiskFormat format);
	int SetFormat(ESectorLength density, size_t numberOfSectors);
	int SetFormat(ESectorLength density, size_t sectors, size_t tracks, size_t sides);

	int SetFormatFromATRHeader(const unsigned char* header);
	int CreateATRHeaderFromFormat(unsigned char* header) const;

	int CalculateOffset(size_t sector) const;

private:

	void Init(); /* reset all data to zero */

	AtrImageConfig fImageConfig;

	bool fWriteProtect;
};

inline size_t AtrImage::GetBytesPerSector() const
{
	if (fImageConfig.fSectorLength == e256BytesPerSector) {
		return 256;
	} else {
		return 128;
	}
}

inline int AtrImage::CalculateOffset(size_t sector) const
{
	if ( (sector == 0) || (sector > fImageConfig.fNumberOfSectors) ) {
		return -1;
	}
	switch (fImageConfig.fSectorLength) {
	case e128BytesPerSector:
		return (sector-1)*128;
	case e256BytesPerSector:
		if (sector <= 3) {
			return (sector-1)*128;
		} else {
			return 384 + (sector-4)*256;
		}
	}
	return -1;
}

inline AtrImageConfig::AtrImageConfig()
	: fDiskFormat(eNoDisk),
	  fSectorLength(e128BytesPerSector),
	  fSectorsPerTrack(0),
	  fTracksPerSide(0),
	  fSides(0),
	  fNumberOfSectors(0),
	  fImageSize(0)
{ }

inline AtrImageConfig::AtrImageConfig(const AtrImageConfig&other)
	: fDiskFormat(other.fDiskFormat),
	  fSectorLength(other.fSectorLength),
	  fSectorsPerTrack(other.fSectorsPerTrack),
	  fTracksPerSide(other.fTracksPerSide),
	  fSides(other.fSides),
	  fNumberOfSectors(other.fNumberOfSectors),
	  fImageSize(other.fImageSize)
{ }

inline AtrImageConfig& AtrImageConfig::operator=(const AtrImageConfig& other)
{
	fDiskFormat = other.fDiskFormat;
	fSectorLength = other.fSectorLength;
	fNumberOfSectors = other.fNumberOfSectors;
	fSectorsPerTrack = other.fSectorsPerTrack;
	fTracksPerSide = other.fTracksPerSide;
	fSides = other.fSides;
	fImageSize = other.fImageSize;
	return *this;
}

inline bool AtrImageConfig::operator==(const AtrImageConfig& other) const
{
	return
		fDiskFormat == other.fDiskFormat &&
		fSectorLength == other.fSectorLength &&
		fNumberOfSectors == other.fNumberOfSectors &&
		fSectorsPerTrack == other.fSectorsPerTrack &&
		fTracksPerSide == other.fTracksPerSide &&
		fSides == other.fSides &&
		fImageSize == other.fImageSize;
}

inline bool AtrImageConfig::operator!=(const AtrImageConfig& other) const
{
	return !(*this == other);
}

inline AtrImageConfig::~AtrImageConfig()
{ }

inline size_t AtrImageConfig::GetSectorLength(size_t sector) const
{
	if (sector == 0 || sector > fNumberOfSectors) {
		return 0;
	}
	if (fSectorLength == e256BytesPerSector && sector > 3) {
		return 256;
	} else {
		return 128;
	}
}

inline void AtrImageConfig::DetermineDiskFormatFromLayout()
{
	fDiskFormat = eUserDefDisk;

	if (fSectorLength == e128BytesPerSector) {
		if (fSectorsPerTrack == 18 && fTracksPerSide == 40 && fSides == 1) {
			fDiskFormat = e90kDisk;
		} else if (fSectorsPerTrack == 26 && fTracksPerSide == 40 && fSides == 1) {
			fDiskFormat = e130kDisk;
		}
	} else {
		if (fSectorsPerTrack == 18 && fTracksPerSide == 40 && fSides == 1) {
			fDiskFormat = e180kDisk;
		} else if (fSectorsPerTrack == 18 && fTracksPerSide == 40 && fSides == 2) {
			fDiskFormat = e360kDisk;
		}
	}
}

inline void AtrImageConfig::CalculateImageSize()
{
	if (fSectorLength == e256BytesPerSector && fNumberOfSectors >3) {
		fImageSize = fNumberOfSectors * 256 - 384;
	} else {
		fImageSize = fNumberOfSectors * 128;
	}
}

inline void AtrImage::SetWriteProtect(bool on)
{
	fWriteProtect = on;
}

inline bool AtrImage::IsWriteProtected() const
{
	return fWriteProtect;
}

#endif
