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

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.

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

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

#include "pinheader.h"
#include "../utils/graphicsutils.h"
#include "../utils/textutils.h"
#include "../debugdialog.h"
#include "partfactory.h"
#include "utils/misc.h"



#include <QDomNodeList>
#include <QDomDocument>
#include <QDomElement>
#include <QLineEdit>
#include <QRegularExpression>

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

QString doubleCopyPinFunction(int pin, const QString & argString, void *)
{
	return argString.arg(pin * 2).arg(pin * 2 + 1).arg(pin * 2 + 1).arg(pin * 2 + 2);
}

QString stdIncCopyPinFunction(int pin, const QString & argString, void *)
{
	return argString.arg(pin).arg(pin + 1);
}

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


static QStringList Forms;

QString PinHeader::FemaleFormString;
QString PinHeader::FemaleRoundedFormString;
QString PinHeader::MaleFormString;
QString PinHeader::ShroudedFormString;
QString PinHeader::LongPadFormString;
QString PinHeader::MolexFormString;

static int MinPins = 1;
static int MinShroudedPins = 6;
static int MaxPins = 64;
static QMap<QString, QString> Spacings;
static QString ShroudedSpacing;

static HoleClassThing TheHoleThing;

PinHeader::PinHeader( ModelPart * modelPart, ViewLayer::ViewID viewID, const ViewGeometry & viewGeometry, long id, QMenu * itemMenu, bool doLabel)
	: PaletteItem(modelPart, viewID, viewGeometry, id, itemMenu, doLabel)
{

	setUpHoleSizes("pinheader", TheHoleThing);

	m_form = modelPart->localProp("form").toString();
	if (m_form.isEmpty()) {
		m_form = modelPart->properties().value("form", FemaleFormString);
		modelPart->setLocalProp("form", m_form);
	}
}

PinHeader::~PinHeader() {
}

void PinHeader::initNames() {
	if (FemaleFormString.isEmpty()) {
		FemaleFormString = FemaleSymbolString + " (female)";
		FemaleRoundedFormString = FemaleSymbolString + " (female rounded)";
		MaleFormString = MaleSymbolString + " (male)";
		ShroudedFormString = MaleSymbolString + " (shrouded male)";
		LongPadFormString = "long pad";
		MolexFormString = "molex";
	}
}

QStringList PinHeader::collectValues(const QString & family, const QString & prop, QString & value) {
	if (prop.compare("form", Qt::CaseInsensitive) == 0) {
		QStringList values;
		Q_FOREACH (QString f, forms()) {
			values.append(f);
		}

		value = m_form;
		return values;
	}

	if (prop.compare("row", Qt::CaseInsensitive) == 0) {
		QStringList values;
		values.append("single");
		values.append("double");

		value = (moduleID().contains("double") || moduleID().contains("shrouded")) ? "double" : "single";
		return values;
	}

	if (prop.compare("position", Qt::CaseInsensitive) == 0) {
		QStringList values;
		values.append("center");
		values.append("alternating");

		value = (moduleID().contains("alternating")) ? "alternating" : "center";
		return values;
	}

	if (prop.compare("package", Qt::CaseInsensitive) == 0) {
		QStringList values;
		values.append("through-hole");
		values.append("SMD");

		value = moduleID().contains("smd") ? "SMD" : "through-hole";
		return values;
	}

	if (prop.compare("pins", Qt::CaseInsensitive) == 0) {
		QStringList values;
		value = modelPart()->properties().value("pins");

		int step = 1;
		int minP = MinPins;
		if (m_form.contains("shrouded")) {
			minP = MinShroudedPins;
			step = 2;
		}
		if (m_form.contains("double")) {
			step = 2;
			minP = 2;
		}

		for (int i = minP; i <= MaxPins; i += step) {
			values << QString::number(i);
		}

		return values;
	}

	if (prop.compare("pin spacing", Qt::CaseInsensitive) == 0) {
		initSpacings();
		QStringList values;

		value = modelPart()->properties().value("pin spacing");
		if (m_form.contains("shrouded")) {
			values.append(value);
		}
		else {
			Q_FOREACH (QString key, Spacings.keys()) {
				values.append(Spacings.value(key));
			}
		}

		return values;
	}


	return PaletteItem::collectValues(family, prop, value);
}

QString PinHeader::getProperty(const QString & key) {
	if (key.compare("form", Qt::CaseInsensitive) == 0) {
		return m_form;
	}

	return PaletteItem::getProperty(key);
}

void PinHeader::addedToScene(bool temporary)
{
	if (this->scene() != nullptr) {
		//setForm(m_form, true);
	}

	return PaletteItem::addedToScene(temporary);
}

const QString & PinHeader::form() {
	return m_form;
}

const QStringList & PinHeader::forms() {
	if (Forms.count() == 0) {
		Forms << FemaleFormString << FemaleRoundedFormString << MaleFormString << ShroudedFormString << LongPadFormString << MolexFormString;
	}
	return Forms;
}

ItemBase::PluralType PinHeader::isPlural() {
	return Plural;
}

