<commit_before>﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

using Microsoft.Win32;
using Palaso.Code;
using Palaso.Reporting;
using Palaso.Extensions;

namespace Palaso.IO
{
	public interface IFileLocator
	{
		string LocateFile(string fileName);
		string LocateFile(string fileName, string descriptionForErrorMessage);
		string LocateOptionalFile(string fileName);
		string LocateFileWithThrow(string fileName);
		string LocateDirectory(string directoryName);
		string LocateDirectoryWithThrow(string directoryName);
		string LocateDirectory(string directoryName, string descriptionForErrorMessage);
		IFileLocator CloneAndCustomize(IEnumerable<string> addedSearchPaths);
	}

	public interface IChangeableFileLocator : IFileLocator
	{
		void AddPath(string path);
		void RemovePath(string path);
	}

	public class FileLocator : IChangeableFileLocator
	{
		protected readonly List<string> _searchPaths;

		public FileLocator(IEnumerable<string> searchPaths  )
		{
			_searchPaths = new List<string>(searchPaths);
		}

		public string LocateFile(string fileName)
		{
			foreach (var path in GetSearchPaths())
			{
				var fullPath = Path.Combine(path, fileName);
				if(File.Exists(fullPath))
					return fullPath;
			}
			return string.Empty;
		}

		/// <summary>
		/// Subclasses (e.g. in Bloom) override this to provide a dynamic set of paths
		/// </summary>
		/// <returns></returns>
		virtual protected IEnumerable<string> GetSearchPaths()
		{
			return _searchPaths;
		}

		public string LocateFile(string fileName, string descriptionForErrorMessage)
		{

			var path = LocateFile(fileName);
			if (string.IsNullOrEmpty(path) || !File.Exists(path))
			{
				ErrorReport.NotifyUserOfProblem(
					"{0} could not find the {1}.  It expected to find it in one of these locations: {2}",
					UsageReporter.AppNameToUseInDialogs, descriptionForErrorMessage, GetSearchPaths().Concat(", ")
					);
			}
			return path;
		}

		public string LocateDirectory(string directoryName)
		{
			foreach (var path in GetSearchPaths())
			{
				var fullPath = Path.Combine(path, directoryName);
				if (Directory.Exists(fullPath))
					return fullPath;
			}
			return string.Empty;
		}
		public string LocateDirectory(string directoryName, string descriptionForErrorMessage)
		{

			var path = LocateDirectory(directoryName);
			if (string.IsNullOrEmpty(path) || !Directory.Exists(path))
			{
				ErrorReport.NotifyUserOfProblem(
					"{0} could not find the {1}.  It expected to find it in one of these locations: {2}",
					UsageReporter.AppNameToUseInDialogs, descriptionForErrorMessage, _searchPaths.Concat(", ")
					);
			}
			return path;
		}
		public string LocateDirectoryWithThrow(string directoryName)
		{
			var path = LocateDirectory(directoryName);
			if (string.IsNullOrEmpty(path) || !Directory.Exists(path))
			{
				throw new ApplicationException(String.Format("Could not find {0}.  It expected to find it in one of these locations: {1}",
					directoryName, _searchPaths.Concat(Environment.NewLine)));
			}
			return path;
		}

		/// <summary>
		///
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns>null if not found</returns>
		public string LocateOptionalFile(string fileName)
		{
			var path = LocateFile(fileName);
			if (string.IsNullOrEmpty(path) || !File.Exists(path))
			{
				return null;
			}
			return path;
		}

		/// <summary>
		/// Throws ApplicationException if not found.
		/// </summary>
		public string LocateFileWithThrow(string fileName)
		{
			var path = LocateFile(fileName);
			if (string.IsNullOrEmpty(path) || !File.Exists(path))
			{
				throw new ApplicationException("Could not find " + fileName + ". It expected to find it in one of these locations: " + Environment.NewLine + _searchPaths.Concat(Environment.NewLine));
			}
			return path;
		}
		/// <summary>
		/// Gives the directory of either the project folder (if running from visual studio), or
		/// the installation folder.  Helpful for finding templates and things; by using this,
		/// you don't have to copy those files into the build directory during development.
		/// It assumes your build directory has "output" as part of its path.
		/// </summary>
		/// <returns></returns>
		public static string DirectoryOfApplicationOrSolution
		{
			get
			{
				string path = DirectoryOfTheApplicationExecutable;
				char sep = Path.DirectorySeparatorChar;
				int i = path.ToLower().LastIndexOf(sep + "output" + sep);

				if (i > -1)
				{
					path = path.Substring(0, i + 1);
				}
				return path;
			}
		}

