package gui;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.accessibility.*;

/**
 * <p><b>This class supports gif animation in label and is derived from {@link GifCLabel} SWT class (see {@link #setGifImage(InputStream)} or {@link #setGifImage(String)})</b><br />
 * <b>Changes by Sorceror, (a)sync.exec(...) call fix by YAMaiDie</b></p>
 */
public class GifCLabel extends Canvas {

    /** Gap between icon and text */
    private static final int GAP = 5;
    /** Left and right margins */
    private static final int DEFAULT_MARGIN = 3;
    /** a string inserted in the middle of text that has been shortened */
    private static final String ELLIPSIS = "..."; //$NON-NLS-1$ // could use the ellipsis glyph on some platforms "\u2026"
    /** the alignment. Either CENTER, RIGHT, LEFT. Default is LEFT*/
    private int align = SWT.LEFT;
    private int leftMargin = DEFAULT_MARGIN;
    private int topMargin = DEFAULT_MARGIN;
    private int rightMargin = DEFAULT_MARGIN;
    private int bottomMargin = DEFAULT_MARGIN;
    private String text;
    private Image image;
    private String appToolTipText;
    private boolean ignoreDispose;

    private Image backgroundImage;
    private Color[] gradientColors;
    private int[] gradientPercents;
    private boolean gradientVertical;
    private Color background;

    private GifThread thread = null;