QString PinHeader::genFZP(const QString & moduleID)
{
	initSpacings();

	QString useModuleID = moduleID;
	int hsix = useModuleID.lastIndexOf(HoleSizePrefix);
	if (hsix >= 0) useModuleID.truncate(hsix);

	QStringList pieces = useModuleID.split("_");
	QString spacing = pieces.at(pieces.count() - 1);

	QString result = PaletteItem::genFZP(useModuleID, "generic_female_pin_header_fzpTemplate", MinPins, MaxPins, 1, useModuleID.contains("smd"));
	result.replace(".percent.", "%");
	QString form = MaleFormString;
	QString formBread = "male";
	QString formText = formBread;
	QString formSchematic = formBread;
	QString formModule = formBread;
	QString formPackage = useModuleID.contains("smd", Qt::CaseInsensitive) ? "smd" : "tht";
	QString formPosition = useModuleID.contains("alternating", Qt::CaseInsensitive) ? "alternating" : "";
	bool isDouble = useModuleID.contains("shrouded") || useModuleID.contains("double");
	QString formRow = isDouble ? "double" : "single";
	if (useModuleID.contains("rounded")) {
		form = FemaleRoundedFormString;
		if (useModuleID.contains("smd")) {
			formText = formRow + " row SMD rounded female";
			formModule = formText.toLower();
			formModule.replace(' ', '_');
			formBread = "rounded_female";
		}
		else {
			if (isDouble) {
				formBread = formModule = "double_row_rounded_female";
				formText = "double row rounded female";
			}
			else {
				formBread = formModule = "rounded_female";
				formText = "rounded female";
				if (!formPosition.isEmpty()) {
					formText += " alternating";
					formModule += "_alternating";
				}
			}
		}
		formSchematic = "female";
	}
	else if (useModuleID.contains("longpad")) {
		form = LongPadFormString;
		formBread = formModule = "longpad";
		formText = "longpad";
		if (!formPosition.isEmpty()) {
			formText += " alternating";
			formModule += "_alternating";
		}
	}
	else if (useModuleID.contains("molex")) {
		form = MolexFormString;
		formBread = formModule = "molex";
		formText = "molex";
	}
	else if (useModuleID.contains("female")) {
		form = FemaleFormString;
		if (useModuleID.contains("smd")) {
			formText = formRow + " row SMD female";
			formModule = formText.toLower();
			formModule.replace(' ', '_');
			formBread = "female";
		}
		else {
			if (isDouble) {
				formBread = formModule = "double_row_female";
				formText = "double row female";
			}
			else {
				formBread = formModule = formText = "female";
				if (!formPosition.isEmpty()) {
					formText += " alternating";
					formModule += "_alternating";
				}
			}
		}
		formSchematic = "female";
	}
	else if (useModuleID.contains("shrouded")) {
		form = ShroudedFormString;
		formText = formBread = formModule = "shrouded";
	}
	else if (useModuleID.contains("smd")) {
		formText = formRow + " row SMD male";
		formModule = formText.toLower();
		formModule.replace(' ', '_');
	}
	else if (isDouble) {
		formBread = formModule = "double_row_male";
		formText = "double row male";
	}
	else if (!formPosition.isEmpty()) {
		formText += " alternating";
		formModule += "_alternating";
	}


	result = result
	         .arg(Spacings.value(spacing, ""))
	         .arg(spacing)
	         .arg(form)
	         .arg(formBread)
	         .arg(formText)
	         .arg(formSchematic)
	         .arg(formModule)
	         .arg(formRow)
	         .arg(formPackage)
	         .arg(formPosition);
	;
	if (useModuleID.contains("smd")) {
		result.replace("nsjumper", QString("smd_%1_row_pin_header").arg(formRow));
		result.replace("jumper", QString("smd_%1_row_pin_header").arg(formRow));
	}
	else if (useModuleID.contains("shrouded")) {
		result.replace("nsjumper", "shrouded");
		result.replace("jumper", "shrouded");
	}
	else if (useModuleID.contains("longpad")) {
		result.replace("nsjumper", formModule);
		result.replace("jumper", formModule);
	}
	else if (useModuleID.contains("molex")) {
		result.replace("nsjumper", "molex");
		result.replace("jumper", "molex");
	}
	else if (isDouble) {
		result.replace("jumper", "jumper_double");
	}
	else if (!formPosition.isEmpty()) {
		result.replace("nsjumper", "nsjumper_alternating");
		result.replace("jumper", "jumper_alternating");
	}

	if (hsix >= 0) {
		return hackFzpHoleSize(result, moduleID, hsix);
	}

	return result;
}

QString PinHeader::genModuleID(QMap<QString, QString> & currPropsMap)
{
	initSpacings();
	QString pins = currPropsMap.value("pins");
	int p = pins.toInt();
	QString spacing = currPropsMap.value("pin spacing");
	if (spacing.isEmpty()) spacing = ShroudedSpacing;
	QString form = currPropsMap.value("form").toLower();
	QString package = currPropsMap.value("package").toLower();
	QString row = currPropsMap.value("row").toLower();
	QString position = currPropsMap.value("position").toLower();
	QString formWord = "male";
	bool isDouble = false;

	if (form.contains("shrouded")) {
		if (p < MinShroudedPins) {
			pins = QString::number(p = MinShroudedPins);
		}
		isDouble = true;
		spacing = ShroudedSpacing;
		formWord = "shrouded";
	}
	else if (form.contains("long pad")) {
		formWord = "longpad";
		if (position.contains("alternating")) {
			formWord += "_alternating";
		}
	}
	else if (form.contains("molex")) {
		formWord = "molex";
	}
	else if (form.contains("female")) {
		QString ff = form.contains("rounded") ? "rounded_female" : "female";
		if (package.contains("smd")) {
			if (row.contains("single", Qt::CaseInsensitive)) {
				formWord = "single_row_smd_" + ff;
			}
			else {
				isDouble = true;
				formWord = "double_row_smd_" + ff;
			}
		}
		else {
			if (row.contains("single", Qt::CaseInsensitive)) {
				formWord = ff;
				if (position.contains("alternating")) {
					formWord += "_alternating";
				}
			}
			else {
				isDouble = true;
				formWord = "double_row_" + ff;
			}
		}
	}
	else if (package.contains("smd")) {
		if (row.contains("single")) {
			formWord = "single_row_smd_male";
		}
		else {
			isDouble = true;
			formWord = "double_row_smd_male";
		}
	}
	else if (row.contains("double", Qt::CaseInsensitive)) {
		formWord = "double_row_male";
		isDouble = true;
	}
	else if (position.contains("alternating")) {
		formWord += "_alternating";
	}

	if (isDouble && (p % 2 == 1)) {
		pins = QString::number(p + 1);
	}

	Q_FOREACH (QString key, Spacings.keys()) {
		if (Spacings.value(key).compare(spacing, Qt::CaseInsensitive) == 0) {
			return QString("generic_%1_pin_header_%2_%3").arg(formWord).arg(pins).arg(key);
		}
	}

	return "";
}

