/**
 *   This file is part of Skript.
 *
 *  Skript is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Skript is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Skript.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * Copyright 2011-2017 Peter Güttinger and contributors
 */
package ch.njol.skript.log;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedList;
import java.util.logging.Level;

import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.Skript;

/**
 * @author Peter Güttinger
 */
public class RetainingLogHandler extends LogHandler {
	
	private final Deque<LogEntry> log = new LinkedList<LogEntry>();
	private int numErrors = 0;
	
	boolean printedErrorOrLog = false;
	
	@Override
	public LogResult log(final LogEntry entry) {
		log.add(entry);
		if (entry.getLevel().intValue() >= Level.SEVERE.intValue())
			numErrors++;
		printedErrorOrLog = false;
		return LogResult.CACHED;
	}
	
	@Override
	public void onStop() {
		if (!printedErrorOrLog && Skript.testing())
			SkriptLogger.LOGGER.warning("Retaining log wasn't instructed to print anything at " + SkriptLogger.getCaller());
	}
	
	public final boolean printErrors() {
		return printErrors(null);
	}
	
	/**
	 * Prints all retained errors or the given one if no errors were retained.
	 * <p>
	 * This handler is stopped if not already done.
	 * 
	 * @param def Error to print if no errors were logged, can be null to not print any error if there are none
	 * @return Whether there were any errors
	 */
	public final boolean printErrors(final @Nullable String def) {
		return printErrors(def, ErrorQuality.SEMANTIC_ERROR);
	}
	
	public final boolean printErrors(final @Nullable String def, final ErrorQuality quality) {
		assert !printedErrorOrLog;
		printedErrorOrLog = true;
		stop();
		
		boolean hasError = false;
		for (final LogEntry e : log) {
			if (e.getLevel().intValue() >= Level.SEVERE.intValue()) {
				SkriptLogger.log(e);
				hasError = true;
			} else {
				e.discarded("not printed");
			}
		}
		
		if (!hasError && def != null)
			SkriptLogger.log(SkriptLogger.SEVERE, def);
		
		return hasError;
	}
	
	/**
	 * Sends all retained error messages to the given recipient.
	 * <p>
	 * This handler is stopped if not already done.
	 * 
	 * @param recipient
	 * @param def Error to send if no errors were logged, can be null to not print any error if there are none
	 * @return Whether there were any errors to send
	 */
	public final boolean printErrors(final CommandSender recipient, final @Nullable String def) {
		assert !printedErrorOrLog;
		printedErrorOrLog = true;
		stop();
		
		final boolean console = recipient == Bukkit.getConsoleSender(); // log as SEVERE instead of INFO
		
		boolean hasError = false;
		for (final LogEntry e : log) {
			if (e.getLevel().intValue() >= Level.SEVERE.intValue()) {
				if (console)
					SkriptLogger.LOGGER.severe(e.getMessage());
				else
					recipient.sendMessage(e.getMessage());
				e.logged();
				hasError = true;
			} else {
				e.discarded("not printed");
			}
		}
		
		if (!hasError && def != null) {
			if (console)
				SkriptLogger.LOGGER.severe(def);
			else
				recipient.sendMessage(def);
		}
		return hasError;
	}
	
	/**
	 * Prints all retained log messages.
	 * <p>
	 * This handler is stopped if not already done.
	 */
	public final void printLog() {
		assert !printedErrorOrLog;
		printedErrorOrLog = true;
		stop();
		SkriptLogger.logAll(log);
	}
	
	public boolean hasErrors() {
		return numErrors != 0;
	}
	
	@Nullable
	public LogEntry getFirstError() {
		for (final LogEntry e : log) {
			if (e.getLevel().intValue() >= Level.SEVERE.intValue())
				return e;
		}
		return null;
	}
	
	public LogEntry getFirstError(final String def) {
		for (final LogEntry e : log) {
			if (e.getLevel().intValue() >= Level.SEVERE.intValue())
				return e;
		}
		return new LogEntry(SkriptLogger.SEVERE, def);
	}
	
	/**
	 * Clears the list of retained log messages.
	 */
	public void clear() {
		for (final LogEntry e : log)
			e.discarded("cleared");
		log.clear();
		numErrors = 0;
	}
	
	public int size() {
		return log.size();
	}
	
	@SuppressWarnings("null")
	public Collection<LogEntry> getLog() {
		return Collections.unmodifiableCollection(log);
	}
	
	public Collection<LogEntry> getErrors() {
		final Collection<LogEntry> r = new ArrayList<LogEntry>();
		for (final LogEntry e : log) {
			if (e.getLevel().intValue() >= Level.SEVERE.intValue())
				r.add(e);
		}
		return r;
	}
	
	public int getNumErrors() {
		return numErrors;
	}
	
}