		public static string DirectoryOfTheApplicationExecutable
		{
			get
			{
				string path;
				bool unitTesting = Assembly.GetEntryAssembly() == null;
				if (unitTesting)
				{
					path = new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath;
					path = Uri.UnescapeDataString(path);
				}
				else
				{
					path = EntryAssembly.Location;
				}
				return Directory.GetParent(path).FullName;
			}
		}


		/// <summary>
		/// Find a file which, on a development machine, lives in [solution]/[distFileFolderName]/[subPath],
		/// and when installed, lives in
		/// [applicationFolder]/[distFileFolderName]/[subPath1]/[subPathN]  or
		/// [applicationFolder]/[subPath]/[subPathN]
		/// </summary>
		/// <example>GetFileDistributedWithApplication("info", "releaseNotes.htm");</example>
		public static string GetFileDistributedWithApplication(bool optional, params string[] partsOfTheSubPath)
		{
			foreach (var directoryHoldingFiles in new []{null, "DistFiles", "common" /*for wesay*/})
			{
				var path = FileLocator.DirectoryOfApplicationOrSolution;
				path = FileLocator.DirectoryOfApplicationOrSolution;
				if(directoryHoldingFiles!=null)
					path = Path.Combine(path, directoryHoldingFiles);

				foreach (var part in partsOfTheSubPath)
				{
					path = System.IO.Path.Combine(path, part);
				}
				if (File.Exists(path))
					return path;
			}

			if (optional)
				return null;
			string subpath="";
			foreach (var part in partsOfTheSubPath)
				{
					subpath = System.IO.Path.Combine(subpath, part);
				}
			throw new ApplicationException("Could not locate the required file, "+ subpath);
		}

		/// <summary>
		/// Find a file which MUST EXIST. On a development machine, lives in [solution]/[distFileFolderName]/[subPath],
		/// and when installed, lives in
		/// [applicationFolder]/[distFileFolderName]/[subPath1]/[subPathN]  or
		/// [applicationFolder]/[subPath]/[subPathN]
		/// </summary>
		/// <example>GetFileDistributedWithApplication("info", "releaseNotes.htm");</example>
		public static string GetFileDistributedWithApplication(params string[] partsOfTheSubPath)
			{
				return GetFileDistributedWithApplication(false, partsOfTheSubPath);
			}


		/// <summary>
		/// Find a file which, on a development machine, lives in [solution]/DistFiles/[subPath],
		/// and when installed, lives in
		/// [applicationFolder]/[subPath1]/[subPathN]
		/// </summary>
		/// <example>GetFileDistributedWithApplication("info", "releaseNotes.htm");</example>
		public static string GetDirectoryDistributedWithApplication(bool optional, params string[] partsOfTheSubPath)
		{
			var path = FileLocator.DirectoryOfApplicationOrSolution;
			foreach (var part in partsOfTheSubPath)
			{
				path = System.IO.Path.Combine(path, part);
			}
			if (Directory.Exists(path))
				return path;

			//try distfiles
			path = FileLocator.DirectoryOfApplicationOrSolution;
			path = Path.Combine(path,"DistFiles");
			foreach (var part in partsOfTheSubPath)
			{
				path = System.IO.Path.Combine(path, part);
			}


			if (optional && !Directory.Exists(path))
				return null;

			RequireThat.Directory(path).Exists();
			return path;
		}
		/// <summary>
		/// Find a directory which MUST EXIST. On a development machine, lives in [solution]/DistFiles/[subPath],
		/// and when installed, lives in
		/// [applicationFolder]/[subPath1]/[subPathN]
		/// </summary>
		/// <example>GetFileDistributedWithApplication("info", "releaseNotes.htm");</example>
		public static string GetDirectoryDistributedWithApplication(params string[] partsOfTheSubPath)
		{
			return GetDirectoryDistributedWithApplication(false, partsOfTheSubPath);
		}

		/// <summary>
		/// Use this when you can't mess with the whole application's filelocator, but you want to add a path or two, e.g., the folder of the current book in Bloom.
		/// </summary>
		/// <param name="addedSearchPaths"></param>
		/// <returns></returns>
		public virtual IFileLocator CloneAndCustomize(IEnumerable<string> addedSearchPaths)<commit_msg>Make FileLocator look in src<commit_after>﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

using Microsoft.Win32;
using Palaso.Code;
using Palaso.Reporting;
using Palaso.Extensions;

namespace Palaso.IO
{
	public interface IFileLocator
	{
		string LocateFile(string fileName);
		string LocateFile(string fileName, string descriptionForErrorMessage);
		string LocateOptionalFile(string fileName);
		string LocateFileWithThrow(string fileName);
		string LocateDirectory(string directoryName);
		string LocateDirectoryWithThrow(string directoryName);
		string LocateDirectory(string directoryName, string descriptionForErrorMessage);
		IFileLocator CloneAndCustomize(IEnumerable<string> addedSearchPaths);
	}

