#!/usr/bin/swift
// create an ISBN barcode
// Jerry Stratton astoundingscripts.com

import Quartz

var arguments = CommandLine.arguments
var commandName = arguments.removeFirst()
func help(message:String = "") {
	if message != "" {
		print("\n"+message+"\n")
	}
	print("Syntax:", commandName, "<isbn list> [--pdf]")
	print("Creates PNG file of bar code for each listed ISBN.")
	exit(0)
}

var isbnList = [String]()
var saveAsPDF = false
while (arguments.count > 0) {
	let argument = arguments.removeFirst()
	if argument == "--pdf" {
		saveAsPDF = true
	} else {
		isbnList.append(argument)
	}
}
if isbnList.count == 0 {
	help(message:"at least one ISBN is required")
}

//constants
let fontname = "Times New Roman"
let dpiScale = 72.0/300.0
let scale = 12.6
let paddingTop = 4*scale
let paddingX = 9*scale
//text seems to have more height than the image says it has, by about this much
let textAdjustment = 0.1
let captionSize = 8*scale
let labelSize = 6*scale

//border
let backgroundColor = NSColor(red:1, green:1, blue:1, alpha:1)
let borderColor = NSColor(red:0, green:0, blue:0, alpha:1)
let borderWidth = 2.0

//caption styles
var captionAttributes: [NSAttributedString.Key: Any] = [:]
let captionStyle = NSMutableParagraphStyle()
captionStyle.alignment = NSTextAlignment.center
let captionFont = NSFont(name: fontname, size: captionSize)!
captionAttributes[NSAttributedString.Key.font] = captionFont
captionAttributes[NSAttributedString.Key.paragraphStyle] = captionStyle

//label styles
var labelAttributes: [NSAttributedString.Key: Any] = [:]
let labelFont = NSFont(name: fontname, size: labelSize)!
labelAttributes[NSAttributedString.Key.font] = labelFont
labelAttributes[NSAttributedString.Key.paragraphStyle] = captionStyle