    private static int DRAW_FLAGS = SWT.DRAW_MNEMONIC | SWT.DRAW_TAB | SWT.DRAW_TRANSPARENT | SWT.DRAW_DELIMITER;

public GifCLabel(Composite parent, int style) {
    super(parent, checkStyle(style));
    if ((style & (SWT.CENTER | SWT.RIGHT)) == 0) style |= SWT.LEFT;
    if ((style & SWT.CENTER) != 0) align = SWT.CENTER;
    if ((style & SWT.RIGHT) != 0)  align = SWT.RIGHT;
    if ((style & SWT.LEFT) != 0)   align = SWT.LEFT;

    addPaintListener(new PaintListener() {
        public void paintControl(PaintEvent event) {
            onPaint(event);
        }
    });

    addTraverseListener(new TraverseListener() {
        public void keyTraversed(TraverseEvent event) {
            if (event.detail == SWT.TRAVERSE_MNEMONIC) {
                onMnemonic(event);
            }
        }
    });

    addListener(SWT.Dispose, new Listener() {
        public void handleEvent(Event event) {
            onDispose(event);
        }
    });

    initAccessible();

}
private static int checkStyle (int style) {
    if ((style & SWT.BORDER) != 0) style |= SWT.SHADOW_IN;
    int mask = SWT.SHADOW_IN | SWT.SHADOW_OUT | SWT.SHADOW_NONE | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
    style = style & mask;
    return style |= SWT.NO_FOCUS | SWT.DOUBLE_BUFFERED;
}

public Point computeSize(int wHint, int hHint, boolean changed) {
    checkWidget();
    Point e = getTotalSize(image, text);
    if (wHint == SWT.DEFAULT){
        e.x += leftMargin + rightMargin;
    } else {
        e.x = wHint;
    }
    if (hHint == SWT.DEFAULT) {
        e.y += topMargin + bottomMargin;
    } else {
        e.y = hHint;
    }
    return e;
}

private void drawBevelRect(GC gc, int x, int y, int w, int h, Color topleft, Color bottomright) {
    gc.setForeground(bottomright);
    gc.drawLine(x+w, y,   x+w, y+h);
    gc.drawLine(x,   y+h, x+w, y+h);

    gc.setForeground(topleft);
    gc.drawLine(x, y, x+w-1, y);
    gc.drawLine(x, y, x,     y+h-1);
}

char _findMnemonic (String string) {
    if (string == null) return '\0';
    int index = 0;
    int length = string.length ();
    do {
        while (index < length && string.charAt (index) != '&') index++;
        if (++index >= length) return '\0';
        if (string.charAt (index) != '&') return Character.toLowerCase (string.charAt (index));
        index++;
    } while (index < length);
    return '\0';
}

public int getAlignment() {
    //checkWidget();
    return align;
}

public int getBottomMargin() {
    //checkWidget();
    return bottomMargin;
}

public Image getImage() {
    //checkWidget();
    return image;
}

public int getLeftMargin() {
    //checkWidget();
    return leftMargin;
}

public int getRightMargin() {
    //checkWidget();
    return rightMargin;
}

private Point getTotalSize(Image image, String text) {
    Point size = new Point(0, 0);

    if (image != null) {
        Rectangle r = image.getBounds();
        size.x += r.width;
        size.y += r.height;
    }

    GC gc = new GC(this);
    if (text != null && text.length() > 0) {
        Point e = gc.textExtent(text, DRAW_FLAGS);
        size.x += e.x;
        size.y = Math.max(size.y, e.y);
        if (image != null) size.x += GAP;
    } else {
        size.y = Math.max(size.y, gc.getFontMetrics().getHeight());
    }
    gc.dispose();

    return size;
}

public int getStyle () {
    int style = super.getStyle();
    switch (align) {
        case SWT.RIGHT: style |= SWT.RIGHT; break;
        case SWT.CENTER: style |= SWT.CENTER; break;
        case SWT.LEFT: style |= SWT.LEFT; break;
    }
    return style;
}

public String getText() {
    //checkWidget();
    return text;
}

public String getToolTipText () {
    checkWidget();
    return appToolTipText;
}

public int getTopMargin() {
    //checkWidget();
    return topMargin;
}

private void initAccessible() {
    Accessible accessible = getAccessible();
    accessible.addAccessibleListener(new AccessibleAdapter() {
        public void getName(AccessibleEvent e) {
            e.result = getText();
        }

        public void getHelp(AccessibleEvent e) {
            e.result = getToolTipText();
        }

        public void getKeyboardShortcut(AccessibleEvent e) {
            char mnemonic = _findMnemonic(GifCLabel.this.text); 
            if (mnemonic != '\0') {
                e.result = "Alt+"+mnemonic; //$NON-NLS-1$
            }
        }
    });

    accessible.addAccessibleControlListener(new AccessibleControlAdapter() {
        public void getChildAtPoint(AccessibleControlEvent e) {
            e.childID = ACC.CHILDID_SELF;
        }

        public void getLocation(AccessibleControlEvent e) {
            Rectangle rect = getDisplay().map(getParent(), null, getBounds());
            e.x = rect.x;
            e.y = rect.y;
            e.width = rect.width;
            e.height = rect.height;
        }

        public void getChildCount(AccessibleControlEvent e) {
            e.detail = 0;
        }

        public void getRole(AccessibleControlEvent e) {
            e.detail = ACC.ROLE_LABEL;
        }

        public void getState(AccessibleControlEvent e) {
            e.detail = ACC.STATE_READONLY;
        }
    });
}

void onDispose(Event event) {
    /* make this handler run after other dispose listeners */
    if (ignoreDispose) {
        ignoreDispose = false;
        return;
    }
    ignoreDispose = true;
    notifyListeners (event.type, event);
    event.type = SWT.NONE;

    gradientColors = null;
    gradientPercents = null;
    backgroundImage = null;
    text = null;
    image = null;
    appToolTipText = null;
}

void onMnemonic(TraverseEvent event) {
    char mnemonic = _findMnemonic(text);
    if (mnemonic == '\0') return;
    if (Character.toLowerCase(event.character) != mnemonic) return;
    Composite control = this.getParent();
    while (control != null) {
        Control [] children = control.getChildren();
        int index = 0;
        while (index < children.length) {
            if (children [index] == this) break;
            index++;
        }
        index++;
        if (index < children.length) {
            if (children [index].setFocus ()) {
                event.doit = true;
                event.detail = SWT.TRAVERSE_NONE;
            }
        }
        control = control.getParent();
    }
}

void onPaint(PaintEvent event) {
    Rectangle rect = getClientArea();
    if (rect.width == 0 || rect.height == 0) return;

    boolean shortenText = false;
    String t = text;
    Image img = image;
    int availableWidth = Math.max(0, rect.width - (leftMargin + rightMargin));
    Point extent = getTotalSize(img, t);
    if (extent.x > availableWidth) {
        img = null;
        extent = getTotalSize(img, t);
        if (extent.x > availableWidth) {
            shortenText = true;
        }
    }

    GC gc = event.gc;
    String[] lines = text == null ? null : splitString(text); 

    // shorten the text
    if (shortenText) {
        extent.x = 0;
        for(int i = 0; i < lines.length; i++) {
            Point e = gc.textExtent(lines[i], DRAW_FLAGS);
            if (e.x > availableWidth) {
                lines[i] = shortenText(gc, lines[i], availableWidth);
                extent.x = Math.max(extent.x, getTotalSize(null, lines[i]).x);
            } else {
                extent.x = Math.max(extent.x, e.x);
            }
        }
        if (appToolTipText == null) {
            super.setToolTipText(text);
        }
    } else {
        super.setToolTipText(appToolTipText);
    }

    // determine horizontal position
    int x = rect.x + leftMargin;
    if (align == SWT.CENTER) {
        x = (rect.width - extent.x)/2;
    }
    if (align == SWT.RIGHT) {
        x = rect.width - rightMargin - extent.x;
    }

    // draw a background image behind the text
    try {
        if (backgroundImage != null) {
            // draw a background image behind the text
            Rectangle imageRect = backgroundImage.getBounds();
            // tile image to fill space
            gc.setBackground(getBackground());
            gc.fillRectangle(rect);
            int xPos = 0;
            while (xPos < rect.width) {
                int yPos = 0;
                while (yPos < rect.height) {
                    gc.drawImage(backgroundImage, xPos, yPos);
                    yPos += imageRect.height;
                }
                xPos += imageRect.width;
            }
        } else if (gradientColors != null) {
            // draw a gradient behind the text
            final Color oldBackground = gc.getBackground();
            if (gradientColors.length == 1) {
                if (gradientColors[0] != null) gc.setBackground(gradientColors[0]);
                gc.fillRectangle(0, 0, rect.width, rect.height);
            } else {
                final Color oldForeground = gc.getForeground();
                Color lastColor = gradientColors[0];
                if (lastColor == null) lastColor = oldBackground;
                int pos = 0;
                for (int i = 0; i < gradientPercents.length; ++i) {
                    gc.setForeground(lastColor);
                    lastColor = gradientColors[i + 1];
                    if (lastColor == null) lastColor = oldBackground;
                    gc.setBackground(lastColor);
                    if (gradientVertical) {
                        final int gradientHeight = (gradientPercents[i] * rect.height / 100) - pos;
                        gc.fillGradientRectangle(0, pos, rect.width, gradientHeight, true);
                        pos += gradientHeight;
                    } else {
                        final int gradientWidth = (gradientPercents[i] * rect.width / 100) - pos;
                        gc.fillGradientRectangle(pos, 0, gradientWidth, rect.height, false);
                        pos += gradientWidth;
                    }
                }
                if (gradientVertical && pos < rect.height) {
                    gc.setBackground(getBackground());
                    gc.fillRectangle(0, pos, rect.width, rect.height - pos);
                }
                if (!gradientVertical && pos < rect.width) {
                    gc.setBackground(getBackground());
                    gc.fillRectangle(pos, 0, rect.width - pos, rect.height);
                }
                gc.setForeground(oldForeground);
            }
            gc.setBackground(oldBackground);
        } else {
            if (background != null || (getStyle() & SWT.DOUBLE_BUFFERED) == 0) {
                gc.setBackground(getBackground());
                gc.fillRectangle(rect);
            }
        }
    } catch (SWTException e) {
        if ((getStyle() & SWT.DOUBLE_BUFFERED) == 0) {
            gc.setBackground(getBackground());
            gc.fillRectangle(rect);
        }
    }

    // draw border
    int style = getStyle();
    if ((style & SWT.SHADOW_IN) != 0 || (style & SWT.SHADOW_OUT) != 0) {
        paintBorder(gc, rect);
    }

    Rectangle imageRect = null;
    int lineHeight = 0, textHeight = 0, imageHeight = 0;

    if (img != null) {
        imageRect = img.getBounds();
        imageHeight = imageRect.height;
    }
    if (lines != null) {
        lineHeight = gc.getFontMetrics().getHeight();
        textHeight = lines.length * lineHeight;
    }

    int imageY = 0, midPoint = 0, lineY = 0;
    if (imageHeight > textHeight ) {
        if (topMargin == DEFAULT_MARGIN && bottomMargin == DEFAULT_MARGIN) imageY = rect.y + (rect.height - imageHeight) / 2;
        else imageY = topMargin;
        midPoint = imageY + imageHeight/2;
        lineY = midPoint - textHeight / 2;
    }
    else {
        if (topMargin == DEFAULT_MARGIN && bottomMargin == DEFAULT_MARGIN) lineY = rect.y + (rect.height - textHeight) / 2;
        else lineY = topMargin;
        midPoint = lineY + textHeight/2;
        imageY = midPoint - imageHeight / 2;
    }

    // draw the image
    if (img != null) {
        gc.drawImage(img, 0, 0, imageRect.width, imageHeight, 
                        x, imageY, imageRect.width, imageHeight);
        x +=  imageRect.width + GAP;
        extent.x -= imageRect.width + GAP;
    }

    // draw the text
    if (lines != null) {
        gc.setForeground(getForeground());
        for (int i = 0; i < lines.length; i++) {
            int lineX = x;
            if (lines.length > 1) {
                if (align == SWT.CENTER) {
                    int lineWidth = gc.textExtent(lines[i], DRAW_FLAGS).x;
                    lineX = x + Math.max(0, (extent.x - lineWidth) / 2);
                }
                if (align == SWT.RIGHT) {
                    int lineWidth = gc.textExtent(lines[i], DRAW_FLAGS).x;
                    lineX = Math.max(x, rect.x + rect.width - rightMargin - lineWidth);
                }
            }
            gc.drawText(lines[i], lineX, lineY, DRAW_FLAGS);
            lineY += lineHeight;
        }
    }
}

private void paintBorder(GC gc, Rectangle r) {
    Display disp= getDisplay();

    Color c1 = null;
    Color c2 = null;

    int style = getStyle();
    if ((style & SWT.SHADOW_IN) != 0) {
        c1 = disp.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
        c2 = disp.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
    }
    if ((style & SWT.SHADOW_OUT) != 0) {        
        c1 = disp.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW);
        c2 = disp.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
    }

