/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed 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 com.android.hierarchyviewerlib.ui;

import com.android.hierarchyviewerlib.models.PixelPerfectModel;
import com.android.hierarchyviewerlib.models.ViewNode;
import com.android.hierarchyviewerlib.models.PixelPerfectModel.IImageChangeListener;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;

public class PixelPerfect extends ScrolledComposite implements IImageChangeListener {
    private Canvas mCanvas;

    private PixelPerfectModel mModel;

    private Image mImage;

    private Color mCrosshairColor;

    private Color mMarginColor;

    private Color mBorderColor;

    private Color mPaddingColor;

    private int mWidth;

    private int mHeight;

    private Point mCrosshairLocation;

    private ViewNode mSelectedNode;

    private Image mOverlayImage;

    private double mOverlayTransparency;

    public PixelPerfect(Composite parent) {
        super(parent, SWT.H_SCROLL | SWT.V_SCROLL);
        mCanvas = new Canvas(this, SWT.NONE);
        setContent(mCanvas);
        setExpandHorizontal(true);
        setExpandVertical(true);
        mModel = PixelPerfectModel.getModel();
        mModel.addImageChangeListener(this);

        mCanvas.addPaintListener(mPaintListener);
        mCanvas.addMouseListener(mMouseListener);
        mCanvas.addMouseMoveListener(mMouseMoveListener);
        mCanvas.addKeyListener(mKeyListener);

        addDisposeListener(mDisposeListener);

        mCrosshairColor = new Color(Display.getDefault(), new RGB(0, 255, 255));
        mBorderColor = new Color(Display.getDefault(), new RGB(255, 0, 0));
        mMarginColor = new Color(Display.getDefault(), new RGB(0, 255, 0));
        mPaddingColor = new Color(Display.getDefault(), new RGB(0, 0, 255));

        imageLoaded();
    }

    private DisposeListener mDisposeListener = new DisposeListener() {
        @Override
        public void widgetDisposed(DisposeEvent e) {
            mModel.removeImageChangeListener(PixelPerfect.this);
            mCrosshairColor.dispose();
            mBorderColor.dispose();
            mPaddingColor.dispose();
        }
    };

    @Override
    public boolean setFocus() {
        return mCanvas.setFocus();
    }

    private MouseListener mMouseListener = new MouseListener() {

        @Override
        public void mouseDoubleClick(MouseEvent e) {
            // pass
        }

        @Override
        public void mouseDown(MouseEvent e) {
            handleMouseEvent(e);
        }

        @Override
        public void mouseUp(MouseEvent e) {
            handleMouseEvent(e);
        }

    };

    private MouseMoveListener mMouseMoveListener = new MouseMoveListener() {
        @Override
        public void mouseMove(MouseEvent e) {
            if (e.stateMask != 0) {
                handleMouseEvent(e);
            }
        }
    };

    private void handleMouseEvent(MouseEvent e) {
        synchronized (PixelPerfect.this) {
            if (mImage == null) {
                return;
            }
            int leftOffset = mCanvas.getSize().x / 2 - mWidth / 2;
            int topOffset = mCanvas.getSize().y / 2 - mHeight / 2;
            e.x -= leftOffset;
            e.y -= topOffset;
            e.x = Math.max(e.x, 0);
            e.x = Math.min(e.x, mWidth - 1);
            e.y = Math.max(e.y, 0);
            e.y = Math.min(e.y, mHeight - 1);
        }
        mModel.setCrosshairLocation(e.x, e.y);
    }

    private KeyListener mKeyListener = new KeyListener() {

        @Override
        public void keyPressed(KeyEvent e) {
            boolean crosshairMoved = false;
            synchronized (PixelPerfect.this) {
                if (mImage != null) {
                    switch (e.keyCode) {
                        case SWT.ARROW_UP:
                            if (mCrosshairLocation.y != 0) {
                                mCrosshairLocation.y--;
                                crosshairMoved = true;
                            }
                            break;
                        case SWT.ARROW_DOWN:
                            if (mCrosshairLocation.y != mHeight - 1) {
                                mCrosshairLocation.y++;
                                crosshairMoved = true;
                            }
                            break;
                        case SWT.ARROW_LEFT:
                            if (mCrosshairLocation.x != 0) {
                                mCrosshairLocation.x--;
                                crosshairMoved = true;
                            }
                            break;
                        case SWT.ARROW_RIGHT:
                            if (mCrosshairLocation.x != mWidth - 1) {
                                mCrosshairLocation.x++;
                                crosshairMoved = true;
                            }
                            break;
                    }
                }
            }
            if (crosshairMoved) {
                mModel.setCrosshairLocation(mCrosshairLocation.x, mCrosshairLocation.y);
            }
        }

        @Override
        public void keyReleased(KeyEvent e) {
            // pass
        }

    };

