/*******************************************************************

Part of the Fritzing project - http://fritzing.org
Copyright (c) 2007-2019 Fritzing

Fritzing 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.

Fritzing 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.old

You should have received a copy of the GNU General Public License
along with Fritzing.  If not, see <http://www.gnu.org/licenses/>.

********************************************************************/

#include "modelbase.h"
#include "../debugdialog.h"
#include "../items/partfactory.h"
#include "../items/moduleidnames.h"
#include "../utils/textutils.h"
#include "../utils/folderutils.h"
#include "../utils/fmessagebox.h"
#include "../version/version.h"
#include "../viewgeometry.h"

#include <QMessageBox>

QList<QString> ModelBase::CoreList;

/////////////////////////////////////////////////

ModelBase::ModelBase( bool makeRoot )
{
	m_checkForReversedWires = m_useOldSchematics = false;
	m_reportMissingModules = true;
	m_referenceModel = nullptr;
	m_root = nullptr;
	if (makeRoot) {
		m_root = new ModelPart();
		m_root->setModelPartShared(new ModelPartSharedRoot());
	}
}

ModelBase::~ModelBase() {
	if (m_root != nullptr) {
		ModelPartShared * modelPartShared = m_root->modelPartShared();
		if (modelPartShared != nullptr) {
			m_root->setModelPartShared(nullptr);
			delete modelPartShared;
		}
		delete m_root;
	}
}

ModelPart * ModelBase::root() {
	return m_root;
}

ModelPart * ModelBase::retrieveModelPart(const QString & /* moduleID */)  {
	return nullptr;
}

