// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
// * with the License.  You may obtain a copy of the License at                                                              *
// *                                                                                                                         *
// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
// *                                                                                                                         *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
// * specific language governing permissions and limitations under the License.                                              *
// ***************************************************************************************************************************
package org.apache.juneau.cp;

import static org.apache.juneau.common.internal.ArgUtils.*;
import static org.apache.juneau.internal.CollectionUtils.*;

import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.regex.*;

import org.apache.juneau.*;
import org.apache.juneau.internal.*;

/**
 * Utility class for finding regular or localized files on the classpath and file system.
 *
 * <h5 class='section'>Example:</h5>
 * <p class='bjava'>
 * 	<jc>// Constructor a file source that looks for files in the "files" working directory, then in the
 * 	// package "foo.bar", then in the package "foo.bar.files", then in the package "files".</jc>
 * 	FileFinder <jv>finder</jv> = FileFinder
 * 		.<jsm>create</jsm>()
 * 		.dir(<js>"files"</js>)
 * 		.cp(foo.bar.MyClass.<jk>class</jk>,<jk>null</jk>,<jk>true</jk>)
 * 		.cp(foo.bar.MyClass.<jk>class</jk>,<js>"files"</js>,<jk>true</jk>)
 * 		.cp(foo.bar.MyClass.<jk>class</jk>,<js>"/files"</js>,<jk>true</jk>)
 * 		.cache(1_000_000l)  <jc>// Cache files less than 1MB in size.</jc>
 * 		.ignore(Pattern.<jsm>compile</jsm>(<js>"(?i)(.*\\.(class|properties))|(package.html)"</js>)) <jc>// Ignore certain files.</jc>
 * 		.build();
 *
 * 	<jc>// Find a normal file.</jc>
 * 	InputStream <jv>is1</jv> = <jv>finder</jv>.getStream(<js>"text.txt"</js>);
 *
 * 	<jc>// Find a localized file called "text_ja_JP.txt".</jc>
 * 	InputStream <jv>is2</jv> = <jv>finder</jv>.getStream(<js>"text.txt"</js>, Locale.<jsf>JAPAN</jsf>);
 * </p>
 *
 * <p>
 * If the <c>locale</c> is specified, then we look for resources whose name matches that locale.
 * For example, if looking for the resource <js>"MyResource.txt"</js> for the Japanese locale, we will look for
 * files in the following order:
 * <ol>
 * 	<li><js>"MyResource_ja_JP.txt"</js>
 * 	<li><js>"MyResource_ja.txt"</js>
 * 	<li><js>"MyResource.txt"</js>
 * </ol>
 *
 * <p>
 * The default implementation of this interface is {@link BasicFileFinder}.
 * The {@link Builder#type(Class)} method is provided for instantiating other instances.
 *
 * <h5 class='section'>Example:</h5>
 * <p class='bjava'>
 * 	<jk>public class</jk> MyFileFinder <jk>extends</jk> BasicFileFinder {
 * 		<jk>public</jk> MyFileFinder(FileFinder.Builder <jv>builder</jv>) {
 * 			<jk>super</jk>(<jv>builder</jv>);
 * 		}
 * 	}
 *
 * 	<jc>// Instantiate subclass.</jc>
 * 	FileFinder <jv>myFinder</jv> = FileFinder.<jsm>create</jsm>().type(MyFileFinder.<jk>class</jk>).build();
 * </p>
 *
 * <p>
 * Subclasses must provide a public constructor that takes in any of the following arguments:
 * <ul>
 * 	<li>{@link Builder} - The builder object.
 * 	<li>Any beans present in the bean store passed into the constructor.
 * 	<li>Any {@link Optional} beans optionally present in bean store passed into the constructor.
 * </ul>
 *
 * <h5 class='section'>See Also:</h5><ul>
 * </ul>
 */
public interface FileFinder {

	//-----------------------------------------------------------------------------------------------------------------
	// Static
	//-----------------------------------------------------------------------------------------------------------------

	/** Represents no file finder */
	public abstract class Void implements FileFinder {}

	/**
	 * Static creator.
	 *
	 * @param beanStore The bean store to use for creating beans.
	 * @return A new builder for this object.
	 */
	static Builder create(BeanStore beanStore) {
		return new Builder(beanStore);
	}

	/**
	 * Static creator.
	 *
	 * @return A new builder for this object.
	 */
	static Builder create() {
		return new Builder(BeanStore.INSTANCE);
	}

	//-----------------------------------------------------------------------------------------------------------------
	// Builder
	//-----------------------------------------------------------------------------------------------------------------

