/*
* This file is part of project OpenEmbroidery. It's copyrighted by
* the contributors recorded in the version control history of the file.
* Original project location https://code.electrolab.fr/openEmbroidery/openEmbroidery_software
*
* SPDX-License-Identifier: CECILL-2.1
* License-Filename: Licence_CeCILL_V2.1-en.txt
*/

#include "OE_io.h"
#include "OE_document.h"
#include "stitchs/OE_staticstitch.h"
#include "stitchs/OE_linkstitch.h"
#include "OE_utils.h"

#include <iostream>
#include <fstream>

static bool loadFromPES(std::string path, OE_document* document);
//static bool saveToPES(const OE_document* document, std::string path);

extern const OE_io ioPes;
const OE_io ioPes = {"pes",
                     "Brother",
                     loadFromPES,
                     0};

static const int pecThreadCount = 65;
struct PESCol
{
	unsigned char rgb[3];
	const char* name;
	const char* dummy;
};

static const PESCol pecThreads[] = {
	{{  0,   0,   0}, "Unknown",         ""}, /* Index  0 */
	{{ 14,  31, 124}, "Prussian Blue",   ""}, /* Index  1 */
	{{ 10,  85, 163}, "Blue",            ""}, /* Index  2 */
	{{  0, 135, 119}, "Teal Green",      ""}, /* Index  3 */ /* TODO: Verify RGB value is correct */
	{{ 75, 107, 175}, "Cornflower Blue", ""}, /* Index  4 */
	{{237,  23,  31}, "Red",             ""}, /* Index  5 */
	{{209,  92,   0}, "Reddish Brown",   ""}, /* Index  6 */
	{{145,  54, 151}, "Magenta",         ""}, /* Index  7 */
	{{228, 154, 203}, "Light Lilac",     ""}, /* Index  8 */
	{{145,  95, 172}, "Lilac",           ""}, /* Index  9 */
	{{158, 214, 125}, "Mint Green",      ""}, /* Index 10 */ /* TODO: Verify RGB value is correct */
	{{232, 169,   0}, "Deep Gold",       ""}, /* Index 11 */
	{{254, 186,  53}, "Orange",          ""}, /* Index 12 */
	{{255, 255,   0}, "Yellow",          ""}, /* Index 13 */
	{{112, 188,  31}, "Lime Green",      ""}, /* Index 14 */
	{{186, 152,   0}, "Brass",           ""}, /* Index 15 */
	{{168, 168, 168}, "Silver",          ""}, /* Index 16 */
	{{125, 111,   0}, "Russet Brown",    ""}, /* Index 17 */ /* TODO: Verify RGB value is correct */
	{{255, 255, 179}, "Cream Brown",     ""}, /* Index 18 */
	{{ 79,  85,  86}, "Pewter",          ""}, /* Index 19 */
	{{  0,   0,   0}, "Black",           ""}, /* Index 20 */
	{{ 11,  61, 145}, "Ultramarine",     ""}, /* Index 21 */
	{{119,   1, 118}, "Royal Purple",    ""}, /* Index 22 */
	{{ 41,  49,  51}, "Dark Gray",       ""}, /* Index 23 */
	{{ 42,  19,   1}, "Dark Brown",      ""}, /* Index 24 */
	{{246,  74, 138}, "Deep Rose",       ""}, /* Index 25 */
	{{178, 118,  36}, "Light Brown",     ""}, /* Index 26 */
	{{252, 187, 197}, "Salmon Pink",     ""}, /* Index 27 */ /* TODO: Verify RGB value is correct */
	{{254,  55,  15}, "Vermillion",      ""}, /* Index 28 */
	{{240, 240, 240}, "White",           ""}, /* Index 29 */
	{{106,  28, 138}, "Violet",          ""}, /* Index 30 */
	{{168, 221, 196}, "Seacrest",        ""}, /* Index 31 */
	{{ 37, 132, 187}, "Sky Blue",        ""}, /* Index 32 */
	{{254, 179,  67}, "Pumpkin",         ""}, /* Index 33 */
	{{255, 243, 107}, "Cream Yellow",    ""}, /* Index 34 */
	{{208, 166,  96}, "Khaki",           ""}, /* Index 35 */
	{{209,  84,   0}, "Clay Brown",      ""}, /* Index 36 */
	{{102, 186,  73}, "Leaf Green",      ""}, /* Index 37 */
	{{ 19,  74,  70}, "Peacock Blue",    ""}, /* Index 38 */
	{{135, 135, 135}, "Gray",            ""}, /* Index 39 */
	{{216, 204, 198}, "Warm Gray",       ""}, /* Index 40 */ /* TODO: Verify RGB value is correct */
	{{ 67,  86,   7}, "Dark Olive",      ""}, /* Index 41 */
	{{253, 217, 222}, "Flesh Pink",      ""}, /* Index 42 */ /* TODO: Verify RGB value is correct */
	{{249, 147, 188}, "Pink",            ""}, /* Index 43 */
	{{  0,  56,  34}, "Deep Green",      ""}, /* Index 44 */
	{{178, 175, 212}, "Lavender",        ""}, /* Index 45 */
	{{104, 106, 176}, "Wisteria Violet", ""}, /* Index 46 */
	{{239, 227, 185}, "Beige",           ""}, /* Index 47 */
	{{247,  56, 102}, "Carmine",         ""}, /* Index 48 */
	{{181,  75, 100}, "Amber Red",       ""}, /* Index 49 */ /* TODO: Verify RGB value is correct */
	{{ 19,  43,  26}, "Olive Green",     ""}, /* Index 50 */
	{{199,   1,  86}, "Dark Fuschia",    ""}, /* Index 51 */ /* TODO: Verify RGB value is correct */
	{{254, 158,  50}, "Tangerine",       ""}, /* Index 52 */
	{{168, 222, 235}, "Light Blue",      ""}, /* Index 53 */
	{{  0, 103,  62}, "Emerald Green",   ""}, /* Index 54 */ /* TODO: Verify RGB value is correct */
	{{ 78,  41, 144}, "Purple",          ""}, /* Index 55 */
	{{ 47, 126,  32}, "Moss Green",      ""}, /* Index 56 */
	{{255, 204, 204}, "Flesh Pink",      ""}, /* Index 57 */ /* TODO: Verify RGB value is correct */ /* TODO: Flesh Pink is Index 42, is this Index incorrect? */
	{{255, 217,  17}, "Harvest Gold",    ""}, /* Index 58 */
	{{  9,  91, 166}, "Electric Blue",   ""}, /* Index 59 */
	{{240, 249, 112}, "Lemon Yellow",    ""}, /* Index 60 */
	{{227, 243,  91}, "Fresh Green",     ""}, /* Index 61 */
	{{255, 153,   0}, "Orange",          ""}, /* Index 62 */ /* TODO: Verify RGB value is correct */ /* TODO: Orange is Index 12, is this Index incorrect? */
	{{255, 240, 141}, "Cream Yellow",    ""}, /* Index 63 */ /* TODO: Verify RGB value is correct */ /* TODO: Cream Yellow is Index 34, is this Index incorrect? */
	{{255, 200, 200}, "Applique",        ""}  /* Index 64 */
};