// loads a model from an fz file--assumes a reference model exists with all parts
bool ModelBase::loadFromFile(const QString & fileName, ModelBase * referenceModel, QList<ModelPart *> & modelParts, bool checkViews) {
	m_referenceModel = referenceModel;

	QFile file(fileName);
	QFileInfo fileInfo(file);
	QString onlyFileName = fileInfo.fileName();
	if (!file.open(QFile::ReadOnly | QFile::Text)) {
		FMessageBox::warning(nullptr, QObject::tr("Fritzing"),
		                     QObject::tr("Cannot read file %1:\n%2.")
		                     .arg(fileName)
		                     .arg(file.errorString()));
		return false;
	}

	QString errorStr;
	int errorLine;
	int errorColumn;
	QDomDocument domDocument;

	if (!domDocument.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) {
		FMessageBox::information(nullptr, QObject::tr("Fritzing"),
		                         QObject::tr("Parse error (1) at line %1, column %2:\n%3\n%4")
		                         .arg(errorLine)
		                         .arg(errorColumn)
								 .arg(errorStr, fileName));
		return false;
	}

	QDomElement root = domDocument.documentElement();
	if (root.isNull()) {
		FMessageBox::information(nullptr, QObject::tr("Fritzing"), QObject::tr("The file %1 is not a Fritzing file (2).").arg(fileName));
		return false;
	}

	Q_EMIT loadedRoot(fileName, this, root);

	if (root.tagName() != "module") {
		FMessageBox::information(nullptr, QObject::tr("Fritzing"), QObject::tr("The file %1 is not a Fritzing file (4).").arg(fileName));
		return false;
	}

	// QUESTION: Do these version checks make any sense for part bins?
	bool checkForOldSchematics = true;
	bool checkForRats = true;
	bool checkForTraces = true;
	bool checkForMysteryParts = true;
	bool checkForObsoleteSMDOrientation = true;
	bool correctPartLabelOffset = false;
	m_fritzingVersion = root.attribute("fritzingVersion");
	if (checkViews) {
		DebugDialog::debug(QString("Project %1 was created with Fritzing %2").arg(fileName, m_fritzingVersion), DebugDialog::Info);
	} else {
		DebugDialog::debug(QString("Parts Bin %1 was created with Fritzing %2").arg(fileName, m_fritzingVersion), DebugDialog::Info);
	}
	if (m_fritzingVersion.length() > 0) {
		// with version 0.4.3 ratsnests in fz files are obsolete
		VersionThing versionThingRats;
		versionThingRats.majorVersion = 0;
		versionThingRats.minorVersion = 4;
		versionThingRats.minorSubVersion = 2;
		versionThingRats.releaseModifier = "";
		VersionThing versionThingFz;
		Version::toVersionThing(m_fritzingVersion,versionThingFz);
		checkForRats = !Version::greaterThan(versionThingRats, versionThingFz);
		// with version 0.6.5 traces are copied to all views
		versionThingRats.minorVersion = 6;
		versionThingRats.minorSubVersion = 4;
		checkForTraces = !Version::greaterThan(versionThingRats, versionThingFz);
		// with version 0.7.6 mystery part spacing implementation changes
		versionThingRats.minorVersion = 7;
		versionThingRats.minorSubVersion = 5;
		checkForMysteryParts = !Version::greaterThan(versionThingRats, versionThingFz);
		// with version 0.8.0 flipSMD is horizontal
		versionThingRats.minorVersion = 7;
		versionThingRats.minorSubVersion = 13;
		checkForObsoleteSMDOrientation = !Version::greaterThan(versionThingRats, versionThingFz);
		// with version 0.8.6 we get a new schematic template
		versionThingRats.minorVersion = 8;
		versionThingRats.minorSubVersion = 5;
		checkForOldSchematics = !Version::greaterThan(versionThingRats, versionThingFz);
		// with version 0.9.3 we don't have to worry about reversed wires
		versionThingRats.minorVersion = 9;
		versionThingRats.minorSubVersion = 2;
		m_checkForReversedWires = !Version::greaterThan(versionThingRats, versionThingFz);

		correctPartLabelOffset = Version::greaterThan(m_fritzingVersion, "1.0.0a");
		if (correctPartLabelOffset) {
			Q_EMIT migratePartLabelOffset(m_fritzingVersion);
		}
	}
	VersionThing versionThingFz;
	Version::toVersionThing(m_fritzingVersion,versionThingFz);
	if (m_fritzingVersion.isEmpty()) {
		FMessageBox::warning(
			nullptr,
			QObject::tr("Missing Version Attribute"),
			QObject::tr("The loaded sketch is missing its 'fritzingVersion' attribute.\n\nFile: %1")
				.arg(onlyFileName)
			);
	} else if (!versionThingFz.ok) {
		FMessageBox::warning(
			nullptr,
			QObject::tr("Invalid Version Format"),
			QObject::tr("The loaded sketch's 'fritzingVersion' attribute '%1' is not formatted correctly.\n\nFile: %2")
				.arg(m_fritzingVersion, onlyFileName)
			);
	} else {
		VersionThing currentVersionThing;
		Version::toVersionThing(Version::versionString(), currentVersionThing);

		if (Version::greaterThan(currentVersionThing, versionThingFz)) {
			FMessageBox::warning(
				nullptr,
				QObject::tr("Version Mismatch"),
				QObject::tr("This sketch was created in a newer version of Fritzing (%1).\nYour current version is %2.\n\nPlease update Fritzing to ensure proper functionality.\n\nFile: %3")
					.arg(m_fritzingVersion, Version::versionString(), onlyFileName)
				);
		}
	}
	ModelPartSharedRoot * modelPartSharedRoot = this->rootModelPartShared();

	Q_EMIT loadedProjectProperties(root.firstChildElement("project_properties"));

	QDomElement title = root.firstChildElement("title");
	if (!title.isNull()) {
		if (modelPartSharedRoot != nullptr) {
			modelPartSharedRoot->setTitle(title.text());
		}
	}

	// ensures changeBinIcon() is not available
	// this may be a bug?
	QString iconFilename = root.attribute("icon");
	if (iconFilename.isEmpty()) {
		iconFilename = title.text() + ".png";
	}

	if (!iconFilename.isEmpty()) {
		if (modelPartSharedRoot != nullptr) {
			modelPartSharedRoot->setIcon(iconFilename);
		}
	}

	QString searchTerm = root.attribute("search");
	if (!searchTerm.isEmpty() && (modelPartSharedRoot != nullptr)) {
		modelPartSharedRoot->setSearchTerm(searchTerm);
	}

	QDomElement views = root.firstChildElement("views");
	Q_EMIT loadedViews(this, views);

	QDomElement instances = root.firstChildElement("instances");
	if (instances.isNull()) {
		FMessageBox::information(nullptr, QObject::tr("Fritzing"), QObject::tr("The file %1 is not a Fritzing file (3).").arg(fileName));
		return false;
	}

	// delete any aready-existing model parts
	for (int i = m_root->children().count() - 1; i >= 0; i--) {
		QObject* child = m_root->children()[i];
		child->setParent(nullptr);
		delete child;
	}

	Q_EMIT loadingInstances(this, instances);

	if (checkForRats) {
		QDomElement instance = instances.firstChildElement("instance");
		while (!instance.isNull()) {
			QDomElement nextInstance = instance.nextSiblingElement("instance");
			if (isRatsnest(instance)) {
				instances.removeChild(instance);
			}

			instance = nextInstance;
		}
	}

	if (checkForTraces) {
		QDomElement instance = instances.firstChildElement("instance");
		while (!instance.isNull()) {
			checkTraces(instance);
			instance = instance.nextSiblingElement("instance");
		}
	}

	if (checkForMysteryParts) {
		QDomElement instance = instances.firstChildElement("instance");
		while (!instance.isNull()) {
			checkMystery(instance);
			instance = instance.nextSiblingElement("instance");
		}
	}

	if (checkForObsoleteSMDOrientation) {
		QDomElement instance = instances.firstChildElement("instance");
		while (!instance.isNull()) {
			if (checkObsoleteOrientation(instance)) {
				Q_EMIT obsoleteSMDOrientationSignal();
				break;
			}
			instance = instance.nextSiblingElement("instance");
		}
	}

	m_useOldSchematics = false;
	if (checkForOldSchematics) {
		QDomElement instance = instances.firstChildElement("instance");
		while (!instance.isNull()) {
			if (checkOldSchematics(instance)) {
				Q_EMIT oldSchematicsSignal(fileName, m_useOldSchematics);
				break;
			}
			instance = instance.nextSiblingElement("instance");
		}
	}

	bool result = loadInstances(domDocument, instances, modelParts, checkViews);

	return result;
}