QString PinHeader::makePcbSvg(const QString & originalExpectedFileName)
{
	QString expectedFileName = originalExpectedFileName;
	int hsix = expectedFileName.indexOf(HoleSizePrefix);
	if (hsix >= 0) {
		expectedFileName.truncate(hsix);
	}
	initSpacings();

	if (expectedFileName.contains("smd")) {
		return makePcbSMDSvg(expectedFileName);
	}

	QString spacingString;
	int pins = TextUtils::getPinsAndSpacing(expectedFileName, spacingString);
	if (pins == 0) return "";

	// only one spacing for mystery parts.
	if (expectedFileName.contains("mystery")) spacingString = "100mil";

	QString svg;

	if (expectedFileName.contains("shrouded")) {
		svg = makePcbShroudedSvg(pins);
	}
	else if (expectedFileName.contains("longpad")) {
		svg = makePcbLongPadSvg(pins, expectedFileName.contains("alternating"), spacingString);
	}
	else if (expectedFileName.contains("molex")) {
		svg = makePcbMolexSvg(pins, spacingString);
	}
	else {
		static QString pcbLayerTemplate = "";
		static QString pcbLayerTemplate2 = "";

		if (pcbLayerTemplate.isEmpty()) {
			QFile file(":/resources/templates/jumper_pcb_svg_template.txt");
			file.open(QFile::ReadOnly);
			pcbLayerTemplate = file.readAll();
			file.close();
			QFile file2(":/resources/templates/jumper_pcb_svg_2nd_template.txt");
			file2.open(QFile::ReadOnly);
			pcbLayerTemplate2 = file2.readAll();
			file2.close();
		}

		bool isDouble = expectedFileName.contains("double");
		QString useTemplate = isDouble ? pcbLayerTemplate2 : pcbLayerTemplate;

		double outerBorder = 15;
		double innerBorder = outerBorder / 2;
		double silkStrokeWidth = 10;
		double standardRadius = 27.5;
		double radius = 29;
		double copperStrokeWidth = 20;
		double totalWidth = (outerBorder * 2) + (silkStrokeWidth * 2) + (innerBorder * 2) + (standardRadius * 2) + copperStrokeWidth;
		double center = totalWidth / 2;
		double spacing = TextUtils::convertToInches(spacingString) * GraphicsUtils::StandardFritzingDPI;

		QString middle;

		bool addSquare = false;
		if (expectedFileName.contains("nsjumper")) {
		}
		else if (expectedFileName.contains("jumper")) {
			addSquare = true;
		}
		else {
			DebugDialog::debug(QString("square: expected filename is confusing %1").arg(expectedFileName));
		}

		double lockOffset = 5;  // mils
		double useLock = 0;
		if (!isDouble && expectedFileName.contains("alternating")) {
			useLock = lockOffset;
		}



		if (addSquare) {
			middle += QString( "<rect id='square' fill='none' height='%1' width='%1' stroke='rgb(255, 191, 0)' stroke-width='%2' x='%3' y='%4'/>\n")
			          .arg(radius * 2)
			          .arg(copperStrokeWidth)
			          .arg(center - radius + useLock)
			          .arg(center - radius);
		}

		QString circle("<circle cx='%1' cy='%2' fill='none' id='connector%3pin' r='%4' stroke='rgb(255, 191, 0)' stroke-width='%5'/>\n");

		if (isDouble) {
			for (int i = 0; i < pins / 2; i++) {
				middle += circle
				          .arg(center)
				          .arg(center + (i * spacing))
				          .arg(i)
				          .arg(radius)
				          .arg(copperStrokeWidth);
			}
			for (int i = 0; i < pins / 2; i++) {
				middle += circle
				          .arg(center)
				          .arg(center + (i * spacing))
				          .arg(i)
				          .arg(radius)
				          .arg(copperStrokeWidth);
			}
			for (int i = pins / 2; i < pins; i++) {
				middle += circle
				          .arg(center + 100)
				          .arg(center + ((pins - i - 1) * spacing))
				          .arg(i)
				          .arg(radius)
				          .arg(copperStrokeWidth);
			}

		}
		else {
			for (int i = 0; i < pins; i++) {
				middle += circle
				          .arg(center + (i % 2 == 0 ? useLock : -useLock))
				          .arg(center + (i * spacing))
				          .arg(i)
				          .arg(radius)
				          .arg(copperStrokeWidth);
			}
		}

		double totalHeight = totalWidth + (pins * spacing) - spacing;
		double originalTotalWidth = totalWidth;
		if (isDouble) {
			totalHeight = totalWidth + (spacing * pins / 2) - spacing;
			totalWidth += 100;
		}

		svg = useTemplate
		      .arg(totalWidth / GraphicsUtils::StandardFritzingDPI)
		      .arg(totalHeight / GraphicsUtils::StandardFritzingDPI)
		      .arg(totalWidth)
		      .arg(totalHeight)

		      .arg(totalWidth - outerBorder - (silkStrokeWidth / 2))
		      .arg(totalHeight - outerBorder - (silkStrokeWidth / 2))

		      .arg(originalTotalWidth - outerBorder - (silkStrokeWidth / 2))

		      .arg(silkStrokeWidth)
		      .arg(silkStrokeWidth / 2)
		      .arg(middle);
	}

	if (hsix >= 0) {
		return hackSvgHoleSizeAux(svg, originalExpectedFileName);
	}

	return svg;
}

