 /// The file URL to the directory in which cloned dependencies will be stored.
 public let CarthageDependencyRepositoriesURL = NSURL.fileURLWithPath("~/.carthage/dependencies".stringByExpandingTildeInPath, isDirectory:true)!
 
+/// The relative path to a project's Cartfile.
+public let CarthageProjectCartfilePath = "Cartfile"
+
 /// Represents a project that is using Carthage.
 public struct Project {
 	/// File URL to the root directory of the project.

	public static func loadFromDirectory(directoryURL: NSURL) -> Result<Project> {
		precondition(directoryURL.fileURL)

		let cartfileURL = directoryURL.URLByAppendingPathComponent("Cartfile", isDirectory: false)

		var error: NSError?
		let cartfileContents = NSString(contentsOfURL: cartfileURL, encoding: NSUTF8StringEncoding, error: &error)

			return failure(error ?? CarthageError.NoCartfile.error)
		}
	}
}

/// Returns a signal that completes when cloning completes successfully.
private func cloneProject(project: ProjectIdentifier, destinationURL: NSURL) -> ColdSignal<String> {
	switch project {
	case let .GitHub(repository):
		return cloneRepository(repository.cloneURLString, destinationURL)
	}
}

/// Returns the file URL at which the given project's repository will be
/// located.
private func repositoryFileURLForProject(project: ProjectIdentifier) -> NSURL {
	return CarthageDependencyRepositoriesURL.URLByAppendingPathComponent(project.name)
}

/// Sends all versions available for the given project.
///
/// This will automatically clone or fetch the project's repository as
/// necessary.
private func versionsForProject(project: ProjectIdentifier) -> ColdSignal<SemanticVersion> {
	let repositoryURL = repositoryFileURLForProject(project)

	var updateSignal: ColdSignal<()>!
	if NSFileManager.defaultManager().fileExistsAtPath(repositoryURL.path!) {
		updateSignal = fetchRepository(repositoryURL)
			.then(.empty())
			.on(subscribed: {
				println("*** Fetching \(project.name)")
			}, terminated: {
				println()
			})
	} else {
		updateSignal = cloneProject(project, repositoryURL)
			.then(.empty())
			.on(subscribed: {
				println("*** Cloning \(project.name)")
			}, terminated: {
				println()
			})
	}

	let tagsSignal = launchGitTask([ "tag", "--sort=-version:refname" ], repositoryFileURL: repositoryURL)
	return updateSignal
		.then(tagsSignal)
		.map { (allTags: String) -> ColdSignal<String> in
			return ColdSignal { subscriber in
				(allTags as NSString).enumerateLinesUsingBlock { (line, stop) in
					if subscriber.disposable.disposed {
						stop.memory = true
					}

					subscriber.put(.Next(Box(line as String)))
				}
			}
		}
		.concat(identity)
		.map { PinnedVersion(tag: $0) }
		.map { version -> ColdSignal<SemanticVersion> in
			return ColdSignal.fromResult(SemanticVersion.fromPinnedVersion(version))
				.catch { _ in .empty() }
		}
		.merge(identity)
}

/// Loads the Cartfile for the given dependency, at the given version.
private func cartfileForDependency(dependency: Dependency<SemanticVersion>) -> ColdSignal<Cartfile> {
	precondition(dependency.version.pinnedVersion != nil)

	let pinnedVersion = dependency.version.pinnedVersion!
	let showObject = "\(pinnedVersion.tag):\(CarthageProjectCartfilePath)"

	let repositoryURL = repositoryFileURLForProject(dependency.project)
	return launchGitTask([ "show", showObject ], repositoryFileURL: repositoryURL)
		.tryMap { Cartfile.fromString($0) }
}

/// Attempts to determine the latest satisfiable version of the given project's
/// Carthage dependencies.
///
/// This will fetch dependency repositories as necessary, but will not check
/// them out into the project's working directory.
public func updatedDependenciesForProject(project: Project) -> ColdSignal<CartfileLock> {
	let resolver = Resolver(versionsForDependency: versionsForProject, cartfileForDependency: cartfileForDependency)
	return resolver.resolveDependenciesInCartfile(project.cartfile)
		.map { dependency -> Dependency<PinnedVersion> in
			return dependency.map { $0.pinnedVersion! }
		}
		.reduce(initial: []) { $0 + [ $1 ] }
		.map { CartfileLock(dependencies: $0) }
}

/// Checks out the dependencies listed in the project's Cartfile.