ModelPart * ModelBase::fixObsoleteModuleID(QDomDocument & domDocument, QDomElement & instance, QString & moduleIDRef) {
	return PartFactory::fixObsoleteModuleID(domDocument, instance, moduleIDRef, m_referenceModel);
}

bool ModelBase::loadInstances(QDomDocument & domDocument, QDomElement & instances, QList<ModelPart *> & modelParts, bool checkViews)
{
	QHash<QString, QString> missingModules;
	QDomElement instance = instances.firstChildElement("instance");
	ModelPart* modelPart = nullptr;
	while (!instance.isNull()) {
		Q_EMIT loadingInstance(this, instance);

		if (checkViews) {
			QDomElement views = instance.firstChildElement("views");
			QDomElement view = views.firstChildElement();
			if (views.isNull() || view.isNull()) {
				// do not load a part with no views
				//QString text;
				//QTextStream stream(&text);
				//instance.save(stream, 0);
				//DebugDialog::debug(text);
				instance = instance.nextSiblingElement("instance");
				continue;
			}
		}

		// for now assume all parts are in the palette
		QString moduleIDRef = instance.attribute("moduleIdRef");

		//DebugDialog::debug("loading " + moduleIDRef);
		if (moduleIDRef.compare(ModuleIDNames::SpacerModuleIDName) == 0) {
			auto * mp = new ModelPart(ModelPart::Space);
			mp->setInstanceText(instance.attribute("path"));
			mp->setParent(m_root);
			mp->modelPartShared()->setModuleID(ModuleIDNames::SpacerModuleIDName);
			mp->modelPartShared()->setPath(instance.attribute("path"));
			modelParts.append(mp);
			instance = instance.nextSiblingElement("instance");
			continue;
		}

		modelPart = m_referenceModel->retrieveModelPart(moduleIDRef);
		if (modelPart == nullptr) {
			DebugDialog::debug(QString("module id %1 not found in database").arg(moduleIDRef));
			modelPart = fixObsoleteModuleID(domDocument, instance, moduleIDRef);
		}
		if (modelPart == nullptr) {
			modelPart = genFZP(moduleIDRef, m_referenceModel);
			if (modelPart != nullptr) {
				instance.setAttribute("moduleIdRef", modelPart->moduleID());
				moduleIDRef = modelPart->moduleID();
			}
		}
		if (modelPart == nullptr) {
			missingModules.insert(moduleIDRef, instance.attribute("path"));
			instance = instance.nextSiblingElement("instance");
			continue;
		}

		if (modelPart->isCore() && m_useOldSchematics) {
			modelPart = createOldSchematicPart(modelPart, moduleIDRef);
		}

		modelPart->setInBin(true);
		modelPart = addModelPart(m_root, modelPart);
		modelPart->setInstanceDomElement(instance);
		modelParts.append(modelPart);

		// TODO Mariano: i think this is not the way
		QString instanceTitle = instance.firstChildElement("title").text();
		if(!instanceTitle.isNull() && !instanceTitle.isEmpty()) {
			modelPart->setInstanceTitle(instanceTitle, false);
		}

		QDomElement localConnectors = instance.firstChildElement("localConnectors");
		QDomElement localConnector = localConnectors.firstChildElement("localConnector");
		while (!localConnector.isNull()) {
			modelPart->setConnectorLocalName(localConnector.attribute("id"), localConnector.attribute("name"));
			localConnector = localConnector.nextSiblingElement("localConnector");
		}

		QString instanceText = instance.firstChildElement("text").text();
		if(!instanceText.isNull() && !instanceText.isEmpty()) {
			modelPart->setInstanceText(instanceText);
		}

		bool ok;
		long index = instance.attribute("modelIndex").toLong(&ok);
		if (ok) {
			// set the index so we can find the same model part later, as we continue loading
			modelPart->setModelIndex(index);
		}

		// note: this QDomNamedNodeMap loop is obsolete, but leaving it here so that old sketches don't get broken (jc, 22 Oct 2009)
		QDomNamedNodeMap map = instance.attributes();
		for (int m = 0; m < map.count(); m++) {
			QDomNode node = map.item(m);
			QString nodeName = node.nodeName();

			if (nodeName.isEmpty()) continue;
			if (nodeName.compare("moduleIdRef") == 0) continue;
			if (nodeName.compare("modelIndex") == 0) continue;
			if (nodeName.compare("originalModelIndex") == 0) continue;
			if (nodeName.compare("path") == 0) continue;

			modelPart->setLocalProp(nodeName, node.nodeValue());
		}

		// "property" loop replaces previous QDomNamedNodeMap loop (jc, 22 Oct 2009)
		QDomElement prop = instance.firstChildElement("property");
		while(!prop.isNull()) {
			QString name = prop.attribute("name");
			if (!name.isEmpty()) {
				QString value = prop.attribute("value");
				if (!value.isEmpty()) {
					modelPart->setLocalProp(name, value);
				}
			}

			prop = prop.nextSiblingElement("property");
		}

		instance = instance.nextSiblingElement("instance");
	}

	if (m_reportMissingModules && missingModules.count() > 0) {
		QString unableToFind = QString("<html><body><b>%1</b><br/><table style='border-spacing: 0px 12px;'>")
		                       .arg(tr("Unable to find the following %n part(s):", "", missingModules.count()));
		Q_FOREACH (QString key, missingModules.keys()) {
			unableToFind += QString("<tr><td>'%1'</td><td><b>%2</b></td><td>'%3'</td></tr>")
			                .arg(key).arg(tr("at")).arg(missingModules.value(key, ""));
		}
		unableToFind += "</table></body></html>";
		FMessageBox::warning(nullptr, QObject::tr("Fritzing"), unableToFind);
	}


	return true;
}

