/*
 * Copyright 2011-2012 Brian Matthews
 *
 * Licensed 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 com.btmatthews.maven.plugins.inmemdb.ldr;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Locale;

import com.btmatthews.maven.plugins.inmemdb.Loader;
import com.btmatthews.maven.plugins.inmemdb.Source;
import com.btmatthews.utils.monitor.Logger;

/**
 * Abstract base class for loaders that implements the {@link
 * Loader#isSupported(com.btmatthews.utils.monitor.Logger, com.btmatthews.maven.plugins.inmemdb.Source)} method.
 *
 * @author <a href="mailto:brian@btmathews.com">Brian Matthews</a>
 * @version 1.0.0
 */
public abstract class AbstractLoader implements Loader {

    /**
     * The prefix used to denote that a resource should be loaded from the classpath
     * rather than the file system.
     */
    protected static final String CLASSPATH_PREFIX = "classpath:";

    /**
     * The length of the classpath: prefix.
     */
    protected static final int CLASSPATH_PREFIX_LENGTH = 10;

    /**
     * The message key for the error reported when an error occurs validating a
     * file.
     */
    protected static final String CANNOT_VALIDATE_FILE = "cannot_validate_file";

    /**
     * Get the extension of the files that the loader will support.
     *
     * @return The file extension.
     */
    protected abstract String getExtension();

    /**
     * Check the contents of the file to see if it is valid for this loader.
     *
     * @param logger Used to report errors and raise exceptions.
     * @param source The source data or script.
     * @return <ul>
     *         <li><code>true</code> if the content is valid.</li>
     *         <li><code>false</code> if the content is invalid.</li>
     *         </ul>
     */
    protected boolean hasValidContent(final Logger logger, final Source source) {
        return true;
    }

    /**
     * Determine whether or not the data or script can be loaded or executed.
     *
     * @param logger Used to report errors and raise exceptions.
     * @param source The source file containing the data or script.
     * @return <ul>
     *         <li><code>true</code> if the data or script can be loaded or
     *         executed.</li>
     *         <li><code>false</code>if the data or script cannot be loaded or
     *         executed.</li>
     *         </ul>
     */
    public final boolean isSupported(final Logger logger, final Source source) {
        boolean result = false;
        if (source != null) {
            final Locale locale = Locale.getDefault();
            final String name = source.getSourceFile().toLowerCase(locale);
            if (name.endsWith(getExtension())) {
                if (source.getSourceFile().startsWith(CLASSPATH_PREFIX)) {
                    result = hasValidContent(logger, source);
                } else {
                    final File sourceFile = new File(source.getSourceFile());
                    result = sourceFile.isFile() && hasValidContent(logger, source);
                }
            }
        }
        return result;
    }

    /**
     * Determine if data set or script source is a file system or class path resource. If the name is prefixed with
     * classpath: then it is a class path resource.
     *
     * @param source The data set or script.
     * @return {@code true} if the source filename is prefixed with classpath:. Otherwise, {@code false}.
     */
    protected boolean isClasspath(final Source source) {
        return source.getSourceFile().startsWith("classpath:");
    }

    /**
     * Get an input stream for the data set or script source.
     *
     * @param source The data set or script.
     * @return An {@link InputStream} or {@code null} if the source cannot be found.
     * @throws IOException If there was an error creating the input stream.
     */
    protected final InputStream getInputStream(final Source source) throws IOException {
        if (isClasspath(source)) {
            final String resource = source.getSourceFile().substring(CLASSPATH_PREFIX_LENGTH);
            final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            return classLoader.getResourceAsStream(resource);
        } else {
            final File file = new File(source.getSourceFile());
            if (file.exists()) {
                return new FileInputStream(file);
            }
        }
        return null;
    }

    /**
     * Get a reader  for the data set or script source.
     *
     * @param source The data set or script.
     * @return A {@link Reader} or {@code null} if the source cannot be found.
     * @throws IOException If there was an error creating the reader.
     */
    protected final Reader getReader(final Source source) throws IOException {
        final InputStream inputStream = getInputStream(source);
        if (inputStream == null) {
            return null;
        } else {
            return new InputStreamReader(inputStream, System.getProperty("file.encoding"));
        }
    }

    public String getTableName(final Source source) {
        final String path = source.getSourceFile();
        final int startIndex;
        final int endIndex = path.length() - getExtension().length();
        if (isClasspath(source)) {
            final int dotPos = path.lastIndexOf(".");
            final int slashPos = path.lastIndexOf("/", CLASSPATH_PREFIX_LENGTH);
            if (slashPos == -1) {
                startIndex = CLASSPATH_PREFIX_LENGTH;
            } else {
                startIndex = slashPos + 1;
            }
        } else {
            final int slashPos = path.lastIndexOf("/");
            if (slashPos == -1) {
                startIndex = 0;
            } else {
                startIndex = slashPos + 1;
            }
        }
        return path.substring(startIndex, endIndex);
    }
}