/*
 * Marks2FossilMirrorState
 *
 * Parses Git and Fossil marks to create a .mirror_state/db file for Fossil.
 * This allows incrementally exporting to an existing Git repository instead of
 * a new one.
 *
 * To use, install ObjFW <https://objfw.nil.im>, then compile using:
 *
 *   $ objfw-compile -o Marks2FossilMirrorState Marks2FossilMirrorState.m
 *
 * You need to use Clang as your compiler (this file uses modern Objective-C
 * features).
 *
 * Usage:
 *
 *   - Copy fossil.marks and git.marks into the destination Git repository.
 *   - Execute ./Marks2FossilMirrorState in the destination Git repository.
 *   - You can now use `fossil git export /path/to/existing/git/repo` from the
 *     Fossil repo.
 *
 * Important: The Fossil repo must have been created from the existing Git repo
 *            for this to work, and the marks files need to be those that were
 *            created in the process of the Git -> Fossil conversion.
 *
 * Copyright (c) 2020, Jonathan Schleifer <js@nil.im>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice is present in all copies.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#import <ObjFW/ObjFW.h>

@interface Marks2FossilMirrorState: OFObject <OFApplicationDelegate>
@end

OF_APPLICATION_DELEGATE(Marks2FossilMirrorState)

@implementation Marks2FossilMirrorState
- (void)applicationDidFinishLaunching: (OFNotification *)notification
{
	if (![OFFileManager.defaultManager
	    directoryExistsAtPath: @".mirror_state"])
		[OFFileManager.defaultManager
		    createDirectoryAtPath: @".mirror_state"];

	OFSubprocess *SQLite = [OFSubprocess
	    subprocessWithProgram: @"sqlite3"
			arguments: @[ @".mirror_state/db" ]];
	[SQLite writeString: @"BEGIN TRANSACTION;\n"
	                     @"DROP TABLE IF EXISTS mmark;\n"
	                     @"CREATE TABLE mmark(\n"
	                     @"    id INTEGER PRIMARY KEY,\n"
	                     @"    uuid TEXT,\n"
	                     @"    isfile BOOLEAN,\n"
	                     @"    githash TEXT,\n"
	                     @"    UNIQUE (uuid, isfile)\n"
	                     @");\n"];

	OFFile *fossilMarksFile = [OFFile fileWithPath: @"fossil.marks"
	                                          mode: @"r"];
	OFFile *gitMarksFile = [OFFile fileWithPath: @"git.marks" mode: @"r"];
	OFMutableDictionary<OFString *, OFString *> *fossilMarks, *gitMarks;
	fossilMarks = [OFMutableDictionary dictionary];
	gitMarks = [OFMutableDictionary dictionary];

	OFString *line;
	while ((line = [fossilMarksFile readLine]) != nil) {
		OFArray<OFString *> *components =
		    [line componentsSeparatedByString: @" "];
		if (components.count != 3) {
			OFLog(@"ERROR: Invalid line: %@", line);
			[OFApplication terminateWithStatus: 1];
		}

		if (![components[0] hasPrefix: @"c"])
			continue;

		if (![components[1] hasPrefix: @":"]) {
			OFLog(@"ERROR: Missing : prefix in line: %@", line);
			[OFApplication terminateWithStatus: 1];
		}

		OFString *mark = [components[1] substringWithRange:
		    OFMakeRange(1, components[1].length - 1)];

		fossilMarks[mark] = components[2];
	}

	while ((line = [gitMarksFile readLine]) != nil) {
		OFArray<OFString *> *components =
		    [line componentsSeparatedByString: @" "];
		if (components.count != 2) {
			OFLog(@"ERROR: Invalid line: %@", line);
			[OFApplication terminateWithStatus: 1];
		}

		if (![components[0] hasPrefix: @":"]) {
			OFLog(@"ERROR: Missing : prefix in line: %@", line);
			[OFApplication terminateWithStatus: 1];
		}

		OFString *mark = [components[0] substringWithRange:
		    OFMakeRange(1, components[0].length - 1)];

		gitMarks[mark] = components[1];
	}

	if (![fossilMarks.allKeys.sortedArray
	    isEqual: gitMarks.allKeys.sortedArray]) {
		OFLog(@"ERROR: Marks don't match!");
		[OFApplication terminateWithStatus: 1];
	}

	for (OFString *mark in fossilMarks)
		[SQLite writeFormat:
		    @"INSERT INTO mmark VALUES ('%@', '%@', 0, '%@');\n",
		    mark, fossilMarks[mark], gitMarks[mark]];

	[SQLite writeString: @"COMMIT;\n"];
	[SQLite closeForWriting];

	[OFApplication terminateWithStatus: [SQLite waitForTermination]];
}
@end