ModelPart * ModelBase::addModelPart(ModelPart * parent, ModelPart * copyChild) {

	//if (copyChild->moduleID() == "df9d072afa2b594ac67b60b4153ff57b_29") {
	//    DebugDialog::debug("alive in here");
	//}

	auto * modelPart = new ModelPart();
	modelPart->copyNew(copyChild);
	modelPart->setParent(parent);
	modelPart->initConnectors();
	modelPart->flipSMDAnd();
	return modelPart;
}

ModelPart * ModelBase::addPart(QString newPartPath, bool addToReference) {
	Q_UNUSED(newPartPath);
	Q_UNUSED(addToReference);
	throw "ModelBase::addPart should not be invoked";
	return nullptr;
}

ModelPart * ModelBase::addPart(QString newPartPath, bool addToReference, bool updateIdAlreadyExists)
{
	Q_UNUSED(updateIdAlreadyExists);
	Q_UNUSED(newPartPath);
	Q_UNUSED(addToReference);
	throw "ModelBase::addPart should not be invoked";
	return nullptr;
}

// TODO Mariano: this function should never get called. Make pure virtual
bool ModelBase::addPart(ModelPart * modelPart, bool update) {
	Q_UNUSED(modelPart);
	Q_UNUSED(update);
	throw "ModelBase::addPart should not be invoked";
	return false;
}