	/**
	 * Builder class.
	 */
	@FluentSetters
	public static class Builder extends BeanBuilder<FileFinder> {

		final Set<LocalDir> roots;
		long cachingLimit;
		Pattern[] include, exclude;

		/**
		 * Constructor.
		 *
		 * @param beanStore The bean store to use for creating beans.
		 */
		protected Builder(BeanStore beanStore) {
			super(BasicFileFinder.class, beanStore);
			roots = set();
			cachingLimit = -1;
			include = new Pattern[]{Pattern.compile(".*")};
			exclude = new Pattern[0];
		}

		@Override /* BeanBuilder */
		protected FileFinder buildDefault() {
			return new BasicFileFinder(this);
		}

		//-------------------------------------------------------------------------------------------------------------
		// Properties
		//-------------------------------------------------------------------------------------------------------------

		/**
		 * Adds a class subpackage to the lookup paths.
		 *
		 * @param c The class whose package will be added to the lookup paths.  Must not be <jk>null</jk>.
		 * @param path The absolute or relative subpath.
		 * @param recursive If <jk>true</jk>, also recursively adds all the paths of the parent classes as well.
		 * @return This object.
		 */
		@FluentSetter
		public Builder cp(Class<?> c, String path, boolean recursive) {
			assertArgNotNull("c", c);
			while (c != null) {
				roots.add(new LocalDir(c, path));
				c = recursive ? c.getSuperclass() : null;
			}
			return this;
		}

		/**
		 * Adds a file system directory to the lookup paths.
		 *
		 * @param path The path relative to the working directory.  Must not be <jk>null</jk>
		 * @return This object.
		 */
		@FluentSetter
		public Builder dir(String path) {
			assertArgNotNull("path", path);
			return path(Paths.get(".").resolve(path));
		}

		/**
		 * Adds a file system directory to the lookup paths.
		 *
		 * @param path The directory path.
		 * @return This object.
		 */
		@FluentSetter
		public Builder path(Path path) {
			roots.add(new LocalDir(path));
			return this;
		}

		/**
		 * Enables in-memory caching of files for quicker retrieval.
		 *
		 * @param cachingLimit The maximum file size in bytes.
		 * @return This object.
		 */
		@FluentSetter
		public Builder caching(long cachingLimit) {
			this.cachingLimit = cachingLimit;
			return this;
		}

		/**
		 * Specifies the regular expression file name patterns to use to include files being retrieved from the file source.
		 *
		 * @param patterns
		 * 	The regular expression include patterns.
		 * 	<br>The default is <js>".*"</js>.
		 * @return This object.
		 */
		@FluentSetter
		public Builder include(String...patterns) {
			this.include = alist(patterns).stream().map(Pattern::compile).toArray(Pattern[]::new);
			return this;
		}

		/**
		 * Specifies the regular expression file name pattern to use to exclude files from being retrieved from the file source.
		 *
		 * @param patterns
		 * 	The regular expression exclude patterns.
		 * 	<br>If none are specified, no files will be excluded.
		 * @return This object.
		 */
		@FluentSetter
		public Builder exclude(String...patterns) {
			this.exclude = alist(patterns).stream().map(Pattern::compile).toArray(Pattern[]::new);
			return this;
		}

		// <FluentSetters>

		@Override /* GENERATED - org.apache.juneau.BeanBuilder */
		public Builder impl(Object value) {
			super.impl(value);
			return this;
		}

		@Override /* GENERATED - org.apache.juneau.BeanBuilder */
		public Builder type(Class<?> value) {
			super.type(value);
			return this;
		}

		// </FluentSetters>
	}

	//-----------------------------------------------------------------------------------------------------------------
	// Instance
	//-----------------------------------------------------------------------------------------------------------------

	/**
	 * Returns the contents of the resource with the specified name.
	 *
	 * @param name The resource name.
	 * 	See {@link Class#getResource(String)} for format.
	 * @param locale
	 * 	The locale of the resource to retrieve.
	 * 	<br>If <jk>null</jk>, won't look for localized file names.
	 * @return The resolved resource contents, or <jk>null</jk> if the resource was not found.
	 * @throws IOException Thrown by underlying stream.
	 */
	Optional<InputStream> getStream(String name, Locale locale) throws IOException;

	/**
	 * Returns the file with the specified name as a string.
	 *
	 * @param name The file name.
	 * @param locale
	 * 	The locale of the resource to retrieve.
	 * 	<br>If <jk>null</jk>, won't look for localized file names.
	 * @return The contents of the file as a string.  Assumes UTF-8 encoding.
	 * @throws IOException If file could not be read.
	 */
	Optional<String> getString(String name, Locale locale) throws IOException;
}