	public interface IChangeableFileLocator : IFileLocator
	{
		void AddPath(string path);
		void RemovePath(string path);
	}

	public class FileLocator : IChangeableFileLocator
	{
		protected readonly List<string> _searchPaths;

		public FileLocator(IEnumerable<string> searchPaths  )
		{
			_searchPaths = new List<string>(searchPaths);
		}

		public string LocateFile(string fileName)
		{
			foreach (var path in GetSearchPaths())
			{
				var fullPath = Path.Combine(path, fileName);
				if(File.Exists(fullPath))
					return fullPath;
			}
			return string.Empty;
		}

		/// <summary>
		/// Subclasses (e.g. in Bloom) override this to provide a dynamic set of paths
		/// </summary>
		/// <returns></returns>
		virtual protected IEnumerable<string> GetSearchPaths()
		{
			return _searchPaths;
		}

		public string LocateFile(string fileName, string descriptionForErrorMessage)
		{

			var path = LocateFile(fileName);
			if (string.IsNullOrEmpty(path) || !File.Exists(path))
			{
				ErrorReport.NotifyUserOfProblem(
					"{0} could not find the {1}.  It expected to find it in one of these locations: {2}",
					UsageReporter.AppNameToUseInDialogs, descriptionForErrorMessage, GetSearchPaths().Concat(", ")
					);
			}
			return path;
		}

		public string LocateDirectory(string directoryName)
		{
			foreach (var path in GetSearchPaths())
			{
				var fullPath = Path.Combine(path, directoryName);
				if (Directory.Exists(fullPath))
					return fullPath;
			}
			return string.Empty;
		}
		public string LocateDirectory(string directoryName, string descriptionForErrorMessage)
		{

			var path = LocateDirectory(directoryName);
			if (string.IsNullOrEmpty(path) || !Directory.Exists(path))
			{
				ErrorReport.NotifyUserOfProblem(
					"{0} could not find the {1}.  It expected to find it in one of these locations: {2}",
					UsageReporter.AppNameToUseInDialogs, descriptionForErrorMessage, _searchPaths.Concat(", ")
					);
			}
			return path;
		}
		public string LocateDirectoryWithThrow(string directoryName)
		{
			var path = LocateDirectory(directoryName);
			if (string.IsNullOrEmpty(path) || !Directory.Exists(path))
			{
				throw new ApplicationException(String.Format("Could not find {0}.  It expected to find it in one of these locations: {1}",
					directoryName, _searchPaths.Concat(Environment.NewLine)));
			}
			return path;
		}

		/// <summary>
		///
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns>null if not found</returns>
		public string LocateOptionalFile(string fileName)
		{
			var path = LocateFile(fileName);
			if (string.IsNullOrEmpty(path) || !File.Exists(path))
			{
				return null;
			}
			return path;
		}

		/// <summary>
		/// Throws ApplicationException if not found.
		/// </summary>
		public string LocateFileWithThrow(string fileName)
		{
			var path = LocateFile(fileName);
			if (string.IsNullOrEmpty(path) || !File.Exists(path))
			{
				throw new ApplicationException("Could not find " + fileName + ". It expected to find it in one of these locations: " + Environment.NewLine + _searchPaths.Concat(Environment.NewLine));
			}
			return path;
		}
		/// <summary>
		/// Gives the directory of either the project folder (if running from visual studio), or
		/// the installation folder.  Helpful for finding templates and things; by using this,
		/// you don't have to copy those files into the build directory during development.
		/// It assumes your build directory has "output" as part of its path.
		/// </summary>
		/// <returns></returns>
		public static string DirectoryOfApplicationOrSolution
		{
			get
			{
				string path = DirectoryOfTheApplicationExecutable;
				char sep = Path.DirectorySeparatorChar;
				int i = path.ToLower().LastIndexOf(sep + "output" + sep);

				if (i > -1)
				{
					path = path.Substring(0, i + 1);
				}
				return path;
			}
		}

		public static string DirectoryOfTheApplicationExecutable
		{
			get
			{
				string path;
				bool unitTesting = Assembly.GetEntryAssembly() == null;
				if (unitTesting)
				{
					path = new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath;
					path = Uri.UnescapeDataString(path);
				}
				else
				{
					path = EntryAssembly.Location;
				}
				return Directory.GetParent(path).FullName;
			}
		}


