/*
 * Copyright (c) 2015 Tricoire Sebastien 3dsman@free.fr
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software
 * in a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 *
 */

#include "OE_document.h"
#include "OE_io.h"
#include "stitchs/OE_linestitch.h"
#include "stitchs/OE_birailstitch.h"
#include "stitchs/OE_linkstitch.h"
#include "stitchs/OE_staticstitch.h"
#include "stitchs/OE_fillstitch.h"
#include "Archive.h"
#include "JsonWriter.h"
#include "JsonReader.h"

#include <string>
#include <iostream>
#include <fstream>
#include <map>
#include <limits>
#include <cmath>

extern const OE_io ioOe;
extern const OE_io ioPes;
extern const OE_io ioSvg;
extern const OE_io ioJpg;
static const std::list<const OE_io*> ios = {&ioOe, &ioPes, &ioSvg, &ioJpg};

static bool endsWithExtension (const std::string& str, const std::string& suffix)
{
	std::string lstr = str;
	std::transform(lstr.begin(), lstr.end(), lstr.begin(), ::tolower);
	return lstr.size() >= 1+suffix.size() && lstr[lstr.size()-suffix.size()-1] == '.' && 0 == lstr.compare(lstr.size()-suffix.size(), suffix.size(), suffix);
}

static inline std::string buildDialogFilters(bool isLoad)
{
	std::string filterStart = "All supported (";
	std::string filterEnd;
	bool firstOne = true;
	for (const OE_io* io :ios)
	{
		if ((isLoad && io->loadFromFile) || (!isLoad && io->saveToFile))
		{
			if (!firstOne)
			{
				filterStart.append(" *.");
			}
			else
			{
				firstOne = false;
				filterStart.append("*.");
			}
			filterStart.append(io->extension);
			filterEnd.append(";;");
			filterEnd.append(io->description);
			filterEnd.append(" (*.");
			filterEnd.append(io->extension);
			filterEnd.append(")");
		}
	}
	filterStart.append(")");
	filterStart.append(filterEnd);
	return filterStart;
}

std::string OE_document::getWriteDialogFilters()
{
	return buildDialogFilters(false);
}

std::string OE_document::getReadDialogFilters()
{
	return buildDialogFilters(true);
}

OE_document::OE_document()
{
	std::list<OE_pattern*> standard_patterns = OE_pattern::createStandardPatterns();
	patterns.insert(patterns.end(), standard_patterns.begin(), standard_patterns.end());
}

OE_document::~OE_document()
{
}

OE_document::ScopeLock::ScopeLock(OE_document& doc) : std::lock_guard<std::recursive_mutex>(doc.accessLock)
{
}

bool OE_document::saveToFile(std::string path)
{
	for (const OE_io* io :ios)
	{
		if (io->saveToFile && endsWithExtension(path, io->extension))
		{
			ScopeLock lock(*this);
			return (*io->saveToFile)(this, path);
		}
	}
	return false;
}

void OE_document::persist(Pakal::Archive* archive)
{
	//archive->set_type_name("OE_document");
	archive->value("curves", curves);
	archive->value("threads", threads);
	archive->value("stitchs", stitchs);
}

bool OE_document::loadFromFile(std::string path)
{
	for (const OE_io* io :ios)
	{
		if (io->loadFromFile && endsWithExtension(path, io->extension))
		{
			ScopeLock lock(*this);
			if ((*io->loadFromFile)(path, this))
			{
				for (auto& stitch : stitchs)
				{
					stitch->refreshDependency();
				}
				return true;
			}
			return false;
		}
	}
	return false;
}

void OE_document::lock()
{
	accessLock.lock();
}

void OE_document::unlock()
{
	accessLock.unlock();
}

bool OE_document::trylock()
{
	return accessLock.try_lock();
}

BoundingBox OE_document::getBound() const
{
	BoundingBox box;
	if (!curves.empty() || !stitchs.empty())
	{
		for (const auto& curve : curves)
		{
			box += curve->getBound();
		}
		for (const auto& stitch : stitchs)
		{
			box += stitch->getBound();
		}
	}
	else
	{
		box += BoundingBox(-hoopSize.x/2, -hoopSize.y/2, hoopSize.x/2, hoopSize.y/2);
	}
	return box;
}

vector_2d OE_document::getHoopSize()
{
	return hoopSize;
}

unsigned int OE_document::getPulsePerMm()
{
	return pulsePerMm;
}

vector_2d OE_document::getZeroPoint()
{
	return zeroPoint;
}

void OE_document::setHoopSize(vector_2d hoopSize)
{
	this->hoopSize = hoopSize;
}

void OE_document::setPulsePerMm(unsigned int pulsePerMm)
{
	this->pulsePerMm = pulsePerMm;
}

void OE_document::setZeroPoint(vector_2d zeroPoint)
{
	this->zeroPoint = zeroPoint;
}


bool OE_document::addCurve(OE_pointcurve* curve)
{
	curves.push_back(curve);
	return true;
}

bool OE_document::addStitch(OE_stitchs* stitch)
{
	stitchs.push_back(stitch);
	return true;
}

bool OE_document::addThread(OE_thread* thread)
{
	threads.push_back(thread);
	return true;
}


void OE_document::removeCurve(OE_pointcurve* curve)
{
	selectedCurves.remove(curve);
	curves.remove(curve);
}

void OE_document::removeStitch(OE_stitchs* stitch)
{
	selectedStitchs.remove(stitch);
	stitchs.remove(stitch);
}

void OE_document::removeThread(OE_thread* thread)
{
	threads.remove(thread);
}


bool OE_document::refresh()
{
	return true;
}

/*
bool OE_document::draw(float dpi)
{
	for (unsigned i=0; i<curves.size(); i++)
	{
		curves.at(i).draw(dpi);
	}
	for (unsigned i=0; i<stitchs.size(); i++)
	{
		stitchs.at(i)->draw();
	}
	return true;
}*/