static uint8_t getU8(std::ifstream& in)
{ // TODO throw on error
	unsigned char u = 0;
	if (in.good())
	{
		in.read((char*)&u, 1);
	}
	return u;
}

static uint32_t getU32LE(std::ifstream& in)
{ // TODO throw on error
	uint32_t u32le = getU8(in);
	u32le |= getU8(in)<<8;
	u32le |= getU8(in)<<16;
	u32le |= getU8(in)<<24;
	return u32le;
}
static bool loadFromPES(std::string path, OE_document* document)
{
	std::ifstream in(path, std::ios_base::in|std::ios_base::binary);
	if (!in.is_open())
	{
		return false;
	}

	in.seekg(8);
	uint32_t pecstart = getU32LE(in);

	std::cout << "PES: " << "pecstart=" << pecstart << std::endl;
	std::cout << "PES: " << "jumping to " << (pecstart+48) << std::endl;
	in.seekg(pecstart+48, std::ios_base::seekdir::_S_beg);

	int nThreads = getU8(in)+1;
	std::cout << "PES: " << nThreads << " threads" << std::endl;
	for (int thread=0; thread<nThreads; thread++)
	{
		int iCol = getU8(in);
		if (iCol >= pecThreadCount)
		{
			std::cout << "PES: " << "thread " << thread << " unmanaged color " << iCol << ". Fallbacking." << std::endl;
			iCol = 0;
		}
		const PESCol* col = &pecThreads[iCol];
		std::cout << "PES: " << "thread " << thread << " col (" <<
		             (int)col->rgb[0] << ", " <<
		             (int)col->rgb[1] << ", " <<
		             (int)col->rgb[2] << ")" << std::endl;
		document->threads.push_back(new OE_thread(OE_color(col->rgb[0]/255.0,
		                                                   col->rgb[1]/255.0,
		                                                   col->rgb[2]/255.0),
		                                          OE_thread::defaultWidth));
	}

	OE_staticstitch* stitch = 0;

	std::cout << "PES: " << "jumping to " << (pecstart+532) << std::endl;
	in.seekg(pecstart+532, std::ios_base::seekdir::_S_beg);

	int iCol = 0;
	auto itThread = document->threads.begin();
	int nstitchs = 0; // TODO remove
	int ntrims = 0; // TODO remove
	int njumps = 0; // TODO remove
	long long x=0;
	long long y=0;
	while(in.good())
	{
		int val1 = getU8(in);
		int val2 = getU8(in);
		if (val1 == 0xFF && val2 == 0x00)
		{
			std::cout << "PES: " << "end of program" << std::endl;
			break;
		}
		else if (val1 == 0xFE && val2 == 0xB0)
		{
			std::cout << "PES: " << "stitch end, thread change" << std::endl;
			getU8(in);
			iCol++;
			itThread++;
			continue;
		}
		int stitchType = 0;
		/* High bit set means 12-bit offset, otherwise 7-bit signed delta */
		if (val1 & 0x80)
		{
			if (val1 & 0x20)
			{
				stitchType = 1; // trim + move to
			}
			if (val1 & 0x10)
			{
				stitchType = 2; // move to
			}

			val1 = ((val1 & 0x0F) << 8) + val2;

			// 12-bit 2's complement
			if (val1 & 0x800)
			{
				val1 -= 0x1000;
			}

			val2 = getU8(in);
		}
		else if (val1 >= 0x40)
		{ // 7-bit 2's complement
			val1 -= 0x80;
		}
		if (val2 & 0x80)
		{
			if (val2 & 0x20)
			{
				stitchType = 1; // trim + move to
			}
			if (val2 & 0x10)
			{
				stitchType = 2; // move to
			}
			val2 = ((val2 & 0x0F) << 8) + getU8(in);

			// 12-bit 2's complement
			if (val2 & 0x800)
			{
				val2 -= 0x1000;
			}
		}
		else if (val2 >= 0x40)
		{ // 7-bit 2's complement
			val2 -= 0x80;
		}
		x += val1;
		y += val2;
		if (!stitch || stitchType)
		{
			OE_staticstitch* newStitch = new OE_staticstitch(*itThread);
			if (stitch)
			{
				document->addStitch(new OE_linkstitch(stitch, newStitch));
			}
			document->addStitch(newStitch);
			stitch = newStitch;
		}
		stitch->addPoint(x/10.0, y/10.0);

		if (stitchType == 1)
		{
			ntrims++;
		}
		if (stitchType == 1 || stitchType == 2)
		{
			njumps++;
		}
		nstitchs++;
	}
	std::cout << "PES: " << iCol << "threads parsed" << std::endl;
	std::cout << "PES: " << nstitchs << "points parsed" << std::endl;
	std::cout << "PES: " << ntrims << "trims parsed" << std::endl;
	std::cout << "PES: " << njumps << "jumps parsed" << std::endl;

	in.close();
	return true;
}