void PinHeader::initSpacings() {
	if (Spacings.count() == 0) {
		ShroudedSpacing = "0.1in (2.54mm)";
		Spacings.insert("1mm", "0.03937in (1.0mm)");
		Spacings.insert("1.25mm", "0.04921in (1.25mm)");
		Spacings.insert("50mil", "0.05in (1.27mm)");
		Spacings.insert("1.5mm", "0.0591in (1.5mm)");
		Spacings.insert("2mm", "0.07874in (2mm)");
		Spacings.insert("2.5mm", "0.09843in (2.5mm)");
		Spacings.insert("100mil", ShroudedSpacing);
		Spacings.insert("150mil", "0.15in (3.81mm)");
		Spacings.insert("0.156in", "0.156in (3.96mm)");
		Spacings.insert("200mil", "0.2in (5.08mm)");
	}
}

QString PinHeader::makeSchematicSvg(const QString & expectedFileName)
{
	QStringList pieces = expectedFileName.split("_");
	if (pieces.count() < 7) return "";

	bool useOldSchematic = expectedFileName.contains(PartFactory::OldSchematicPrefix);

	QString spacingString;
	int pins = TextUtils::getPinsAndSpacing(expectedFileName, spacingString);
	QString form = expectedFileName.contains("female") ? "female" :"male";
	bool sizeTenth = expectedFileName.contains("10thin") || !useOldSchematic;  // some parts before version 0.8.5 used the 0.1 grid
	bool isDouble = sizeTenth && expectedFileName.contains("double");

	double width, unitHeight;
	double divisor = isDouble ? 2 : 1;
	if (sizeTenth) {
		width = isDouble ? 0.5 : 0.2;
		unitHeight = GraphicsUtils::StandardSchematicSeparation10thinMils / 1000;  // inches
	}
	else {
		unitHeight = GraphicsUtils::StandardSchematicSeparationMils / 1000;  // inches
		width = 0.87;
	}
	double unitHeightPoints = unitHeight * 72;

	QString header("<?xml version='1.0' encoding='utf-8'?>\n"
	               "<svg version='1.2' baseProfile='tiny' id='svg2' xmlns:svg='http://www.w3.org/2000/svg' "
	               "xmlns='http://www.w3.org/2000/svg'  x='0in' y='0in' width='%3in' "
	               "height='%1in' viewBox='0 0 %4 %2'>\n"
	               "<g id='schematic' >\n");


	QString svg = header.arg(unitHeight * pins / divisor).arg(unitHeightPoints * pins / divisor).arg(width).arg(width * 72);

	QString templateFile = QString(":/resources/templates/generic_%1_%2%3pin_header_schem_template.txt")
	                       .arg(form.contains("female") ? "female" : "male")
	                       .arg(sizeTenth ? "10thin_" : "")
	                       .arg(isDouble ? "double_" : "")
	                       ;
	if (sizeTenth) {
		if (isDouble) {
			svg += TextUtils::incrementTemplate(templateFile, pins / 2, unitHeightPoints, TextUtils::standardMultiplyPinFunction, doubleCopyPinFunction, nullptr);
		}
		else {
			svg += TextUtils::incrementTemplate(templateFile, pins, unitHeightPoints, TextUtils::standardMultiplyPinFunction, stdIncCopyPinFunction, nullptr);
		}
	}
	else {
		svg += TextUtils::incrementTemplate(templateFile, pins, unitHeightPoints, TextUtils::standardMultiplyPinFunction, TextUtils::standardCopyPinFunction, nullptr);
	}


	svg += "</g>\n</svg>";

	return svg;
}

QString PinHeader::makeBreadboardSvg(const QString & expectedFileName)
{
	QStringList pieces = expectedFileName.split("_");
	if (pieces.count() < 7) return "";

	int pinIndex = pieces.count() - 3;

	int pins = pieces.at(pinIndex).toInt();
	if (expectedFileName.contains("shrouded")) {
		return makeBreadboardShroudedSvg(pins);
	}
	if (expectedFileName.contains("double") && !expectedFileName.contains("smd", Qt::CaseInsensitive)) {
		//return makeBreadboardDoubleSvg(expectedFileName, pins);
	}

	double unitHeight = 0.1;  // inches
	double unitHeightPoints = unitHeight * 10000;

	QString header("<?xml version='1.0' encoding='utf-8'?>\n"
	               "<svg version='1.2' baseProfile='tiny' "
	               "xmlns='http://www.w3.org/2000/svg'  x='0in' y='0in' width='%1in' "
	               "height='0.1in' viewBox='0 0 %2 1000'>\n"
	               "<g id='breadboard' >\n");

	QString fileForm;
	if (expectedFileName.contains("round")) {
		fileForm = "rounded_female";
		header += "<rect fill='#404040' width='%2' height='1000'/>\n";
	}
	else if (expectedFileName.contains("female")) {
		fileForm = "female";
		header += "<rect fill='#404040' width='%2' height='1000'/>\n";
	}
	else {
		fileForm = "male";
	}

	QString svg = header.arg(unitHeight * pins).arg(unitHeightPoints * pins);
	svg += TextUtils::incrementTemplate(QString(":/resources/templates/generic_%1_pin_header_bread_template.txt").arg(fileForm),
	                                    pins, unitHeightPoints, TextUtils::standardMultiplyPinFunction, TextUtils::standardCopyPinFunction, nullptr);

	svg += "</g>\n</svg>";

	return svg;
}