    private PaintListener mPaintListener = new PaintListener() {
        @Override
        public void paintControl(PaintEvent e) {
            synchronized (PixelPerfect.this) {
                e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                e.gc.fillRectangle(0, 0, mCanvas.getSize().x, mCanvas.getSize().y);
                if (mImage != null) {
                    // Let's be cool and put it in the center...
                    int leftOffset = mCanvas.getSize().x / 2 - mWidth / 2;
                    int topOffset = mCanvas.getSize().y / 2 - mHeight / 2;
                    e.gc.drawImage(mImage, leftOffset, topOffset);
                    if (mOverlayImage != null) {
                        e.gc.setAlpha((int) (mOverlayTransparency * 255));
                        int overlayTopOffset =
                                mCanvas.getSize().y / 2 + mHeight / 2
                                        - mOverlayImage.getBounds().height;
                        e.gc.drawImage(mOverlayImage, leftOffset, overlayTopOffset);
                        e.gc.setAlpha(255);
                    }

                    if (mSelectedNode != null) {
                        // If the screen is in landscape mode, the
                        // coordinates are backwards.
                        int leftShift = 0;
                        int topShift = 0;
                        int nodeLeft = mSelectedNode.left;
                        int nodeTop = mSelectedNode.top;
                        int nodeWidth = mSelectedNode.width;
                        int nodeHeight = mSelectedNode.height;
                        int nodeMarginLeft = mSelectedNode.marginLeft;
                        int nodeMarginTop = mSelectedNode.marginTop;
                        int nodeMarginRight = mSelectedNode.marginRight;
                        int nodeMarginBottom = mSelectedNode.marginBottom;
                        int nodePadLeft = mSelectedNode.paddingLeft;
                        int nodePadTop = mSelectedNode.paddingTop;
                        int nodePadRight = mSelectedNode.paddingRight;
                        int nodePadBottom = mSelectedNode.paddingBottom;
                        ViewNode cur = mSelectedNode;
                        while (cur.parent != null) {
                            leftShift += cur.parent.left - cur.parent.scrollX;
                            topShift += cur.parent.top - cur.parent.scrollY;
                            cur = cur.parent;
                        }

                        // Everything is sideways.
                        if (cur.width > cur.height) {
                            e.gc.setForeground(mPaddingColor);
                            e.gc.drawRectangle(leftOffset + mWidth - nodeTop - topShift - nodeHeight
                                    + nodePadBottom,
                                    topOffset + leftShift + nodeLeft + nodePadLeft, nodeHeight
                                            - nodePadBottom - nodePadTop, nodeWidth - nodePadRight
                                            - nodePadLeft);
                            e.gc.setForeground(mMarginColor);
                            e.gc.drawRectangle(leftOffset + mWidth - nodeTop - topShift - nodeHeight
                                    - nodeMarginBottom, topOffset + leftShift + nodeLeft
                                    - nodeMarginLeft,
                                    nodeHeight + nodeMarginBottom + nodeMarginTop, nodeWidth
                                            + nodeMarginRight + nodeMarginLeft);
                            e.gc.setForeground(mBorderColor);
                            e.gc.drawRectangle(
                                    leftOffset + mWidth - nodeTop - topShift - nodeHeight, topOffset
                                            + leftShift + nodeLeft, nodeHeight, nodeWidth);
                        } else {
                            e.gc.setForeground(mPaddingColor);
                            e.gc.drawRectangle(leftOffset + leftShift + nodeLeft + nodePadLeft,
                                    topOffset + topShift + nodeTop + nodePadTop, nodeWidth
                                            - nodePadRight - nodePadLeft, nodeHeight
                                            - nodePadBottom - nodePadTop);
                            e.gc.setForeground(mMarginColor);
                            e.gc.drawRectangle(leftOffset + leftShift + nodeLeft - nodeMarginLeft,
                                    topOffset + topShift + nodeTop - nodeMarginTop, nodeWidth
                                            + nodeMarginRight + nodeMarginLeft, nodeHeight
                                            + nodeMarginBottom + nodeMarginTop);
                            e.gc.setForeground(mBorderColor);
                            e.gc.drawRectangle(leftOffset + leftShift + nodeLeft, topOffset
                                    + topShift + nodeTop, nodeWidth, nodeHeight);
                        }
                    }
                    if (mCrosshairLocation != null) {
                        e.gc.setForeground(mCrosshairColor);
                        e.gc.drawLine(leftOffset, topOffset + mCrosshairLocation.y, leftOffset
                                + mWidth - 1, topOffset + mCrosshairLocation.y);
                        e.gc.drawLine(leftOffset + mCrosshairLocation.x, topOffset, leftOffset
                                + mCrosshairLocation.x, topOffset + mHeight - 1);
                    }
                }
            }
        }
    };

    private void doRedraw() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                mCanvas.redraw();
            }
        });
    }

    private void loadImage() {
        mImage = mModel.getImage();
        if (mImage != null) {
            mWidth = mImage.getBounds().width;
            mHeight = mImage.getBounds().height;
        } else {
            mWidth = 0;
            mHeight = 0;
        }
        setMinSize(mWidth, mHeight);
    }

    @Override
    public void imageLoaded() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    loadImage();
                    mCrosshairLocation = mModel.getCrosshairLocation();
                    mSelectedNode = mModel.getSelected();
                    mOverlayImage = mModel.getOverlayImage();
                    mOverlayTransparency = mModel.getOverlayTransparency();
                }
            }
        });
        doRedraw();
    }

    @Override
    public void imageChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    loadImage();
                }
            }
        });
        doRedraw();
    }

    @Override
    public void crosshairMoved() {
        synchronized (this) {
            mCrosshairLocation = mModel.getCrosshairLocation();
        }
        doRedraw();
    }

    @Override
    public void selectionChanged() {
        synchronized (this) {
            mSelectedNode = mModel.getSelected();
        }
        doRedraw();
    }

    // Note the syncExec and then synchronized... It avoids deadlock
    @Override
    public void treeChanged() {
        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    mSelectedNode = mModel.getSelected();
                }
            }
        });
        doRedraw();
    }

    @Override
    public void zoomChanged() {
        // pass
    }

    @Override
    public void overlayChanged() {
        synchronized (this) {
            mOverlayImage = mModel.getOverlayImage();
            mOverlayTransparency = mModel.getOverlayTransparency();
        }
        doRedraw();
    }

    @Override
    public void overlayTransparencyChanged() {
        synchronized (this) {
            mOverlayTransparency = mModel.getOverlayTransparency();
        }
        doRedraw();
    }
}