bool ModelBase::save(const QString & fileName, bool asPart) {
	QFileInfo info(fileName);
	QDir dir = info.absoluteDir();

	QString temp = dir.absoluteFilePath("temp.xml");
	QFile file1(temp);
	if (!file1.open(QFile::WriteOnly | QFile::Text)) {
		FMessageBox::warning(nullptr, QObject::tr("Fritzing"),
				     QObject::tr("Cannot write temp file. Save aborted. \n\nerror: %1\n\ntemp file: %2\n\ntarget file: %3.")
				     .arg(file1.errorString())
				     .arg(temp)
				     .arg(fileName)
		                    );
		return false;
	}

	QXmlStreamWriter streamWriter(&file1);
	save(fileName, streamWriter, asPart);
	file1.flush();
	if (streamWriter.hasError() || file1.error() != QFile::NoError) {
		FMessageBox::warning(nullptr, QObject::tr("Fritzing"),
				     QObject::tr("Error while writing temp file. Save aborted. \n\nerror: %1\n\ntemp file: %2\n\ntarget file: %3.")
				     .arg(file1.errorString())
				     .arg(temp)
				     .arg(fileName)
				    );
		file1.close();
		return false;
	}
	file1.close();
	QFile original(fileName);
	if(original.exists() && !original.remove()) {
		file1.remove();
		FMessageBox::warning(
		    nullptr,
		    tr("File save failed!"),
		    tr("Couldn't overwrite file '%1'.\nReason: %2 (errcode %3)")
		    .arg(fileName)
		    .arg(original.errorString())
		    .arg(original.error())
		);
		return false;
	}
	file1.rename(fileName);
	return true;
}

void ModelBase::save(const QString & fileName, QXmlStreamWriter & streamWriter, bool asPart) {
	streamWriter.setAutoFormatting(true);
	if(asPart) {
		m_root->saveAsPart(streamWriter, true);
	} else {
		m_root->saveInstances(fileName, streamWriter, true, false);
	}
}

bool ModelBase::paste(ModelBase * referenceModel, QByteArray & data, QList<ModelPart *> & modelParts, QHash<QString, QRectF> & boundingRects, bool preserveIndex)
{
	m_referenceModel = referenceModel;

	QDomDocument domDocument;
	QString errorStr;
	int errorLine;
	int errorColumn;
	bool result = domDocument.setContent(data, &errorStr, &errorLine, &errorColumn);
	if (!result) return false;

	QDomElement el = domDocument.documentElement();
	if (el.isNull()) {
		return false;
	}

	QDomElement boundingRectsElement = el.firstChildElement("boundingRects");
	if (!boundingRectsElement.isNull()) {
		QDomElement boundingRect = boundingRectsElement.firstChildElement("boundingRect");
		while (!boundingRect.isNull()) {
			QString name = boundingRect.attribute("name");
			QString rect = boundingRect.attribute("rect");
			QRectF br;
			if (!rect.isEmpty()) {
				QStringList s = rect.split(" ");
				if (s.count() == 4) {
					QRectF r(s[0].toDouble(), s[1].toDouble(), s[2].toDouble(), s[3].toDouble());
					br = r;
				}
			}
			boundingRects.insert(name, br);
			boundingRect = boundingRect.nextSiblingElement("boundingRect");
		}
	}

	QDomElement instances = el.firstChildElement("instances");
	if (instances.isNull()) {
		return false;
	}

	if (!preserveIndex) {
		// need to map modelIndexes from copied parts to new modelIndexes
		QHash<long, long> oldToNew;
		QDomElement instance = instances.firstChildElement("instance");
		while (!instance.isNull()) {
			long oldModelIndex = instance.attribute("modelIndex").toLong();
			oldToNew.insert(oldModelIndex, ModelPart::nextIndex());
			instance = instance.nextSiblingElement("instance");
		}
		renewModelIndexes(instances, "instance", oldToNew);
	}

	return loadInstances(domDocument, instances, modelParts, true);
}

