package net.filebot.ui.rename;

import static net.filebot.Logging.*;
import static net.filebot.media.MediaDetection.*;
import static net.filebot.util.FileUtilities.*;

import java.io.File;
import java.text.Format;
import java.util.Map;
import java.util.logging.Level;

import javax.script.ScriptException;

import net.filebot.ApplicationFolder;
import net.filebot.format.ExpressionFileFormat;
import net.filebot.format.MediaBindingBean;
import net.filebot.similarity.Match;

class ExpressionFormatter implements MatchFormatter {

	private String expression;
	private ExpressionFileFormat format;

	private Format preview;
	private Class<?> target;

	public ExpressionFormatter(String expression, Format preview, Class<?> target) {
		if (expression == null || expression.isEmpty()) {
			throw new IllegalArgumentException("Expression must not be null or empty");
		}

		this.expression = expression;
		this.preview = preview;
		this.target = target;
	}

	public ExpressionFormatter(ExpressionFileFormat format, Format preview, Class<?> target) {
		this(format.getExpression(), preview, target);

		// use compiled format expression right away
		this.format = format;
	}

	public synchronized ExpressionFileFormat getFormat() throws ScriptException {
		if (format == null) {
			format = new ExpressionFileFormat(expression);
		}
		return format;
	}

	public Class<?> getTargetClass() {
		return target;
	}

	@Override
	public boolean canFormat(Match<?, ?> match) {
		// target object is required, file is optional
		return target.isInstance(match.getValue()) && (match.getCandidate() == null || match.getCandidate() instanceof File);
	}

	@Override
	public String preview(Match<?, ?> match) {
		return preview != null ? preview.format(match.getValue()) : match.getValue().toString();
	}

	@Override
	public String format(Match<?, ?> match, boolean extension, Map<?, ?> context) throws ScriptException {
		// evaluate the expression using the given bindings
		Object bindingBean = new MediaBindingBean(match.getValue(), (File) match.getCandidate(), (Map) context);

		// lazy initialize script engine
		String destination = getFormat().format(bindingBean);

		return getPath((File) match.getCandidate(), destination);
	}

	private String getPath(File source, String destination) {
		if (source == null) {
			return destination;
		}

		// resolve against parent folder
		File parent = new File(destination).getParentFile();
		if (parent == null || parent.isAbsolute() || destination.startsWith(".")) {
			return destination;
		}

		// resolve against home folder
		if (destination.startsWith("~")) {
			return ApplicationFolder.UserHome.resolve(destination.substring(1)).getAbsolutePath();
		}

		// try to resolve against structure root folder by default
		try {
			File structureRoot = getStructureRoot(source);
			if (structureRoot != null) {
				for (File f : listPath(parent)) {
					if (isVolumeRoot(structureRoot)) {
						break;
					}
					if (isStructureRoot(f)) {
						structureRoot = structureRoot.getParentFile();
					}
				}
				return new File(structureRoot, destination).getAbsolutePath();
			}
		} catch (Exception e) {
			debug.log(Level.SEVERE, "Failed to resolve structure root: " + source, e);
		}

		// resolve against parent folder by default
		return destination;
	}

}