QString PinHeader::makeBreadboardDoubleSvg(const QString & expectedFileName, int pins) {
	QString header("<?xml version='1.0' encoding='utf-8'?>\n"
	               "<svg version='1.2' baseProfile='tiny' "
	               "xmlns='http://www.w3.org/2000/svg'  x='0in' y='0in' width='%1in' "
	               "height='0.2in' viewBox='0 0 %2 2000'>\n"
	               "<g id='breadboard' >\n");

	QString fileForm;
	if (expectedFileName.contains("round")) {
		fileForm = "rounded_female";
		header += "<rect fill='#404040' width='%2' height='2000'/>\n";
	}
	else if (expectedFileName.contains("female")) {
		fileForm = "female";
		header += "<rect fill='#404040' width='%2' height='2000'/>\n";
	}
	else {
		fileForm = "male";
	}

	double unitHeight = 0.1;  // inches
	double unitHeightPoints = unitHeight * 10000;

	int userData[2];
	userData[0] = pins;
	userData[1] = 1;
	QString svg = header.arg(unitHeight * pins / 2).arg(unitHeightPoints * pins / 2);
	svg += TextUtils::incrementTemplate(QString(":/resources/templates/generic_%1_pin_header_bread_template.txt").arg(fileForm),
	                                    pins / 2, unitHeightPoints, TextUtils::standardMultiplyPinFunction, TextUtils::negIncCopyPinFunction, userData);

	svg += TextUtils::incrementTemplate(QString(":/resources/templates/generic_%1_pin_header_bread_2nd_template.txt").arg(fileForm),
	                                    pins / 2, unitHeightPoints, TextUtils::standardMultiplyPinFunction, TextUtils::standardCopyPinFunction, nullptr);

	svg += "</g>\n</svg>";

	return svg;
}


QString PinHeader::makeBreadboardShroudedSvg(int pins)
{
	QString header("<?xml version='1.0' encoding='utf-8'?>\n"
	               "<svg version='1.2' baseProfile='tiny' "
	               "xmlns='http://www.w3.org/2000/svg'  x='0in' y='0in' width='%1in' "
	               "height='0.3484167in' viewBox='0 0 [400] 348.4167in'>\n"
	               "<g id='breadboard' >\n"
	               "<rect id='bgnd' x='0' y='0' width='[400]' height='348.4167' stroke='none' stroke-width='0' fill='#1a1a1a' />\n"
	               "<rect id='top inset' x='0' y='0' width='[400]' height='38.861' stroke='none' stroke-width='0' fill='#2a2a29' />\n"
	               "<rect id='bottom inset' x='0' y='309.5557' width='[400]' height='38.861' stroke='none' stroke-width='0' fill='#595959' />\n"
	               "<path id='left inset'  d='M0,0 0,348.4167 38.861,309.5557 38.861,38.861z' stroke='none' stroke-width='0' fill='#373737' />\n"
	               "<path id='right inset'  d='M[400],0 [400],348.4167 [361.139],309.5557 [361.139],38.861z' stroke='none' stroke-width='0' fill='#474747' />\n"
	               "<rect id='top border' x='0' y='0' width='[400]' height='24.972' stroke='none' stroke-width='0' fill='#404040' />\n"
	               "<rect id='bottom border' x='0' y='323.4447' width='[400]' height='24.972' stroke='none' stroke-width='0' fill='#404040' />\n"
	               "<rect id='left border' x='0' y='0' width='24.972' height='348.4167' stroke='none' stroke-width='0' fill='#404040' />\n"
	               "<rect id='right border' x='[375.028]' y='0' width='24.972' height='348.4167' stroke='none' stroke-width='0' fill='#404040' />\n"
	               "%2\n"
	               "%3\n"
	               "<rect id='slot' x='{115.9514}' y='280.4973' width='168.0972' height='67.9194' stroke='none' stroke-width='0' fill='#1a1a1a' />\n"
	               "</g>\n"
	               "</svg>\n"
	              );

	QString repeatT("<rect id='upper connector bgnd' x='[173.618055]' y='95.972535' width='52.76389' height='52.76389' stroke='none' stroke-width='0' fill='#141414' />\n"
	                "<rect id='connector%1pin' x='[184.03472]' y='106.3892' width='31.93056' height='31.93056' stroke='none' stroke-width='0' fill='#8c8663' />\n"
	                "<rect id='upper connector top inset' x='[184.03472]' y='106.3892' width='31.93056' height='7.75' stroke='none' stroke-width='0' fill='#B8AF82' />\n"
	                "<rect id='upper connector bottom inset' x='[184.03472]' y='130.56976' width='31.93056' height='7.75' stroke='none' stroke-width='0' fill='#5E5B43' />\n"
	                "<path id='upper connector left inset' d='M[184.03472],106.3892 [184.03472],138.31976 [191.7847],130.56976 [191.7847],114.1392z' stroke='none' stroke-width='0' fill='#9A916C' />\n"
	                "<path id='upper connector right inset' d='M[215.96522],106.3892 [215.96522],138.31976 [208.21522],130.56976 [208.21522],114.1392z' stroke='none' stroke-width='0' fill='#9A916C' />\n"
	               );

	QString repeatB("<rect id='lower connector bgnd' x='[173.618055]' y='195.972535' width='52.76389' height='52.76389' stroke='none' stroke-width='0' fill='#141414' />\n"
	                "<rect id='connector%1pin' x='[184.03472]' y='206.3892' width='31.93056' height='31.93056' stroke='none' stroke-width='0' fill='#8c8663' />\n"
	                "<rect id='lower connector top inset' x='[184.03472]' y='206.3892' width='31.93056' height='7.75' stroke='none' stroke-width='0' fill='#B8AF82' />\n"
	                "<rect id='lower connector bottom inset' x='[184.03472]' y='230.56976' width='31.93056' height='7.75' stroke='none' stroke-width='0' fill='#5E5B43' />\n"
	                "<path id='lower connector left inset' d='M[184.03472],206.3892 [184.03472],238.31976 [191.7847],230.56976 [191.7847],214.1392z' stroke='none' stroke-width='0' fill='#9A916C' />\n"
	                "<path id='lower connector right inset' d='M[215.96522],206.3892 [215.96522],238.31976 [208.21522],230.56976 [208.21522],214.1392z' stroke='none' stroke-width='0' fill='#9A916C' />\n"
	               );


	double increment = 100;  // 0.1in

	QString svg = TextUtils::incrementTemplateString(header, 1, increment * (pins - 2) / 2, TextUtils::incMultiplyPinFunction, TextUtils::noCopyPinFunction, nullptr);
	svg.replace("{", "[");
	svg.replace("}", "]");
	svg = TextUtils::incrementTemplateString(svg, 1, increment * (pins - 2) / 4, TextUtils::incMultiplyPinFunction, TextUtils::noCopyPinFunction, nullptr);

	int userData[2];
	userData[0] = pins;
	userData[1] = 1;
	QString repeatTs = TextUtils::incrementTemplateString(repeatT, pins / 2, increment, TextUtils::standardMultiplyPinFunction, TextUtils::negIncCopyPinFunction, userData);
	QString repeatBs = TextUtils::incrementTemplateString(repeatB, pins / 2, increment, TextUtils::standardMultiplyPinFunction, TextUtils::standardCopyPinFunction, nullptr);

	return svg.arg(TextUtils::getViewBoxCoord(svg, 2) / 1000.0).arg(repeatTs).arg(repeatBs);
}