void ModelBase::renewModelIndexes(QDomElement & parentElement, const QString & childName, QHash<long, long> & oldToNew)
{
	QDomElement instance = parentElement.firstChildElement(childName);
	while (!instance.isNull()) {
		long oldModelIndex = instance.attribute("modelIndex").toLong();
		instance.setAttribute("modelIndex", QString::number(oldToNew.value(oldModelIndex)));
		QDomElement views = instance.firstChildElement("views");
		if (!views.isNull()) {
			QDomElement view = views.firstChildElement();
			while (!view.isNull()) {
				bool ok;
				long superpart = view.attribute("superpart").toLong(&ok);
				if (ok) {
					view.setAttribute("superpart", QString::number(oldToNew.value(superpart)));
				}
				QDomElement connectors = view.firstChildElement("connectors");
				if (!connectors.isNull()) {
					QDomElement connector = connectors.firstChildElement("connector");
					while (!connector.isNull()) {
						QDomElement connects = connector.firstChildElement("connects");
						if (!connects.isNull()) {
							QDomElement connect = connects.firstChildElement("connect");
							while (!connect.isNull()) {
								bool ok;
								oldModelIndex = connect.attribute("modelIndex").toLong(&ok);
								if (ok) {
									long newModelIndex = oldToNew.value(oldModelIndex, -1);
									if (newModelIndex != -1) {
										connect.setAttribute("modelIndex", QString::number(newModelIndex));
									}
									else {
										//DebugDialog::debug(QString("keep old model index %1").arg(oldModelIndex));
									}
								}
								connect = connect.nextSiblingElement("connect");
							}
						}
						connector = connector.nextSiblingElement("connector");
					}
				}

				view = view.nextSiblingElement();
			}
		}

		instance = instance.nextSiblingElement(childName);
	}
}

void ModelBase::setReportMissingModules(bool b) {
	m_reportMissingModules = b;
}

ModelPart * ModelBase::genFZP(const QString & moduleID, ModelBase * referenceModel) {
	QString path = PartFactory::getFzpFilename(moduleID);
	if (path.isEmpty()) return nullptr;

	ModelPart* mp = referenceModel->addPart(path, true, true);
	if (mp != nullptr) mp->setCore(true);
	return mp;
}

ModelPartSharedRoot * ModelBase::rootModelPartShared() {
	if (m_root == nullptr) return nullptr;

	return m_root->modelPartSharedRoot();
}

bool ModelBase::isRatsnest(QDomElement & instance) {
	QString moduleIDRef = instance.attribute("moduleIdRef");
	if (moduleIDRef.compare(ModuleIDNames::WireModuleIDName) != 0) return false;

	QDomElement views = instance.firstChildElement("views");
	if (views.isNull()) return false;

	QDomElement view = views.firstChildElement();
	while (!view.isNull()) {
		QDomElement geometry = view.firstChildElement("geometry");
		if (!geometry.isNull()) {
			int flags = geometry.attribute("wireFlags").toInt();
			if ((flags & ViewGeometry::RatsnestFlag) != 0) {
				return true;
			}
			if ((flags & ViewGeometry::ObsoleteJumperFlag) != 0) {
				return true;
			}
		}

		view = view.nextSiblingElement();
	}

	return false;
}


bool ModelBase::checkOldSchematics(QDomElement & instance)
{
	if (instance.attribute("moduleIdRef").compare(ModuleIDNames::WireModuleIDName) != 0) {
		return false;
	}

	QDomElement views = instance.firstChildElement("views");
	QDomElement schematicView = views.firstChildElement("schematicView");
	QDomElement geometry = schematicView.firstChildElement("geometry");
	if (geometry.isNull()) return false;

	int flags = geometry.attribute("wireFlags", "0").toInt();
	return (flags & ViewGeometry::SchematicTraceFlag) != 0;
}


bool ModelBase::checkObsoleteOrientation(QDomElement & instance)
{
	QString flippedSMD = instance.attribute("flippedSMD", "");
	if (flippedSMD != "true") return false;

	QDomElement views = instance.firstChildElement("views");
	QDomElement pcbView = views.firstChildElement("pcbView");
	return (pcbView.attribute("layer", "") == "copper0");
}

