package CH.ifa.draw.io;

import java.awt.Dimension;
import java.awt.Point;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import javax.swing.JOptionPane;

import CH.ifa.draw.util.KnownPlugins;
import CH.ifa.draw.util.UnknownTypeException;
import de.renew.draw.storables.api.StorableApi;
import de.renew.draw.storables.ontology.Drawing;
import de.renew.draw.storables.ontology.StorableInput;
import de.renew.draw.ui.ontology.StatusDisplayer;

/**
 * Provides methods to load retrievable {@link Drawing} objects from a {@link InputStream} bytestream and
 * from a {@link URL} location.
 */
public class StorableInputDrawingLoader {
    /**
     * Creates log4j Logger for this class to represent logging information.
     */
    private static final org.apache.log4j.Logger logger =
        org.apache.log4j.Logger.getLogger(StorableInputDrawingLoader.class);

    /**
     * Constructs Loader without parameters.
     */
    public StorableInputDrawingLoader() {}

    /**
     * Override to establish a new policy to create
     * input handlers.
     * @param location file from URL location to create storable input from
     * @param useUFT specifies whether the UFT is to be used.
     * @return StorableInput created
     * @throws IOException If an I/ O exception occurs
     */
    protected StorableInput makeStorableInput(URL location, boolean useUFT) throws IOException {
        return StorableApi.createStorableInput(location, useUFT);
    }

    /**
     * Override to establish a new policy to create
     * input handlers.
     * @param stream file from stream to create storable input from
     * @param useUFT specifies whether the UFT is to be used.
     * @return StorableInput created
     */
    protected StorableInput makeStorableInput(InputStream stream, boolean useUFT) {
        return StorableApi.createStorableInput(stream, useUFT);
    }

    /**
     * Loads a positioned drawing from a filepath with a status displayer to show error message.
     * @param file filepath
     * @param sd   status displayer
     * @return The positioned drawing loaded
     * @throws IOException If an I/ O exception occurs
     */
    public PositionedDrawing readFromStorableInput(File file, StatusDisplayer sd)
        throws IOException
    {
        try {
            return readFromStorableInput(file.toURI().toURL(), sd);
        } catch (MalformedURLException e) {
            sd.showStatus("Error " + e);
            return null;
        }
    }

    /**
     * Loads a positioned drawing from an {@link InputStream}.
     * @param stream The input stream
     * @return The positioned drawing loaded
     * @throws FileNotFoundException If no usable file could be found in the input stream
     * @throws IOException If an I/ O exception occurs
     */
    public PositionedDrawing readFromStorableInput(InputStream stream)
        throws FileNotFoundException, IOException
    {
        StorableInput input = StorableApi.createStorableInput(stream, true);
        return readFromStorableInput(input);
    }

    /**
     * Loads a positioned drawing from an {@link URL} and provides a status displayer.
     * @param location The Url location
     * @param sd The status Displayer
     * @return The positioned drawing loaded
     * @throws IOException If an I/ O exception occurs
     */
    public PositionedDrawing readFromStorableInput(URL location, StatusDisplayer sd)
        throws IOException
    {
        StorableInput input = null;
        try {
            input = makeStorableInput(location, true);
            return readFromStorableInput(input, sd);
        } catch (IOException e) {
            input = makeStorableInput(location, false);
            return readFromStorableInput(input, sd);
        } finally {
            if (input != null) {
                input.close();
            }
        }
    }

    /**
     * Loads a positioned drawing from an {@link StorableInput} and provides a status displayer.
     * @param input The storable input object
     * @param sd The status Displayer
     * @return The positioned drawing loaded
     */
    private PositionedDrawing readFromStorableInput(StorableInput input, StatusDisplayer sd) {
        Drawing drawing = null;
        Point newWindowLoc = null;
        Dimension newWindowDim = null;
        try {
            drawing = readStorableDrawing(input);
            // is there a window position to restore?
            if (input.canReadInt()) {
                newWindowLoc = new Point(input.readInt(), input.readInt());
                newWindowDim = new Dimension(input.readInt(), input.readInt());
            }
        } catch (FileNotFoundException fnfe) {
            sd.showStatus("Error: File not found. " + fnfe);
        } catch (IOException ioe) {
            logger.error("Could not open Drawing: " + input.getURI().toString());
            if (logger.isDebugEnabled()) {
                logger.debug(StorableInputDrawingLoader.class.getSimpleName() + ": " + ioe);
            }
            if (ioe instanceof UnknownTypeException) {
                UnknownTypeException ute = (UnknownTypeException) ioe;
                String missingPlugin = KnownPlugins.guessPluginByClass(ute.getType());

                StringBuilder sb = new StringBuilder();
                sb.append("Error:\n");
                sb.append("Could not open Drawing:\n");
                try {
                    sb.append(URLDecoder.decode(input.getURI().toString(), "UTF-8"));
                } catch (UnsupportedEncodingException uee) {
                    sb.append(input.getURI().toString());
                }
                sb.append("\n\n");
                sb.append("There might be a plugin missing.");
                if (missingPlugin != null) {
                    sb.append("\n");
                    sb.append("Try to install the plugin: ");
                    sb.append(missingPlugin);
                }
                String message = sb.toString();
                JOptionPane.showMessageDialog(
                    null, message, "Error: Could not open Drawing", JOptionPane.PLAIN_MESSAGE);
            }
            sd.showStatus("Error: Could not open Drawing. " + ioe);
        }
        if (drawing == null) {
            return null;
        }
        return new PositionedDrawing(newWindowLoc, newWindowDim, drawing);
    }

    /**
     * Loads a positioned drawing from an {@link StorableInput}.
     * @param input The storable input object
     * @return The positioned drawing loaded
     * @throws IOException If an I/ O exception occurs.
     * @throws FileNotFoundException If no usable file could be found in the input object
     */
    private PositionedDrawing readFromStorableInput(StorableInput input)
        throws FileNotFoundException, IOException
    {
        Drawing drawing = null;
        Point newWindowLoc = null;
        Dimension newWindowDim = null;
        try {
            drawing = readStorableDrawing(input);
            // is there a window position to restore?
            if (input.canReadInt()) {
                newWindowLoc = new Point(input.readInt(), input.readInt());
                newWindowDim = new Dimension(input.readInt(), input.readInt());
            }
        } catch (FileNotFoundException fnfe) {
            String msg =
                "StorableInputDrawingLoader.readFromStorableInput : Error: File not found. " + fnfe;
            throw new FileNotFoundException(msg);
        } catch (IOException ioe) {
            String msg = "StorableInputDrawingLoader.readFromStorableInput : Error " + ioe;
            throw new IOException(msg);
        }
        if (drawing == null) {
            return null;
        }
        return new PositionedDrawing(newWindowLoc, newWindowDim, drawing);
    }

    /**
     * Loads a drawing from an {@link StorableInput} object.
     * @param input the storable input object
     * @return The drawing loaded
     * @throws IOException exception
     */
    public static Drawing readStorableDrawing(StorableInput input) throws IOException {
        if (input.canReadInt()) {
            input.setVersion(input.readInt());
        } else {
            input.setVersion(0);
        }
        return (Drawing) input.readStorable();
    }
}