QString PinHeader::makePcbShroudedSvg(int pins)
{
	QString header("<?xml version='1.0' encoding='utf-8'?>\n"
	               "<svg version='1.2' baseProfile='tiny' xmlns='http://www.w3.org/2000/svg' \n"
	               "x='0in' y='0in' width='0.3542in' height='%1in' viewBox='0 0 3542 [3952]'>"
	               "<g id='copper0' >\n"
	               "<g id='copper1' >\n"
	               "<rect id='square' x='936' y='1691' width='570' height='570' stroke='#ff9400' r='285' fill='none' stroke-width='170'/>\n"
	               "%2\n"
	               "%3\n"
	               "</g>\n"
	               "</g>\n"
		       "<g id='silkscreen' >\n"
		       "<rect x='40' y='40' width='3462' height='[3872]' fill='none' stroke='black' stroke-width='80'/>\n"
		       "<path d='m473,{1150} 0,-{677} 2596,0 0,[3076] -2596,0 0,-{677}' fill='none' stroke='black' stroke-width='80'/>\n"
		       "<rect x='40' y='{1150}' width='550' height='1652' fill='none' stroke='black' stroke-width='80'/>\n"
	               "</g>\n"
	               "</svg>\n"
	              );


	QString repeatL = "<circle id='connector%1pin' cx='1221' cy='[1976]' stroke='#ff9400' r='285' fill='none' stroke-width='170'/>\n";
	QString repeatR = "<circle id='connector%1pin' cx='2221' cy='[1976]' stroke='#ff9400' r='285' fill='none' stroke-width='170'/>\n";


	double increment = 1000;  // 0.1in

	QString svg = TextUtils::incrementTemplateString(header, 1, increment * (pins - 2) / 2, TextUtils::incMultiplyPinFunction, TextUtils::noCopyPinFunction, nullptr);
	svg.replace("{", "[");
	svg.replace("}", "]");
	svg = TextUtils::incrementTemplateString(svg, 1, increment * (pins - 2) / 4, TextUtils::incMultiplyPinFunction, TextUtils::noCopyPinFunction, nullptr);

	int userData[2];
	userData[0] = pins;
	userData[1] = 1;
	QString repeatLs = TextUtils::incrementTemplateString(repeatR, pins / 2, increment, TextUtils::standardMultiplyPinFunction, TextUtils::negIncCopyPinFunction, userData);
	QString repeatRs = TextUtils::incrementTemplateString(repeatL, pins / 2, increment, TextUtils::standardMultiplyPinFunction, TextUtils::standardCopyPinFunction, nullptr);

	return svg.arg(TextUtils::getViewBoxCoord(svg, 3) / 10000.0).arg(repeatLs).arg(repeatRs);
}

QString PinHeader::makePcbLongPadSvg(int pins, bool isAlternating, const QString & spacingString)
{
	if (isAlternating) return makePcbLongPadAlternatingSvg(pins, spacingString);

	double dpi = 25.4;
	double originalHeight = 0.108;           // inches
	double increment = 0.1;                 // inches
	if (auto inc = TextUtils::convertToInches(spacingString, false)) {
		increment = *inc;
	}
	QString header("<?xml version='1.0' encoding='utf-8'?>\n"
	               "<svg version='1.2' baseProfile='tiny' xmlns='http://www.w3.org/2000/svg' \n"
	               "x='0in' y='0in' width='0.148in' height='%1in' viewBox='0 0 3.7592 %2'>\n"
	               "<g id='copper0' >\n"
	               "<g id='copper1' >\n"
	               "%3\n"
	               "</g>\n"
	               "</g>\n"
	               "<g id='silkscreen' >\n"
	               "<line class='other' x1='1.2446' y1='0.1016' x2='2.5146' y2='0.1016' stroke='#f0f0f0' stroke-width='0.2032' stroke-linecap='round'/>\n"
	               "<line class='other' x1='1.2446' y1='%4' x2='2.5146' y2='%4' stroke='#f0f0f0' stroke-width='0.2032' stroke-linecap='round'/>\n"
	               "</g>\n"
	               "</svg>\n"
	              );


	double cy = 1.3716;
	double cy2 = 0.8128;
	QString repeat = "<circle id='connector%1pin' cx='1.8796' cy='%2' r='0.7493' stroke='#F7BD13' stroke-width='0.381' fill='none' />\n"
	                 "<path stroke='none' stroke-width='0' fill='#F7BD13'\n"
	                 "d='m0,%2a0.9398,0.9398 0 0 0 0.9398,0.9398l1.8796,0a0.9398,0.9398 0 0 0 0.9398,-0.9398l-0,0"
	                 "a0.9398,0.9398 0 0 0 -0.9398,-0.9398l-1.8796,0a0.9398,0.9398 0 0 0 -0.9398,0.9398l0,0z"
	                 "M1.8796,%3a0.5588,0.5588 0 1 1 0,1.1176 0.5588,0.5588 0 1 1 0,-1.1176z' />\n";

	QString repeats;
	for (int i = 0; i < pins; i++) {
		repeats += repeat.arg(i).arg(cy).arg(cy2);
		cy += increment * dpi;
		cy2 += increment * dpi;
	}

	double totalHeight = originalHeight + ((pins - 1) * increment);
	double lineOffset = 0.1016;  // already in dpi
	return header.arg(totalHeight).arg(totalHeight * dpi).arg(repeats).arg(totalHeight * dpi - lineOffset);
}