void ModelBase::checkTraces(QDomElement & instance) {
	QString moduleIDRef = instance.attribute("moduleIdRef");
	if (moduleIDRef.compare(ModuleIDNames::WireModuleIDName) != 0) return;

	QDomElement views = instance.firstChildElement("views");
	if (views.isNull()) return;

	QDomElement bbView = views.firstChildElement("breadboardView");
	QDomElement schView = views.firstChildElement("schematicView");
	QDomElement pcbView = views.firstChildElement("pcbView");

	if (!bbView.isNull() && !schView.isNull() && !pcbView.isNull()) {
		// if it's a breadboard wire; just make sure flag is correct

		QList<QDomElement> elements;
		elements << bbView << schView << pcbView;
		Q_FOREACH (QDomElement element, elements) {
			QDomElement geometry = element.firstChildElement("geometry");
			if (!geometry.isNull()) {
				int flags = geometry.attribute("wireFlags").toInt();
				if ((flags & ViewGeometry::PCBTraceFlag) != 0) return;				// not a breadboard wire, bail out
				if ((flags & ViewGeometry::SchematicTraceFlag) != 0) return;		// not a breadboard wire, bail out

				if ((flags & ViewGeometry::NormalFlag) == 0) {
					flags |= ViewGeometry::NormalFlag;
					geometry.setAttribute("wireFlags", QString::number(flags));
				}
			}
		}


		return;
	}

	if (!bbView.isNull()) {
		QDomElement geometry = bbView.firstChildElement("geometry");
		if (!geometry.isNull()) {
			int flags = geometry.attribute("wireFlags").toInt();
			if ((flags & ViewGeometry::NormalFlag) == 0) {
				flags |= ViewGeometry::NormalFlag;
				geometry.setAttribute("wireFlags", QString::number(flags));
			}
		}
		schView = bbView.cloneNode(true).toElement();
		pcbView = bbView.cloneNode(true).toElement();
		schView.setTagName("schematicView");
		pcbView.setTagName("pcbView");
		views.appendChild(pcbView);
		views.appendChild(schView);
		return;
	}

	if (!schView.isNull()) {
		QDomElement geometry = schView.firstChildElement("geometry");
		if (!geometry.isNull()) {
			int flags = geometry.attribute("wireFlags").toInt();
			if ((flags & ViewGeometry::PCBTraceFlag) != 0) {
				flags ^= ViewGeometry::PCBTraceFlag;
				flags |= ViewGeometry::SchematicTraceFlag;
				geometry.setAttribute("wireFlags", QString::number(flags));
			}
		}
		pcbView = schView.cloneNode(true).toElement();
		bbView = schView.cloneNode(true).toElement();
		pcbView.setTagName("pcbView");
		bbView.setTagName("breadboardView");
		views.appendChild(bbView);
		views.appendChild(pcbView);
		return;
	}

	if (!pcbView.isNull()) {
		schView = pcbView.cloneNode(true).toElement();
		bbView = pcbView.cloneNode(true).toElement();
		schView.setTagName("schematicView");
		bbView.setTagName("breadboardView");
		views.appendChild(bbView);
		views.appendChild(schView);
		return;
	}

	QDomElement iconView = views.firstChildElement("iconView");
	if (!iconView.isNull()) return;

	QString string;
	QTextStream stream(&string);
	instance.save(stream, 0);
	stream.flush();
	DebugDialog::debug(QString("no wire view elements in fz file %1").arg(string));
}

const QString & ModelBase::fritzingVersion() {
	return m_fritzingVersion;
}

void ModelBase::setReferenceModel(ModelBase * modelBase) {
	m_referenceModel = modelBase;
}

void ModelBase::checkMystery(QDomElement & instance)
{
	QString moduleIDRef = instance.attribute("moduleIdRef");
	if (moduleIDRef.contains("mystery", Qt::CaseInsensitive)) {}
	else if (moduleIDRef.contains("sip", Qt::CaseInsensitive)) {}
	else if (moduleIDRef.contains("dip", Qt::CaseInsensitive)) {}
	else return;

	QString spacing;
	int pins = TextUtils::getPinsAndSpacing(moduleIDRef, spacing);

	QDomElement prop = instance.firstChildElement("property");
	while (!prop.isNull()) {
		if (prop.attribute("name", "").compare("spacing") == 0) {
			QString trueSpacing = prop.attribute("value", "");
			if (trueSpacing.isEmpty()) trueSpacing = "300mil";

			if (moduleIDRef.contains(spacing)) {
				moduleIDRef.replace(spacing, trueSpacing);
				instance.setAttribute("moduleIdRef", moduleIDRef);
				return;
			}

			// if we're here, it's a single sided mystery part.
			moduleIDRef = QString("mystery_part_sip_%1_100mil").arg(pins);
			instance.setAttribute("moduleIdRef", moduleIDRef);
			return;
		}
		prop = prop.nextSiblingElement("property");
	}
}

bool ModelBase::onCoreList(const QString & moduleID) {
	// CoreList contains db entries that are (presumably) overridden by an fzp in the parts folder
	return CoreList.contains(moduleID);

}

