package de.renew.draw.storables.api.services;

import java.awt.Rectangle;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.List;
import java.util.function.Predicate;

import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.storables.ontology.DrawingChangeEvent;
import de.renew.draw.storables.ontology.Figure;
import de.renew.draw.storables.ontology.FigureChangeEvent;
import de.renew.draw.storables.ontology.FigureEnumeration;
import de.renew.draw.storables.ontology.Locator;
import de.renew.draw.storables.ontology.Storable;
import de.renew.draw.storables.ontology.StorableInput;
import de.renew.draw.storables.ontology.StorableOutput;
import de.renew.ioontology.ExtensionFileFilter;
import de.renew.ioontology.MultiExtensionFileFilter;
import de.renew.ioontology.exporting.ExportFormat;
import de.renew.ioontology.importing.ImportFormat;

/**
 * Service class that provides methods for the {@link de.renew.draw.storables.api.StorableApi} to call.
 *
 * @author Clara von Bargen
 */
public interface StorableService {

    /**
     * Creates a StorableInput with the given input stream.
     * UTF character encoding is used by default.
     *
     * @param stream the stream to be read for the storable input
     * @return the created StorableInput
     */
    StorableInput createStorableInput(InputStream stream);

    /**
     * Creates a StorableInput with the given input stream and encoding information.
     *
     * @param stream the stream to be read for the storable input
     * @param useUTF8 whether UTF-8 ({@code true}) or the default encoding ({@code false} is to be used
     * @return the created StorableInput
     */
    StorableInput createStorableInput(InputStream stream, boolean useUTF8);

    /**
     * Creates a StorableInput with the given location and encoding information.
     *
     * @param location URL of the file
     * @param useUTF8 whether UTF-8 ({@code true}) or the default encoding ({@code false} is to be used
     * @return the created StorableInput
     * @throws IOException if an I/O error occurs while reading from the input location
     */
    StorableInput createStorableInput(URL location, boolean useUTF8) throws IOException;

    /**
     * Creates a StorableInput with the given string.
     *
     * @param string the string to be read for the storable input
     * @return the created StorableInput
     */
    StorableInput createStorableInput(String string);

    /**
     * Creates a PatchingStorableInput with the given input stream.
     * UTF character encoding is used by default.
     *
     * @param stream the stream to be read for the patching storable input
     * @return the created PatchingStorableInput
     */
    StorableInput createPatchingStorableInput(InputStream stream);

    /**
     * Creates a PatchingStorableInput with the given location and encoding information.
     *
     * @param location URL of the file
     * @param useUTF8 whether UTF-8 ({@code true}) or the default encoding ({@code false} is to be used
     * @return the created PatchingStorableInput
     * @throws IOException if an I/O error occurs while reading from the input location
     */
    StorableInput createPatchingStorableInput(URL location, boolean useUTF8) throws IOException;

    /**
     * Creates a StorableOutput with the given file.
     *
     * @param file the file from which the FileOutputStream for the PrintWriter will be created
     * @return the created StorableOutput
     * @throws FileNotFoundException if file wasn't found
     */
    StorableOutput createStorableOutput(File file) throws FileNotFoundException;

    /**
     * Creates a StorableOutput with the given output stream.
     *
     * @param stream the OutputStream the PrintWriter will be initialized with
     * @return the created StorableOutput
     */
    StorableOutput createStorableOutput(OutputStream stream);



    /**
     * Creates a deep clone of the provided Storable.
     *
     * @param original the storable, which will be cloned
     * @param <S> the type of the object being cloned, which has to extend {@link Storable}
     * @return a deep clone of the original Storable
     * @throws Exception throws Exception if Storable wasn't cloned successfully
     */
    <S extends Storable> S cloneStorable(S original) throws Exception;


    /**
     * Constructs a drawing change event.
     * @param source drawing the event comes from
     * @param r the rectangle describing the area of the drawing the event relates to
     * @return the constructed DrawingChangeEvent
     */
    DrawingChangeEvent createDrawingChangeEvent(Drawing source, Rectangle r);

    /**
     * Constructs a figure change event.
     * @param figure the object on which the Event initially occurred
     * @param rectangle the area to be invalidated
     * @return the constructed FigureChangeEvent
     */
    FigureChangeEvent createFigureChangeEvent(Figure figure, Rectangle rectangle);

