// This file is part of ZetaGlest <https://github.com/ZetaGlest>
//
// Copyright (C) 2018  The ZetaGlest team
//
// ZetaGlest is a fork of MegaGlest <https://megaglest.org>
//
// 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 3 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, see <https://www.gnu.org/licenses/>

#include "BMPReader.h"
#include "data_types.h"
#include "pixmap.h"
#include <stdexcept>
#include "util.h"
#include "leak_dumper.h"

using std::runtime_error;

namespace Shared {
	namespace Graphics {

		// =====================================================
		//	Structs used for BMP-reading
		// =====================================================
#pragma pack(push, 1)

		struct BitmapFileHeader {
			uint8 type1;
			uint8 type2;
			uint32 size;
			uint16 reserved1;
			uint16 reserved2;
			uint32 offsetBits;
		};

		struct BitmapInfoHeader {
			uint32 size;
			int32 width;
			int32 height;
			uint16 planes;
			uint16 bitCount;
			uint32 compression;
			uint32 sizeImage;
			int32 xPelsPerMeter;
			int32 yPelsPerMeter;
			uint32 clrUsed;
			uint32 clrImportant;
		};

#pragma pack(pop)

		/**Returns a string containing the extensions we want, intitialisation is guaranteed*/
		//static inline const string* getExtensionsBmp() {
		static inline std::vector<string> getExtensionsBmp() {
			//static const string extensions[] = {"bmp", ""};
			static std::vector<string> extensions;

			if (extensions.empty() == true) {
				extensions.push_back("bmp");
			}

			return extensions;
		}

		// =====================================================
		//	class BMPReader
		// =====================================================

		BMPReader::BMPReader() : FileReader<Pixmap2D>(getExtensionsBmp()) {
		}

		/**Reads a Pixmap2D-object
		  *This function reads a Pixmap2D-object from the given ifstream utilising the already existing Pixmap2D* ret.
		  *Path is used for printing error messages
		  *@return <code>NULL</code> if the Pixmap2D could not be read, else the pixmap*/
		Pixmap2D* BMPReader::read(ifstream& in, const string& path, Pixmap2D* ret) const {
			if (GlobalStaticFlags::getIsNonGraphicalModeEnabled() == true) {
				throw game_runtime_error("Loading graphics in headless server mode not allowed!");
			}

			//read file header
			BitmapFileHeader fileHeader;
			in.read((char*) &fileHeader, sizeof(BitmapFileHeader));
			static bool bigEndianSystem = Shared::PlatformByteOrder::isBigEndian();
			if (bigEndianSystem == true) {
				fileHeader.offsetBits = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.offsetBits);
				fileHeader.reserved1 = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.reserved1);
				fileHeader.reserved2 = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.reserved2);
				fileHeader.size = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.size);
				fileHeader.type1 = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.type1);
				fileHeader.type2 = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.type2);
			}

			if (fileHeader.type1 != 'B' || fileHeader.type2 != 'M') {
				throw game_runtime_error(path + " is not a bitmap", true);
			}

			//read info header
			BitmapInfoHeader infoHeader;
			in.read((char*) &infoHeader, sizeof(BitmapInfoHeader));
			if (bigEndianSystem == true) {
				infoHeader.bitCount = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.bitCount);
				infoHeader.clrImportant = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.clrImportant);
				infoHeader.clrUsed = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.clrUsed);
				infoHeader.compression = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.compression);
				infoHeader.height = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.height);
				infoHeader.planes = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.planes);
				infoHeader.size = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.size);
				infoHeader.sizeImage = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.sizeImage);
				infoHeader.width = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.width);
				infoHeader.xPelsPerMeter = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.xPelsPerMeter);
				infoHeader.yPelsPerMeter = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.yPelsPerMeter);
			}

			if (infoHeader.bitCount != 24) {
				throw game_runtime_error(path + " is not a 24 bit bitmap", true);
			}

			int h = infoHeader.height;
			int w = infoHeader.width;
			int components = (ret->getComponents() == -1) ? 3 : ret->getComponents();
			const int fileComponents = 3;
			//std::cout << "BMP-Components: Pic: " << components << " old: " << (ret->getComponents()) << " File: " << 3 << std::endl;
			ret->init(w, h, components);
			uint8* pixels = ret->getPixels();
			char buffer[4];
			//BMP is padded to sizes of 4
			const int padSize = (4 - (w*fileComponents) % 4) % 4; //Yeah, could be faster
			//std::cout << "Padsize = " << padSize;
			for (int y = 0, i = 0; y < h; ++y) {
				for (int x = 0; x < w; ++x, i += components) {
					uint8 r, g, b;
					//			in.read((char*)&b, 1);
					//			if(bigEndianSystem == true) {
					//				b = Shared::PlatformByteOrder::fromCommonEndian(b);
					//			}
					//
					//			in.read((char*)&g, 1);
					//			if(bigEndianSystem == true) {
					//				g = Shared::PlatformByteOrder::fromCommonEndian(g);
					//			}
					//
					//			in.read((char*)&r, 1);
					//			if(bigEndianSystem == true) {
					//				r = Shared::PlatformByteOrder::fromCommonEndian(r);
					//			}
					uint8 bgr[3] = { 0,0,0 };
					in.read((char*) &bgr[0], 3);
					b = bgr[0];
					g = bgr[1];
					r = bgr[2];
					if (bigEndianSystem == true) {
						b = Shared::PlatformByteOrder::fromCommonEndian(b);
						g = Shared::PlatformByteOrder::fromCommonEndian(g);
						r = Shared::PlatformByteOrder::fromCommonEndian(r);
					}
					if (!in.good()) {
						return NULL;
					}
					switch (components) {
						case 1:
							pixels[i] = (r + g + b) / 3;
							break;
						case 3:
							pixels[i] = r;
							pixels[i + 1] = g;
							pixels[i + 2] = b;
							break;
						case 4:
							pixels[i] = r;
							pixels[i + 1] = g;
							pixels[i + 2] = b;
							pixels[i + 3] = 255;
							break;
					}
				}
				if (padSize) {
					in.read(buffer, padSize);
				}
			}
			/*for(int i = 0; i < w*h*components; ++i) {
				if (i%39 == 0) std::cout << std::endl;
				int first = pixels[i]/16;
				if (first < 10)
					std:: cout << first;
				else
					std::cout << (char)('A'+(first-10));
				first = pixels[i]%16;
				if (first < 10)
					std:: cout << first;
				else
					std::cout << (char)('A'+(first-10));
				std::cout << " ";
			}*/
			return ret;
		}

	}
} //end namespace