ModelPart * ModelBase::createOldSchematicPart(ModelPart * modelPart, QString & moduleIDRef) {
	QString schematicFilename = modelPart->imageFileName(ViewLayer::SchematicView, ViewLayer::Schematic);
	if (!schematicFilename.startsWith("schematic")) {
		schematicFilename = modelPart->imageFileName(ViewLayer::SchematicView, ViewLayer::SchematicFrame);
		if (!schematicFilename.startsWith("schematic")) {
			return modelPart;
		}
	}

	DebugDialog::debug("schematic " + schematicFilename);
	QString oldModuleIDRef = PartFactory::OldSchematicPrefix + moduleIDRef;
	ModelPart * oldModelPart = m_referenceModel->retrieveModelPart(oldModuleIDRef);         // cached after the first time it's created
	if (oldModelPart != nullptr) {
		moduleIDRef = oldModuleIDRef;
		return oldModelPart;
	}

	int ix = schematicFilename.indexOf("/");
	schematicFilename.insert(ix + 1, PartFactory::OldSchematicPrefix);
	QString oldSvgPath = FolderUtils::getAppPartsSubFolderPath("") + "/svg/obsolete/"+ schematicFilename;
	oldModelPart = createOldSchematicPartAux(modelPart, oldModuleIDRef, schematicFilename, oldSvgPath);
	if (oldModelPart != nullptr) {
		moduleIDRef = oldModuleIDRef;
		return oldModelPart;
	}

	oldSvgPath = ":resources/parts/svg/obsolete/"+ schematicFilename;
	oldModelPart = createOldSchematicPartAux(modelPart, oldModuleIDRef, schematicFilename, oldSvgPath);
	if (oldModelPart != nullptr) {
		moduleIDRef = oldModuleIDRef;
		return oldModelPart;
	}


	// see whether it's a generated part
	oldSvgPath = PartFactory::getSvgFilename(schematicFilename);
	if (!oldSvgPath.isEmpty()) {
		oldModelPart = createOldSchematicPartAux(modelPart, oldModuleIDRef, schematicFilename, oldSvgPath);
		if (oldModelPart != nullptr) {
			moduleIDRef = oldModuleIDRef;
			return oldModelPart;
		}
	}

	return modelPart;
}

ModelPart * ModelBase::createOldSchematicPartAux(ModelPart * modelPart, const QString & oldModuleIDRef, const QString & oldSchematicFileName, const QString & oldSvgPath)
{
	if (!QFile::exists(oldSvgPath)) return nullptr;

	// create oldModelPart, set up the new image file name, add it to refmodel
	QString path = modelPart->path();
	QFile newFzp(path);
	if (!newFzp.open(QIODevice::ReadOnly)) {
		DebugDialog::debug(QString("Unable to open :%1").arg(path));
	}
	QDomDocument oldDoc;
	QDomDocument::ParseResult parseResult = oldDoc.setContent(&newFzp);
	if (!parseResult.operator bool()) {
		QString logMessage = QString("Parse Error: %1 at line %2, column %3 in %4")
								 .arg(parseResult.errorMessage)
								 .arg(parseResult.errorLine)
								 .arg(parseResult.errorColumn)
								 .arg(path);
		DebugDialog::debug(logMessage);
		return nullptr;
	}

	QDomElement root = oldDoc.documentElement();
	root.setAttribute("moduleId", oldModuleIDRef);
	QDomElement views = root.firstChildElement("views");
	QDomElement schematicView = views.firstChildElement("schematicView");
	QDomElement layers = schematicView.firstChildElement("layers");
	if (layers.isNull()) {
		DebugDialog::debug(QString("Could not find layers in old schematic of %1").arg(oldModuleIDRef));
		return nullptr;
	}

	layers.setAttribute("image", oldSchematicFileName);

	QString oldFzpPath = PartFactory::fzpPath() + oldModuleIDRef + ".fzp";
	if (!TextUtils::writeUtf8(oldFzpPath, oldDoc.toString())) {
		DebugDialog::debug(QString("Error writing part to %1 .").arg(oldFzpPath));
		return nullptr;
	}

	ModelPart * oldModelPart = m_referenceModel->addPart(oldFzpPath, true, true);
	oldModelPart->setCore(modelPart->isCore());
	oldModelPart->setContrib(modelPart->isContrib());
	return oldModelPart;
}

bool ModelBase::checkForReversedWires() {
	return m_checkForReversedWires;
}