		/// <summary>
		/// Find a file which, on a development machine, lives in [solution]/[distFileFolderName]/[subPath],
		/// and when installed, lives in
		/// [applicationFolder]/[distFileFolderName]/[subPath1]/[subPathN]  or
		/// [applicationFolder]/[subPath]/[subPathN]
		/// </summary>
		/// <example>GetFileDistributedWithApplication("info", "releaseNotes.htm");</example>
		public static string GetFileDistributedWithApplication(bool optional, params string[] partsOfTheSubPath)
		{
			foreach (var directoryHoldingFiles in new []{null, "DistFiles", "common" /*for wesay*/})
			{
				var path = FileLocator.DirectoryOfApplicationOrSolution;
				path = FileLocator.DirectoryOfApplicationOrSolution;
				if(directoryHoldingFiles!=null)
					path = Path.Combine(path, directoryHoldingFiles);

				foreach (var part in partsOfTheSubPath)
				{
					path = System.IO.Path.Combine(path, part);
				}
				if (File.Exists(path))
					return path;
			}

			if (optional)
				return null;
			string subpath="";
			foreach (var part in partsOfTheSubPath)
				{
					subpath = System.IO.Path.Combine(subpath, part);
				}
			throw new ApplicationException("Could not locate the required file, "+ subpath);
		}

		/// <summary>
		/// Find a file which MUST EXIST. On a development machine, lives in [solution]/[distFileFolderName]/[subPath],
		/// and when installed, lives in
		/// [applicationFolder]/[distFileFolderName]/[subPath1]/[subPathN]  or
		/// [applicationFolder]/[subPath]/[subPathN]
		/// </summary>
		/// <example>GetFileDistributedWithApplication("info", "releaseNotes.htm");</example>
		public static string GetFileDistributedWithApplication(params string[] partsOfTheSubPath)
			{
				return GetFileDistributedWithApplication(false, partsOfTheSubPath);
			}


		/// <summary>
		/// Find a file which, on a development machine, lives in [solution]/DistFiles/[subPath],
		/// and when installed, lives in
		/// [applicationFolder]/[subPath1]/[subPathN]
		/// </summary>
		/// <example>GetFileDistributedWithApplication("info", "releaseNotes.htm");</example>
		public static string GetDirectoryDistributedWithApplication(bool optional, params string[] partsOfTheSubPath)
		{
			var path = FileLocator.DirectoryOfApplicationOrSolution;
			foreach (var part in partsOfTheSubPath)
			{
				path = System.IO.Path.Combine(path, part);
			}
			if (Directory.Exists(path))
				return path;

			//try distfiles
			path = FileLocator.DirectoryOfApplicationOrSolution;
			path = Path.Combine(path,"DistFiles");
			foreach (var part in partsOfTheSubPath)
			{
				path = System.IO.Path.Combine(path, part);
			}
			if (Directory.Exists(path))
				return path;

			//try src (e.g. Bloom keeps its javascript under source directory (and in distfiles only when installed)
			path = FileLocator.DirectoryOfApplicationOrSolution;
			path = Path.Combine(path, "src");
			foreach (var part in partsOfTheSubPath)
			{
				path = System.IO.Path.Combine(path, part);
			}

			if (optional && !Directory.Exists(path))
				return null;

			RequireThat.Directory(path).Exists();
			return path;
		}
		/// <summary>
		/// Find a directory which MUST EXIST. On a development machine, lives in [solution]/DistFiles/[subPath],
		/// and when installed, lives in
		/// [applicationFolder]/[subPath1]/[subPathN]
		/// </summary>
		/// <example>GetFileDistributedWithApplication("info", "releaseNotes.htm");</example>
		public static string GetDirectoryDistributedWithApplication(params string[] partsOfTheSubPath)
		{
			return GetDirectoryDistributedWithApplication(false, partsOfTheSubPath);
		}

		/// <summary>
		/// Use this when you can't mess with the whole application's filelocator, but you want to add a path or two, e.g., the folder of the current book in Bloom.
		/// </summary>
		/// <param name="addedSearchPaths"></param>
		/// <returns></returns>
		public virtual IFileLocator CloneAndCustomize(IEnumerable<string> addedSearchPaths)
{'fraction_non_alphanumeric': 0.11585782638414217, 'fraction_numerical': 0.0019366598313966736, 'mean_word_length': 3.760574837310195, 'pattern_counts': {'":': 0, '<': 91, '<?xml version=': 0, '>': 93, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7285492', 'n_tokens_mistral': 6105, 'n_tokens_neox': 5165, 'n_words': 1340}