    if (c1 != null && c2 != null) {
        gc.setLineWidth(1);
        drawBevelRect(gc, r.x, r.y, r.width-1, r.height-1, c1, c2);
    }
}

public void setAlignment(int align) {
    checkWidget();
    if (align != SWT.LEFT && align != SWT.RIGHT && align != SWT.CENTER) {
        SWT.error(SWT.ERROR_INVALID_ARGUMENT);
    }
    if (this.align != align) {
        this.align = align;
        redraw();
    }
}

public void setBackground (Color color) {
    super.setBackground (color);
    // Are these settings the same as before?
    if (backgroundImage == null && 
        gradientColors == null && 
        gradientPercents == null) {
        if (color == null) {
            if (background == null) return;
        } else {
            if (color.equals(background)) return;
        }       
    }
    background = color;
    backgroundImage = null;
    gradientColors = null;
    gradientPercents = null;
    redraw ();
}

public void setBackground(Color[] colors, int[] percents) {
    setBackground(colors, percents, false);
}

public void setBackground(Color[] colors, int[] percents, boolean vertical) {   
    checkWidget();
    if (colors != null) {
        if (percents == null || percents.length != colors.length - 1) {
            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
        }
        if (getDisplay().getDepth() < 15) {
            // Don't use gradients on low color displays
            colors = new Color[] {colors[colors.length - 1]};
            percents = new int[] { };
        }
        for (int i = 0; i < percents.length; i++) {
            if (percents[i] < 0 || percents[i] > 100) {
                SWT.error(SWT.ERROR_INVALID_ARGUMENT);
            }
            if (i > 0 && percents[i] < percents[i-1]) {
                SWT.error(SWT.ERROR_INVALID_ARGUMENT);
            }
        }
    }

    // Are these settings the same as before?
    final Color background = getBackground();
    if (backgroundImage == null) {
        if ((gradientColors != null) && (colors != null) && 
            (gradientColors.length == colors.length)) {
            boolean same = false;
            for (int i = 0; i < gradientColors.length; i++) {
                same = (gradientColors[i] == colors[i]) ||
                    ((gradientColors[i] == null) && (colors[i] == background)) ||
                    ((gradientColors[i] == background) && (colors[i] == null));
                if (!same) break;
            }
            if (same) {
                for (int i = 0; i < gradientPercents.length; i++) {
                    same = gradientPercents[i] == percents[i];
                    if (!same) break;
                }
            }
            if (same && this.gradientVertical == vertical) return;
        }
    } else {
        backgroundImage = null;
    }
    // Store the new settings
    if (colors == null) {
        gradientColors = null;
        gradientPercents = null;
        gradientVertical = false;
    } else {
        gradientColors = new Color[colors.length];
        for (int i = 0; i < colors.length; ++i)
            gradientColors[i] = (colors[i] != null) ? colors[i] : background;
        gradientPercents = new int[percents.length];
        for (int i = 0; i < percents.length; ++i)
            gradientPercents[i] = percents[i];
        gradientVertical = vertical;
    }
    // Refresh with the new settings
    redraw();
}

