/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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 org.apache.directory.studio.templateeditor.editor.widgets;


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Base64;

import org.apache.directory.studio.entryeditors.IEntryEditor;
import org.apache.directory.studio.ldapbrowser.core.model.IAttribute;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.widgets.FormToolkit;

import org.apache.directory.studio.templateeditor.EntryTemplatePlugin;
import org.apache.directory.studio.templateeditor.EntryTemplatePluginConstants;
import org.apache.directory.studio.templateeditor.EntryTemplatePluginUtils;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateImage;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateWidget;


/**
 * This class implements an editor image.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class EditorImage extends EditorWidget<TemplateImage>
{
    /** The widget's composite */
    private Composite composite;

    /** The image label, used to display the image */
    private Label imageLabel;

    /** The 'Save As...' button */
    private ToolItem saveAsToolItem;

    /** The 'Clear' button */
    private ToolItem clearToolItem;

    /** The 'Browse...' button */
    private ToolItem browseToolItem;

    /** The current image */
    private Image image;

    /** The image data as bytes array */
    private byte[] imageBytes;

    /** The default width */
    private static int DEFAULT_WIDTH = 400;

    /** The default height */
    private static int DEFAULT_HEIGHT = 300;


    /**
     * Creates a new instance of EditorImage.
     * 
     * @param editor
     *      the associated editor
     * @param templateImage
     *      the associated template image
     * @param toolkit
     *      the associated toolkit
     */
    public EditorImage( IEntryEditor editor, TemplateImage templateImage, FormToolkit toolkit )
    {
        super( templateImage, editor, toolkit );
    }


    /**
     * {@inheritDoc}
     */
    public Composite createWidget( Composite parent )
    {
        // Creating and initializing the widget UI
        Composite composite = initWidget( parent );

        // Updating the widget's content
        updateWidget();

        // Adding the listeners
        addListeners();

        return composite;
    }


    /**
     * Creates and initializes the widget UI.
     *
     * @param parent
     *      the parent composite
     * @return
     *      the associated composite
     */
    private Composite initWidget( Composite parent )
    {
        // Creating the widget composite
        composite = getToolkit().createComposite( parent );
        composite.setLayoutData( getGridata() );

        // Creating the layout
        GridLayout gl = new GridLayout( ( needsToolbar() ? 2 : 1 ), false );
        gl.marginHeight = gl.marginWidth = 0;
        gl.horizontalSpacing = gl.verticalSpacing = 0;
        composite.setLayout( gl );

        // Image Label
        imageLabel = getToolkit().createLabel( composite, null );
        imageLabel.setLayoutData( new GridData( SWT.CENTER, SWT.CENTER, false, false ) );

        // Toolbar (if needed)
        if ( needsToolbar() )
        {
            ToolBar toolbar = new ToolBar( composite, SWT.VERTICAL );
            toolbar.setLayoutData( new GridData( SWT.NONE, SWT.FILL, false, true ) );

            // Save As Button
            if ( getWidget().isShowSaveAsButton() )
            {
                saveAsToolItem = new ToolItem( toolbar, SWT.PUSH );
                saveAsToolItem.setToolTipText( Messages.getString( "EditorImage.SaveAs" ) ); //$NON-NLS-1$
                saveAsToolItem.setImage( EntryTemplatePlugin.getDefault().getImage(
                    EntryTemplatePluginConstants.IMG_TOOLBAR_SAVE_AS ) );
            }

            // Clear Button
            if ( getWidget().isShowClearButton() )
            {
                clearToolItem = new ToolItem( toolbar, SWT.PUSH );
                clearToolItem.setToolTipText( Messages.getString( "EditorImage.Clear" ) ); //$NON-NLS-1$
                clearToolItem.setImage( EntryTemplatePlugin.getDefault().getImage(
                    EntryTemplatePluginConstants.IMG_TOOLBAR_CLEAR ) );
            }
            // Browse Button
            if ( getWidget().isShowBrowseButton() )
            {
                browseToolItem = new ToolItem( toolbar, SWT.PUSH );
                browseToolItem.setToolTipText( Messages.getString( "EditorImage.Browse" ) ); //$NON-NLS-1$
                browseToolItem.setImage( EntryTemplatePlugin.getDefault().getImage(
                    EntryTemplatePluginConstants.IMG_TOOLBAR_BROWSE_IMAGE ) );
            }
        }

        return composite;
    }


    /**
     * Indicates if the widget needs a toolbar for actions.
     *
     * @return
     *      <code>true</code> if the widget needs a toolbar for actions,
     *      <code>false</code> if not
     */
    private boolean needsToolbar()
    {
        return getWidget().isShowSaveAsButton() || getWidget().isShowClearButton() || getWidget().isShowBrowseButton();
    }


    /**
     * Updates the widget's content.
     */
    private void updateWidget()
    {
        // Initializing the image bytes from the given entry.
        initImageBytesFromEntry();

        // Constrains and displays it
        constrainAndDisplayImage();

        // Updating the states of the buttons
        updateButtonsStates();
    }


    /**
     * Initializes the image bytes from the given entry.
     */
    private void initImageBytesFromEntry()
    {
        // Checking is we need to display a value taken from the entry
        // or use the given value
        String attributeType = getWidget().getAttributeType();
        if ( attributeType != null )
        {
            // Getting the image bytes in the attribute
            IAttribute attribute = getAttribute();
            if ( ( attribute != null ) && ( attribute.isBinary() ) && ( attribute.getValueSize() > 0 ) )
            {
                imageBytes = attribute.getBinaryValues()[0];
            }
            else
            {
                imageBytes = null;
            }
        }
        else
        {
            // Getting the image bytes given in the template
            String imageDataString = getWidget().getImageData();
            if ( ( imageDataString != null ) && ( !imageDataString.equals( "" ) ) ) //$NON-NLS-1$
            {
                imageBytes = Base64.getDecoder().decode( imageDataString );
            }
        }
    }


    /**
     * Returns an {@link ImageData} constructed from an array of bytes.
     *
     * @param imageBytes
     *      the array of bytes
     * @return
     *      the corresponding {@link ImageData}
     * @throws SWTException
     *      if an error occurs when constructing the {@link ImageData}
     */
    private ImageData getImageData( byte[] imageBytes ) throws SWTException
    {
        if ( imageBytes != null && imageBytes.length > 0 )
        {
            return new ImageData( new ByteArrayInputStream( imageBytes ) );
        }
        else
        {
            return null;
        }
    }


    /**
     * Returns the {@link ImageData} associated with the current image bytes.
     *
     * @return
     *      the {@link ImageData} associated with the current image bytes.
     */
    private ImageData getImageData()
    {
        if ( imageBytes != null )
        {
            // Getting the image data associated with the bytes
            try
            {
                return getImageData( imageBytes );
            }
            catch ( SWTException e )
            {
                // Nothing to do, we just need to return the default image.
            }
        }

        // No image
        return EntryTemplatePlugin.getDefault().getImage( EntryTemplatePluginConstants.IMG_NO_IMAGE ).getImageData();
    }


    /**
     * Constrains and displays the image.
     */
    private void constrainAndDisplayImage()
    {
        // Getting the image data
        ImageData imageData = getImageData();

        // Getting width and height from the template image
        int templateImageWidth = getWidget().getImageWidth();
        int templateImageHeight = getWidget().getImageHeight();

        // No resizing is required
        if ( ( templateImageWidth == TemplateWidget.DEFAULT_SIZE )
            && ( templateImageHeight == TemplateWidget.DEFAULT_SIZE ) )
        {
            // Checking if the dimensions of the image are greater than the default values
            if ( ( imageData.width > DEFAULT_WIDTH ) || ( imageData.height > DEFAULT_HEIGHT ) )
            {
                // Calculating scale factors to determine whether width or height should be used
                float widthScaleFactor = imageData.width / DEFAULT_WIDTH;
                float heightScaleFactor = imageData.height / DEFAULT_HEIGHT;

                // Resizing the image data
                if ( widthScaleFactor >= heightScaleFactor )
                {
                    imageData = getScaledImageData( imageData, DEFAULT_WIDTH, TemplateWidget.DEFAULT_SIZE );
                }
                else
                {
                    imageData = getScaledImageData( imageData, TemplateWidget.DEFAULT_SIZE, DEFAULT_HEIGHT );
                }
            }
        }
        else
        {
            // Resizing the image data
            imageData = getScaledImageData( imageData, templateImageWidth, templateImageHeight );
        }

        // Creating the image
        image = new Image( PlatformUI.getWorkbench().getDisplay(), imageData );

        // Setting the image
        imageLabel.setImage( image );
    }


    /**
     * Returns a scaled copy of the given data scaled to the given dimensions, 
     * or the original image data if scaling is not needed.
     *
     * @param imageData
     *      the image data
     * @param width
     *      the preferred width
     * @param height
     *      the preferred height
     * @return
     *      a scaled copy of the given data scaled to the given dimensions, 
     *      or the original image data if scaling is not needed.
     */
    private ImageData getScaledImageData( ImageData imageData, int width, int height )
    {
        // Resizing the image with the given width value
        if ( ( width != TemplateWidget.DEFAULT_SIZE ) && ( height == TemplateWidget.DEFAULT_SIZE ) )
        {
            // Computing the scale factor
            float scaleFactor = ( float ) imageData.width / ( float ) width;

            // Computing the final height
            int finalHeight = ( int ) ( imageData.height / scaleFactor );

            // Returning the scaled image data
            return imageData.scaledTo( width, finalHeight );
        }
        // Resizing the image with the given height value
        else if ( ( width == TemplateWidget.DEFAULT_SIZE ) && ( height != TemplateWidget.DEFAULT_SIZE ) )
        {
            // Computing the scale factor
            float scaleFactor = ( float ) imageData.height / ( float ) height;

            // Computing the final height
            int finalWidth = ( int ) ( imageData.width / scaleFactor );

            // Returning the scaled image data
            return imageData.scaledTo( finalWidth, height );
        }
        // Resizing the image with the given width and height values
        else if ( ( width != TemplateWidget.DEFAULT_SIZE ) && ( height != TemplateWidget.DEFAULT_SIZE ) )
        {
            // Returning the original image data
            return imageData.scaledTo( width, height );
        }

        // No resizing needed
        return imageData;
    }


    /**
     * Adds the listeners.
     */
    private void addListeners()
    {
        // Save As button
        if ( ( saveAsToolItem != null ) && ( !saveAsToolItem.isDisposed() ) )
        {
            saveAsToolItem.addSelectionListener( new SelectionAdapter()
            {
                public void widgetSelected( SelectionEvent e )
                {
                    saveAsToolItemAction();
                }
            } );
        }

        // Clear button
        if ( ( clearToolItem != null ) && ( !clearToolItem.isDisposed() ) )
        {
            clearToolItem.addSelectionListener( new SelectionAdapter()
            {
                public void widgetSelected( SelectionEvent e )
                {
                    clearToolItemAction();
                }
            } );
        }

        // Browse button
        if ( ( browseToolItem != null ) && ( !browseToolItem.isDisposed() ) )
        {
            browseToolItem.addSelectionListener( new SelectionAdapter()
            {
                public void widgetSelected( SelectionEvent e )
                {
                    browseToolItemAction();
                }
            } );
        }
    }


    /**
     * This method is called when the 'Save As...' toolbar item is clicked.
     */
    private void saveAsToolItemAction()
    {
        // Launching a FileDialog to select where to save the file
        FileDialog fd = new FileDialog( PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), SWT.SAVE );
        String selected = fd.open();
        if ( selected != null )
        {
            // Getting the selected file
            File selectedFile = new File( selected );
            if ( ( !selectedFile.exists() ) || ( selectedFile.canWrite() ) )
            {
                try
                {
                    FileOutputStream fos = new FileOutputStream( selectedFile );
                    fos.write( imageBytes );
                    fos.close();
                }
                catch ( Exception e )
                {
                    // Logging the error
                    EntryTemplatePluginUtils.logError( e, "An error occurred while saving the image to disk.", //$NON-NLS-1$
                        new Object[0] );

                    // Launching an error dialog
                    MessageDialog
                        .openError(
                            PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
                            Messages.getString( "EditorImage.ErrorSavingMessageDialogTitle" ), Messages.getString( "EditorImage.ErrorSavingMessageDialogMessage" ) ); //$NON-NLS-1$ //$NON-NLS-2$
                }
            }
        }
    }


    /**
     * This method is called when the 'Clear...' toolbar item is clicked.
     */
    private void clearToolItemAction()
    {
        // Launching a confirmation dialog
        if ( MessageDialog.openConfirm( PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), Messages
            .getString( "EditorImage.Confirmation" ), Messages.getString( "EditorImage.ConfirmationClearImage" ) ) ) //$NON-NLS-1$ //$NON-NLS-2$
        {
            // Removing the image bytes
            imageBytes = null;

            // Constrains and displays the image
            constrainAndDisplayImage();

            // Refreshing the states of the buttons
            updateButtonsStates();

            // Updating the entry
            updateEntry();

            // Updating the image
            composite.getParent().update();
        }
    }


    /**
     * This method is called when the 'Browse...' toolbar item is clicked.
     */
    private void browseToolItemAction()
    {
        // Launching a FileDialog to select the file to load
        FileDialog fd = new FileDialog( PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), SWT.OPEN );
        String selected = fd.open();
        
        if ( selected != null )
        {
            // Getting the selected file
            File selectedFile = new File( selected );
            
            if ( ( selectedFile.exists() ) && ( selectedFile.canRead() ) )
            {
                try
                {
                    FileInputStream fis = null;
                    ByteArrayOutputStream baos = null;
                    
                    try
                    {
                        fis = new FileInputStream( selectedFile );
                        baos = new ByteArrayOutputStream( ( int ) selectedFile.length() );
                        byte[] buf = new byte[4096];
                        int len;
                        while ( ( len = fis.read( buf ) ) > 0 )
                        {
                            baos.write( buf, 0, len );
                        }
        
                        imageBytes = baos.toByteArray();
                    }
                    finally
                    {
                        if ( fis != null )
                        {
                            fis.close();
                        }
                        
                        if ( baos != null )
                        {
                            baos.close();
                        }
                    }
                }
                catch ( Exception e )
                {
                    // Logging the error
                    EntryTemplatePluginUtils.logError( e, "An error occurred while reading the image from disk.", //$NON-NLS-1$
                        new Object[0] );

                    // Launching an error dialog
                    MessageDialog
                        .openError(
                            PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
                            Messages.getString( "EditorImage.ErrorReadingMessageDialogTitle" ), Messages.getString( "EditorImage.ErrorReadingMessageDialogMessage" ) ); //$NON-NLS-1$ //$NON-NLS-2$
                }

                // Constrains and displays the image
                constrainAndDisplayImage();

                // Refreshing the states of the buttons
                updateButtonsStates();
            }
            else
            {
                // Logging the error
                EntryTemplatePluginUtils
                    .logError(
                        null,
                        "An error occurred while reading the image from disk. Image file does not exist or is not readable.", //$NON-NLS-1$
                        new Object[0] );

                // Launching an error dialog
                MessageDialog
                    .openError(
                        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
                        Messages.getString( "EditorImage.ErrorReadingMessageDialogTitle" ), Messages.getString( "EditorImage.ErrorReadingMessageDialogMessage" ) ); //$NON-NLS-1$ //$NON-NLS-2$
            }

            // Updating the entry
            updateEntry();

            // Updating the image
            composite.getParent().update();
        }
    }


    /**
     * Updates the states of the buttons.
     */
    private void updateButtonsStates()
    {
        if ( ( imageBytes != null ) && ( imageBytes.length > 0 ) )
        {
            if ( ( saveAsToolItem != null ) && ( !saveAsToolItem.isDisposed() ) )
            {
                saveAsToolItem.setEnabled( true );
            }

            if ( ( clearToolItem != null ) && ( !clearToolItem.isDisposed() ) )
            {
                clearToolItem.setEnabled( true );
            }

            if ( ( browseToolItem != null ) && ( !browseToolItem.isDisposed() ) )
            {
                browseToolItem.setEnabled( true );
            }
        }
        else
        {
            if ( ( saveAsToolItem != null ) && ( !saveAsToolItem.isDisposed() ) )
            {
                saveAsToolItem.setEnabled( false );
            }

            if ( ( clearToolItem != null ) && ( !clearToolItem.isDisposed() ) )
            {
                clearToolItem.setEnabled( false );
            }

            if ( ( browseToolItem != null ) && ( !browseToolItem.isDisposed() ) )
            {
                browseToolItem.setEnabled( true );
            }
        }
    }


    /**
     * {@inheritDoc}
     */
    public void update()
    {
        updateWidget();
    }


    /**
     * {@inheritDoc}
     */
    public void dispose()
    {
        image.dispose();
    }


    /**
     * This method is called when the entry has been updated in the UI.
     */
    private void updateEntry()
    {
        // Getting the attribute
        IAttribute attribute = getAttribute();
        if ( attribute == null )
        {
            if ( ( imageBytes != null ) && ( imageBytes.length != 0 ) )
            {
                // Creating a new attribute with the value
                addNewAttribute( imageBytes );
            }
        }
        else
        {
            if ( ( imageBytes != null ) && ( imageBytes.length != 0 ) )
            {
                // Modifying the existing attribute
                modifyAttributeValue( imageBytes );
            }
            else
            {
                // Deleting the attribute
                deleteAttribute();
            }
        }
    }
}
