package com.illumon.iris.importers;

import com.fishlib.base.verify.Require;
import com.fishlib.io.logger.Logger;
import com.illumon.iris.binarystore.TableWriter;
import com.illumon.iris.db.tables.TableDefinition;
import com.illumon.iris.db.tables.dataimport.logtailer.TableListenerFactory;
import com.illumon.iris.db.tables.dataimport.logtailer.TableListenerFactoryImpl;
import com.illumon.iris.utils.ImportException;
import org.jetbrains.annotations.NotNull;

/**
 * Abstract base class for importers. Handles most of the boilerplate.
 * Implementing classes will need to implement {@link #processData()},
 * and will most likely want to extend {@link StandardImporterArguments}.
 * {@link #processData()} will acquire data from a source and feed it to the
 * {@link TableWriter} via {@link TableWriter#getSetter(String) TableWriter.getSetter}.
 * {@link com.illumon.iris.binarystore.RowSetter#set(Object) set()}
 * and {@link TableWriter#writeRow()}.
 *
 * @param <IAT> The actual class that extends StandardImporterArguments
 */
public abstract class BaseImporter<IAT extends StandardImporterArguments> {
    protected final Logger log;
    private TableWriter tableWriter = null;
    private TableDefinition tableDefinition = null;
    private TableListenerFactory tableListenerFactory = null;
    private final IAT arguments;

    /**
     * Construct the BaseImporter.
     *
     * @param log use this Logger for feedback
     * @param arguments importer arguments defining the import run
     */
    BaseImporter(@NotNull final Logger log, @NotNull final IAT arguments) {
        this.log = Require.neqNull(log, "log");
        this.arguments = Require.neqNull(arguments, "arguments");
    }

    /**
     * Get the importer arguments, in the actual parameterized type.
     *
     * @return the parsed importer arguments
     */
    @SuppressWarnings("WeakerAccess")
    protected IAT getImporterArguments() {
        return arguments;
    }

    /**
     * Accessor for TableWriter.
     *
     * @return the TableWriter
     */
    protected TableWriter getTableWriter() {
        return tableWriter;
    }

    /**
     * Accessor for table definition
     * @return the TableDefinition
     */
    protected TableDefinition getTableDefinition() { return tableDefinition; }

    /**
     * Accessor for TableListenerFactory.
     * @return the TableListenerFactory
     */
    @SuppressWarnings("WeakerAccess")
    protected TableListenerFactory getTableListenerFactory() {
        return tableListenerFactory;
    }

    /**
     * Set up the import by validating arguments, creating the tableWriter, etc.
     * Implementors should not need to override this.
     */
    @SuppressWarnings("WeakerAccess")
    void setUpImport() {
        log.info().append("Beginning import for ").append(arguments).endl();

        final ImportTableWriterFactory tableWriterFactory = arguments.getImportTableWriterFactory();

        tableDefinition = tableWriterFactory.getTableDefinition();

        // we do not support multi-partition import here yet
        if( arguments.getPartitioningColumnName() != null) {
            throw new IllegalArgumentException("Multi-partition import is not supported in this importer");
        }
        tableWriter = tableWriterFactory.getTableWriter(null);

        tableListenerFactory = new TableListenerFactoryImpl(log, arguments.getNamespace(), arguments.getTableName(), tableWriter);
    }

    /**
     * Import the data - e.g. read the rows from the data source.
     * The Overriding class does the actual import using arguments, tableWriter, and tableListenerFactory
     */
    protected abstract void processData();

    /**
     * Wrap up the import process. Close the tableWriter and any other finalization tasks.
     * Implementors should not need to override this.
     */
    @SuppressWarnings("WeakerAccess")
    protected void finishImport() {
        log.info().append("Closing table writer").endl();
        try {
            if (tableWriter != null) {
                tableWriter.close();
            }
        } catch (Exception e) {
            throw new ImportException("Failed to close writer", e);
        }

        log.info().append("Done importing").endl();
    }

    /**
     * Execute the steps for an import to a specific location.
     * Implementors should not need to override this.
     */
    @SuppressWarnings("WeakerAccess")
    protected void doImport() {
        setUpImport();
        processData();
        finishImport();
    }
}