    /**
     * Constructs a figure change event without specifying the area to be invalidated.
     * @param figure the object on which the Event initially occurred
     * @return the constructed FigureChangeEvent
     */
    FigureChangeEvent createFigureChangeEvent(Figure figure);

    /**
     * Returns a FigureEnumeration that yields only figures matching the predicate.
     * @param enumeration source enumeration
     * @param predicate   filter condition
     * @return filtered FigureEnumeration
     */
    FigureEnumeration createFilteredFigureEnumerator(
        FigureEnumeration enumeration, Predicate<Figure> predicate);

    /**
     * Returns a FigureEnumeration that combines two FigureEnumerations into one.
     * @param enumA first enumeration to be used in merge
     * @param enumB second enumeration to be used in merge
     * @return merged FigureEnumeration
     */
    FigureEnumeration createMergedFigureEnumerator(
        FigureEnumeration enumA, FigureEnumeration enumB);

    /**
     * Tests whether the Drawing is an instance of NullDrawing.
     * @param drawing the Drawing that is tested
     * @return whether the Drawing is a NulLDrawing
     */
    boolean isDrawingNullDrawing(Drawing drawing);

    /**
     * Creates an ElbowText locator that allows locating a point 10 pixels above the center of a Figure.
     * @return the created locator
     */
    Locator createElbowTextLocator();

    /**
     * Creates an offset locator to offset another Locator.
     * @see Locator
     * @param locator the locator that will receive the offset
     * @return the created locator
     */
    Locator createOffsetLocator(Locator locator);

    /**
     * Creates an offset locator to offset another Locator.
     * @see Locator
     * @param locator the locator that will receive the offset
     * @param offsetX offset on the x-axis
     * @param offsetY offset on the y-axis
     * @return the created locator
     */
    Locator createOffsetLocator(Locator locator, int offsetX, int offsetY);

    /**
     * Creates and returns a file filter that includes all supported input file types.
     * The preferred file filter is set to the last one selected by the user.
     *
     * @return file filter for all known input file types
     */
    MultiExtensionFileFilter getKnownDrawingTypesFileFilter();

    /**
     * Gets the registered ExtensionFileFilter for the DrawingType.
     * @param drawingType name of drawing type
     * @return filter / {@code null} if the drawing type is not registered
     */
    ExtensionFileFilter getExtensionFileFilterByDrawingType(String drawingType);

    /**
     * Registers a new drawing type.
     * @param drawingType name of the drawing type
     * @param fileFilter matching file filter for type
     */
    void registerDrawingType(String drawingType, ExtensionFileFilter fileFilter);

    /**
     * Retracts a drawing type from the set of known types.
     * @param drawingType the name of the drawing type to be retracted
     */
    void unregisterDrawingType(String drawingType);

    /**
     * Instantiates a Drawing based on its name.
     * @param drawingType class name of drawing (fully qualified class name)
     * @return created drawing, or {@code null} if the drawing type cannot be instantiated
     */
    Drawing createDrawingForDrawingType(String drawingType);

    /**
     * Adds an ExportFormat for Drawings to Renew.
     * It is added to the known export formats.
     *
     * @param exportFormat the ExportFormat which is to be added to Renew
     */
    void addExportFormat(ExportFormat<Drawing> exportFormat);

    /**
     * Removes an ExportFormat for Drawings from Renew.
     * It is removed from the known export formats.
     *
     * @param exportFormat the ExportFormat which is to be removed from Renew
     */
    void removeExportFormat(ExportFormat<Drawing> exportFormat);

    /**
     * Returns a list that contains all ExportFormats for Drawings known to Renew.
     * @return list containing all ExportFormats for Drawings known to Renew
     */
    List<ExportFormat<Drawing>> getExportFormats();

    /**
     * Adds an ImportFormat for Drawings to Renew.
     * @param importFormat The ImportFormat which is added to Renew
     */
    void addImportFormat(ImportFormat<Drawing> importFormat);

    /**
     * Removes an ImportFormat for Drawings from Renew.
     * @param importFormat The importFormat to be removed
     */
    void removeImportFormat(ImportFormat<Drawing> importFormat);

    /**
     * Returns a list that contains all ImportFormats for Drawings known to Renew.
     * @return list containing all ImportFormats for Drawings known to Renew
     */
    List<ImportFormat<Drawing>> getImportFormats();
}