QString PinHeader::makePcbLongPadAlternatingSvg(int pins, const QString & spacingString)
{
	double dpi = 25.4;
	double originalHeight = 0.108;           // inches
	double increment = 0.1;                 // inches
	if (auto inc = TextUtils::convertToInches(spacingString, false)) {
		increment = *inc;
	}
	QString header("<?xml version='1.0' encoding='utf-8'?>\n"
	               "<svg version='1.2' baseProfile='tiny' xmlns='http://www.w3.org/2000/svg' \n"
	               "x='0in' y='0in' width='0.13in' height='%1in' viewBox='0 0 3.302 %2'>\n"
	               "<g id='copper0' >\n"
	               "<g id='copper1' >\n"
	               "%3\n"
	               "</g>\n"
	               "</g>\n"
	               "<g id='silkscreen' >\n"
	               "<line class='other' x1='1.651' y1='0.1016' x2='1.651' y2='0.3556' stroke='#f0f0f0' stroke-width='0.2032' stroke-linecap='round'/>\n"
	               "<line class='other' x1='1.651' y1='0.1016' x2='0.6604' y2='0.1016' stroke='#f0f0f0' stroke-width='0.2032' stroke-linecap='round'/>\n"
	               "<line class='other' x1='0.6604' y1='0.1016' x2='0.381' y2='0.381' stroke='#f0f0f0' stroke-width='0.2032' stroke-linecap='round'/>\n"
	               "<line class='other' x1='1.651' y1='0.1016' x2='2.6416' y2='0.1016' stroke='#f0f0f0' stroke-width='0.2032' stroke-linecap='round'/>\n"
	               "<line class='other' x1='2.6416' y1='0.1016' x2='2.921' y2='0.381' stroke='#f0f0f0' stroke-width='0.2032' stroke-linecap='round'/>\n"
	               "%4\n"
	               "%5\n"
	               "</g>\n"
	               "</svg>\n"
	              );

	QString bottom("<line class='other' x1='1.651' y1='[2.6416]' x2='1.651' y2='[2.3876]' stroke='#f0f0f0' stroke-width='0.2032' stroke-linecap='round'/>\n"
	               "<line class='other' x1='1.651' y1='[2.6416]' x2='2.6416' y2='[2.6416]' stroke='#f0f0f0' stroke-width='0.2032' stroke-linecap='round'/>\n"
	               "<line class='other' x1='2.6416' y1='[2.6416]' x2='2.921' y2='[2.3622]' stroke='#f0f0f0' stroke-width='0.2032' stroke-linecap='round'/>\n"
	               "<line class='other' x1='1.651' y1='[2.6416]' x2='0.6604' y2='[2.6416]' stroke='#f0f0f0' stroke-width='0.2032' stroke-linecap='round'/>\n"
	               "<line class='other' x1='0.6604' y1='[2.6416]' x2='0.381' y2='[2.3622]' stroke='#f0f0f0' stroke-width='0.2032' stroke-linecap='round'/>\n");
	bottom = TextUtils::incrementTemplateString(bottom, 1, increment * dpi * (pins - 1), TextUtils::incMultiplyPinFunction, TextUtils::noCopyPinFunction, nullptr);

	QString between;
	QString betweens;
	double lineLength = 0.508 + increment * dpi - 0.1 * dpi;
	if (lineLength > 0.0) {
		between = QString("<line class='other' x1='1.651' y1='[%1]' x2='1.651' y2='[2.3876]' stroke='#f0f0f0' stroke-width='0.2032' stroke-linecap='round'/>\n").arg(2.3876 + lineLength);
		betweens = TextUtils::incrementTemplateString(between, pins - 1, increment * dpi, TextUtils::standardMultiplyPinFunction, TextUtils::noCopyPinFunction, nullptr);
	}

	double ncy = 1.3716;
	double ncx = 1.524;
	double ncy2 = 0.8636;
	double ncx2 = 0;
	double offset = 0.254;

	QString repeat("<circle id='connector%1pin' cx='%3' cy='%2' r='0.635' stroke='#F7BD13' stroke-width='0.254' fill='none' />\n"
	               "<path fill='#F7BD13' stroke='none' stroke-width='0'\n"
	               "d='m%4,%2a0.762,0.762 0 0 0 0.762,0.762l1.524,0a0.762,0.762 0 0 0 0.762,-0.762l-0,0"
	               "a0.762,0.762 0 0 0 -0.762,-0.762l-1.524,0a0.762,0.762 0 0 0 -0.762,0.762l0,0z\n"
	               "M%3,%5a0.508,0.508 0 0 1 0,1.016 0.508,0.508 0 0 1 0,-1.016z' />\n");

	QString repeats;
	for (int i = 0; i < pins; i++) {
		double useOffset = (i % 2 == 1) ? offset : 0;
		repeats += repeat.arg(i).arg(ncy).arg(ncx + useOffset).arg(ncx2 + useOffset).arg(ncy2);
		ncy += increment * dpi;
		ncy2 += increment * dpi;
	}

	double totalHeight = originalHeight + ((pins - 1) * increment);
	return header.arg(totalHeight).arg(totalHeight * dpi).arg(repeats).arg(bottom).arg(betweens);
}