public void setBackground(Image image) {
    checkWidget();
    if (image == backgroundImage) return;
    if (image != null) {
        gradientColors = null;
        gradientPercents = null;
    }
    backgroundImage = image;
    redraw();

}

public void setBottomMargin(int bottomMargin) {
    checkWidget();
    if (this.bottomMargin == bottomMargin || bottomMargin < 0) return;
    this.bottomMargin = bottomMargin;
    redraw();
}

public void setFont(Font font) {
    super.setFont(font);
    redraw();
}

public void setImage(Image image) {
    checkWidget();
    if(thread != null) {
        thread.stopRunning();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    if (image != this.image) {
        this.image = image;
        redraw();
    }
}

public void setGifImage(String path) {
    try {
        this.setGifImage(new FileInputStream(new File(path)));
    } catch (FileNotFoundException e) {
        this.image = null;
        return;
    }
}

public void setGifImage(InputStream inputStream) {
    checkWidget();
    if(thread != null) thread.stopRunning();

    ImageLoader loader = new ImageLoader();

    try {
        loader.load(inputStream);
    } catch (Exception e) {
        this.image = null;
        return;
    }

    if (loader.data[0] != null)
        this.image = new Image(this.getDisplay(), loader.data[0]);

    if (loader.data.length > 1) {
        thread = new GifThread(loader);
        thread.start();
    }

    redraw();
}

@Override
public void dispose() {
    super.dispose();
    if(thread != null) thread.stopRunning();
}


private class GifThread extends Thread {

    private int imageNumber = 0;
    private ImageLoader loader = null;
    private boolean run = true;

    public GifThread(ImageLoader loader) {
        this.loader = loader;
    }

    public void run() {
        while(run) {
            int delayTime = loader.data[imageNumber].delayTime;
            try {
                Thread.sleep(delayTime * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(!GifCLabel.this.isDisposed()) {
                GifCLabel.this.getDisplay().asyncExec(new Runnable() {
                    public void run() {
                        if(!run){
                            return;
                        }
                        if(!GifCLabel.this.isDisposed()) {
                            imageNumber = imageNumber == loader.data.length - 1 ? 0 : imageNumber + 1;
                            if (!GifCLabel.this.image.isDisposed()) GifCLabel.this.image.dispose();
                            ImageData nextFrameData = loader.data[imageNumber];
                            GifCLabel.this.image = new Image(GifCLabel.this.getDisplay(), nextFrameData);
                            GifCLabel.this.redraw();
                        } else stopRunning();
                    }
                });
            } else stopRunning();
        } 
    }

    public void stopRunning() {
        run = false;
    }
}

public void setLeftMargin(int leftMargin) {
    checkWidget();
    if (this.leftMargin == leftMargin || leftMargin < 0) return;
    this.leftMargin = leftMargin;
    redraw();
}

public void setMargins (int leftMargin, int topMargin, int rightMargin, int bottomMargin) {
    checkWidget();
    this.leftMargin = Math.max(0, leftMargin);
    this.topMargin = Math.max(0, topMargin);
    this.rightMargin = Math.max(0, rightMargin);
    this.bottomMargin = Math.max(0, bottomMargin);
    redraw();
}

public void setRightMargin(int rightMargin) {
    checkWidget();
    if (this.rightMargin == rightMargin || rightMargin < 0) return;
    this.rightMargin = rightMargin;
    redraw();
}

public void setText(String text) {
    checkWidget();
    if (text == null) text = ""; //$NON-NLS-1$
    if (! text.equals(this.text)) {
        this.text = text;
        redraw();
    }
}

public void setToolTipText (String string) {
    super.setToolTipText (string);
    appToolTipText = super.getToolTipText();
}

public void setTopMargin(int topMargin) {
    checkWidget();
    if (this.topMargin == topMargin || topMargin < 0) return;
    this.topMargin = topMargin;
    redraw();
}

protected String shortenText(GC gc, String t, int width) {
    if (t == null) return null;
    int w = gc.textExtent(ELLIPSIS, DRAW_FLAGS).x;
    if (width<=w) return t;
    int l = t.length();
    int max = l/2;
    int min = 0;
    int mid = (max+min)/2 - 1;
    if (mid <= 0) return t;
    TextLayout layout = new TextLayout (getDisplay());
    layout.setText(t);
    mid = validateOffset(layout, mid);
    while (min < mid && mid < max) {
        String s1 = t.substring(0, mid);
        String s2 = t.substring(validateOffset(layout, l-mid), l);
        int l1 = gc.textExtent(s1, DRAW_FLAGS).x;
        int l2 = gc.textExtent(s2, DRAW_FLAGS).x;
        if (l1+w+l2 > width) {
            max = mid;          
            mid = validateOffset(layout, (max+min)/2);
        } else if (l1+w+l2 < width) {
            min = mid;
            mid = validateOffset(layout, (max+min)/2);
        } else {
            min = max;
        }
    }
    String result = mid == 0 ? t : t.substring(0, mid) + ELLIPSIS + t.substring(validateOffset(layout, l-mid), l);
    layout.dispose();
    return result;
}
int validateOffset(TextLayout layout, int offset) {
    int nextOffset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER);
    if (nextOffset != offset) return layout.getPreviousOffset(nextOffset, SWT.MOVEMENT_CLUSTER);
    return offset;
}
private String[] splitString(String text) {
    String[] lines = new String[1];
    int start = 0, pos;
    do {
        pos = text.indexOf('\n', start);
        if (pos == -1) {
            lines[lines.length - 1] = text.substring(start);
        } else {
            boolean crlf = (pos > 0) && (text.charAt(pos - 1) == '\r');
            lines[lines.length - 1] = text.substring(start, pos - (crlf ? 1 : 0));
            start = pos + 1;
            String[] newLines = new String[lines.length+1];
            System.arraycopy(lines, 0, newLines, 0, lines.length);
            lines = newLines;
        }
    } while (pos != -1);
    return lines;
}
}