//create each ISBN image
while isbnList.count > 0 {
	var isbn = isbnList.removeFirst()

	//validate characters
	if isbn.range(of: "^[0-9-]+$", options: .regularExpression) == nil {
		help(message:isbn + " contains invalid characters for an ISBN")
	}

	//validate digits only
	isbn = isbn.replacingOccurrences(of: "-", with: "")
	if isbn.count != 13 {
		help(message:isbn + " is not 13 digits")
	}

	//validate checksum
	var checksum = 0
	for (index, digit) in isbn.enumerated() {
		let digitValue = digit.wholeNumberValue!
		checksum += digitValue
		if index % 2 == 1 { checksum += 2*digitValue }
	}
	if checksum % 10 != 0 {
		help(message:isbn + " does not checksum correctly.")
	}

	let isbnFormatted = NSMutableString(string:isbn)
	isbnFormatted.insert("-", at: 12)
	isbnFormatted.insert("-", at: 6)
	isbnFormatted.insert("-", at: 4)
	isbnFormatted.insert("-", at: 3)

	//break the isbn into a single digit off to the left, and then two halves well-spaced
	var isbnB = isbn
	let isbn9 = String(isbnB.removeFirst())
	let isbnA = String(" " + isbnB.prefix(6).map({String($0)}).joined(separator:" ") + " ")
	isbnB = String(" " + isbnB.suffix(6).map({String($0)}).joined(separator:" ") + " ")

	//generate barcode
	let isbnData = isbn.data(using: String.Encoding.utf8)
	var isbnImage:CIImage? = nil
	if let barcodeFilter = CIFilter(name: "CICode128BarcodeGenerator") {
		barcodeFilter.setValue(isbnData, forKey: "inputMessage")
		barcodeFilter.setValue(0.0, forKey: "inputQuietSpace")
		isbnImage = barcodeFilter.outputImage?.transformed(by:CGAffineTransform(scaleX: scale, y: 1.6*scale))
	} else {
		print("Unable to get barcode generator")
		exit(0)
	}

	let nsVersion = NSCIImageRep(ciImage: isbnImage!)
	let barcode = NSImage(size:nsVersion.size)
	barcode.addRepresentation(nsVersion)
	let barcodeSize = nsVersion.size

	//add text and border
	//ISBN caption on top
	let caption = NSAttributedString(string:"ISBN: " + String(isbnFormatted), attributes:captionAttributes)
	let captionHeight = caption.size().height

	//ISBN label inset into the bottom of the barcode
	let label9 = NSAttributedString(string:isbn9, attributes:labelAttributes)
	let labelA = NSAttributedString(string:isbnA as String, attributes:labelAttributes)
	let labelB = NSAttributedString(string:isbnB, attributes:labelAttributes)

	//create a label from the entire ISBN so that all three parts use the same height for positioning
	let label = NSAttributedString(string:isbn, attributes:labelAttributes)
	let labelHeight = label.size().height

	//combine text and barcode
	let imageSize = NSSize(
		width: barcodeSize.width+borderWidth*2+paddingX*2,
		height: barcodeSize.height+captionHeight+labelHeight+borderWidth*2+paddingTop
	)
	let bug = NSImage(size:imageSize, flipped:false) { (outputRect) -> Bool in
		//background color
		backgroundColor.setFill()
		outputRect.fill()

		//barcode
		let barcodeRect = NSRect(x:paddingX, y:labelHeight, width:barcodeSize.width, height:barcodeSize.height)
		barcode.draw(in: barcodeRect)

		//caption
		let captionY = outputRect.height-borderWidth-paddingTop/2-captionHeight-captionHeight*textAdjustment
		let captionRect = NSRect(x:0, y:captionY, width:outputRect.width, height:captionHeight)
		caption.draw(in: captionRect)

		//label first character
		let labelY = barcodeRect.minY - labelHeight/2
		var labelX = barcodeRect.minX - label9.size().width*1.2
		var labelRect = NSRect(x:labelX, y:labelY, width:label9.size().width, height:label9.size().height)
		label9.draw(in:labelRect)

		//label left half
		labelX = barcodeRect.minX + barcodeRect.width/4 - labelA.size().width/2
		labelRect = NSRect(x:labelX, y:labelY, width:labelA.size().width, height:labelA.size().height)
		backgroundColor.setFill()
		labelRect.fill()
		labelA.draw(in:labelRect)

		//label right half
		labelX = barcodeRect.minX + barcodeRect.width*3/4 - labelB.size().width/2
		labelRect = NSRect(x:labelX, y:labelY, width:labelB.size().width, height:labelB.size().height)
		backgroundColor.setFill()
		labelRect.fill()
		labelB.draw(in:labelRect)

		//border
		borderColor.setFill()
		outputRect.frame(withWidth:borderWidth)

		return true
	}

	//generate file
	var imageData = Data()

	//write image to file
	var filename = String(isbnFormatted) + ".png"
	if saveAsPDF {
		guard let pdfPage = PDFPage(image: bug) else {
			print("Unable to acquire PDF data.")
			exit(0)
		}
		imageData = pdfPage.dataRepresentation!
		filename = String(isbnFormatted) + ".pdf"
	} else {
		imageData = bug.tiffRepresentation!
		guard let bitmapVersion = NSBitmapImageRep(data: imageData) else {
			print("Unable to acquire bitmap.")
			exit(0)
		}
		//set the resolution to the desired dpi
		bitmapVersion.size = NSSize(width: bitmapVersion.size.width*dpiScale, height: bitmapVersion.size.height*dpiScale)
		let filetype = NSBitmapImageRep.FileType.png
		imageData = bitmapVersion.representation(using: filetype, properties:[:])!
	}

	do {
		try imageData.write(to: NSURL.fileURL(withPath: filename))
	} catch {
		print("Unable to save file: ", filename)
	}
}