QString PinHeader::makePcbMolexSvg(int pins, const QString & spacingString)
{
	double dpi = 25.4;
	double originalHeight = 0.105;           // inches
	double increment = 0.1;                 // inches
	if (auto inc = TextUtils::convertToInches(spacingString, false)) {
		increment = *inc;
	}
	QString header("<?xml version='1.0' encoding='utf-8'?>\n"
	               "<svg version='1.2' baseProfile='tiny' xmlns='http://www.w3.org/2000/svg' \n"
	               "x='0in' y='0in' width='0.225in' height='%1in' viewBox='0 0 5.715 %2'>\n"
	               "<g id='copper0' >\n"
	               "<g id='copper1' >\n"
	               "<rect id='square' stroke='#F7BD13' stroke-width='0.4318' fill='none' x='2.3876' y='.6096' width='1.4478' height='1.4478' />\n"
	               "%3\n"
	               "</g>\n"
	               "</g>\n"
	               "<g id='silkscreen' >\n"
	               "<rect class='other' stroke='#f0f0f0' stroke-width='0.127' fill='none' stroke-linecap='round' x='0.0635' y='0.0635'  width='5.588' height='%4' />\n"
	               "<rect class='other' stroke='#f0f0f0' stroke-width='0.127' fill='none' stroke-linecap='round' x='4.3815' y='1.3335'  width='1.27' height='%5' />\n"
	               "</g>\n"
	               "</svg>\n"
	              );


	double cy = 1.3335;         // already in dpi
	QString repeat = "<circle id='connector%1pin' cx='3.1115' cy='%2' r='0.7239' stroke='#F7BD13' stroke-width='0.4318' fill='none' />\n";

	QString repeats;
	for (int i = 0; i < pins; i++) {
		repeats += repeat.arg(i).arg(cy + (i * increment * dpi));
	}

	double totalHeight = originalHeight + ((pins - 1) * increment);
	double strokeWidth = .127;  // already in dpi
	double h = 0.0001;
	return header.arg(totalHeight).arg(totalHeight * dpi).arg(repeats).arg((totalHeight * dpi) - strokeWidth).arg(qMax(h, (totalHeight * dpi) - cy - cy));
}

QString PinHeader::makePcbSMDSvg(const QString & expectedFileName)
{
	QStringList pieces = expectedFileName.split("_");

	int pins = pieces.at(5).toInt();
	QString spacingString = pieces.at(6);

	bool singleRow = expectedFileName.contains("single");

	double spacing = TextUtils::convertToInches(spacingString) * GraphicsUtils::StandardFritzingDPI;

	QString header("<?xml version='1.0' encoding='utf-8'?>\n"
	               "<svg version='1.2' baseProfile='tiny' xmlns:svg='http://www.w3.org/2000/svg' "
	               "xmlns='http://www.w3.org/2000/svg'  x='0in' y='0in' width='%1in' "
	               "height='%4in' viewBox='0 0 %2 %5'>\n"
	               "<g id='silkscreen' >\n"
		       "<rect x='2' y='86.708672' width='%3' height='%6' fill='none' stroke='black' stroke-width='4' stroke-opacity='1'/>\n"
	               "</g>\n"
	               "<g id='copper1'>\n");


	double baseWidth = 152.7559;			// mils
	double totalHeight = 283.4646;
	double totalWidth = baseWidth + ((pins - 1) * spacing);
	double rectHeight = 102.047256;
	double y = 141.823;
	if (!singleRow) {
		totalHeight = 393.7;
		totalWidth = baseWidth + ((pins - 2) * spacing / 2);
		rectHeight = 200;
		y = 251.9685;
	}

	QString svg = header.arg(totalWidth / GraphicsUtils::StandardFritzingDPI)
	              .arg(totalWidth)
	              .arg(totalWidth - 4)
	              .arg(totalHeight  / GraphicsUtils::StandardFritzingDPI)
	              .arg(totalHeight)
	              .arg(rectHeight);

	double x = 51.18110;
	if (singleRow) {
		for (int i = 0; i < pins; i++) {
			double ay = (i % 2 == 0) ? 0 : y;
			svg += QString("<rect id='connector%1pin' x='%2' y='%3' width='51.18110' height='141.823' fill='#f7bf13' fill-opacity='1' stroke='none' stroke-width='0'/>\n").arg(i).arg(x).arg(ay);
			x += spacing;
		}
	}
	else {
		double holdX = x;
		for (int i = 0; i < pins / 2; i++) {
			svg += QString("<rect id='connector%1pin' x='%2' y='0' width='51.18110' height='141.823' fill='#f7bf13' fill-opacity='1' stroke='none' stroke-width='0'/>\n").arg(pins - 1 - i).arg(x);
			x += spacing;
		}
		x = holdX;
		for (int i = 0; i < pins / 2; i++) {
			svg += QString("<rect id='connector%1pin' x='%2' y='%3' width='51.18110' height='141.823' fill='#f7bf13' fill-opacity='1' stroke='none' stroke-width='0'/>\n").arg(i).arg(x).arg(y);
			x += spacing;
		}
	}

	svg += "</g>\n</svg>";
	return svg;
}

bool PinHeader::collectExtraInfo(QWidget * parent, const QString & family, const QString & prop, const QString & value, bool swappingEnabled, QString & returnProp, QString & returnValue, QWidget * & returnWidget, bool & hide)
{
	if (prop.compare("hole size", Qt::CaseInsensitive) == 0) {
		if (moduleID().contains("smd", Qt::CaseInsensitive)) {
			// or call the ancestor function?
			return false;
		}
		else {
			return collectHoleSizeInfo(TheHoleThing.holeSizeValue, parent, swappingEnabled, returnProp, returnValue, returnWidget);
		}
	}

	return PaletteItem::collectExtraInfo(parent, family, prop, value, swappingEnabled, returnProp, returnValue, returnWidget, hide);
}

void PinHeader::swapEntry(const QString & text) {
	generateSwap(text, genModuleID, genFZP, makeBreadboardSvg, makeSchematicSvg, makePcbSvg);
	PaletteItem::swapEntry(text);
}
