/*******************************************************************************
 * Copyright (c) 2010 Oak Ridge National Laboratory.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 ******************************************************************************/

package org.deidentifier.arx.gui.view.impl.common;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;

import org.deidentifier.arx.gui.view.SWTUtil;
import org.eclipse.draw2d.AbstractLayout;
import org.eclipse.draw2d.FigureListener;
import org.eclipse.draw2d.FigureUtilities;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.Polygon;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.nebula.visualization.internal.widgets.introspection.MeterIntrospector;
import org.eclipse.nebula.visualization.widgets.figureparts.RoundScale;
import org.eclipse.nebula.visualization.widgets.figureparts.RoundScaledRamp;
import org.eclipse.nebula.visualization.widgets.figures.AbstractRoundRampedFigure;
import org.eclipse.nebula.visualization.widgets.util.PointsUtil;
import org.eclipse.nebula.visualization.xygraph.util.XYGraphMediaFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;

/**
 * The figure of Express Meter. Adapted for ARX by fabian.prasser@gmail.com
 * 
 * @author Xihui Chen
 * @author Fabian Prasser
 */
public class ComponentMeterFigure extends AbstractRoundRampedFigure {

    static class Needle extends Polygon {
        public Needle() {
            setBackgroundColor(XYGraphMediaFactory.getInstance().getColor(XYGraphMediaFactory.COLOR_RED));
        }
        @Override
        protected void fillShape(Graphics g) {
            g.setAntialias(SWT.ON);
            super.fillShape(g);
        }
    }
    
    class XMeterLayout extends AbstractLayout {
        
        private static final int GAP_BTW_NEEDLE_SCALE = -5;
        
        int M = 0;
        
        /** Used as a constraint for the scale. */
        public static final String SCALE = "scale";   //$NON-NLS-1$
        /** Used as a constraint for the Needle. */
        public static final String NEEDLE = "needle"; //$NON-NLS-1$
        /** Used as a constraint for the Ramp */
        public static final String RAMP = "ramp";      //$NON-NLS-1$
        /** Used as a constraint for the value label*/
        public static final String VALUE_LABEL = "valueLabel";      //$NON-NLS-1$
        
        private RoundScale scale;
        private RoundScaledRamp ramp;
        private Polygon needle;
        private Label valueLabel;
        private PointList needlePoints = new PointList(new int[] {0,0,0,0,0,0});
        
        
        public void layout(IFigure container) {
            Rectangle area = container.getClientArea(); 
            // calculate a virtual area
            
            if(scale != null && scale.isDirty()) {
                M = Math.max(FigureUtilities.getTextWidth(
                        scale.format(scale.getRange().getLower()), scale.getFont()),
                        FigureUtilities.getTextWidth(
                        scale.format(scale.getRange().getUpper()), scale.getFont()))/2;
            }
            
            int h = area.height;
            int w = area.width;         
            int offsetY = 0;
            int offsetX = 0;
            if(h > HW_RATIO * (w - 2*M)) {
                h = (int) (HW_RATIO * (w - 2*M));
                offsetY = (area.height - h) / 2;
                if (offsetY > 10) {
                    offsetY -= 10;
                } else {
                    offsetY = 0;
                }
            } else {
                offsetX = ((int) (HW_RATIO * (w - 2*M)) - h);
            }
            
       
            double r = h / (1 - Math.sin(ALPHA) / 2);
            int x = (int) (area.x - r * (1.0 - Math.cos(ALPHA)) + M);
            int y = area.y;
                        
            area = new Rectangle(x + offsetX, y + offsetY, (int)(2*r), (int)(2*r));         
            Point center = area.getCenter();
            
            if(scale != null) {             
                scale.setBounds(area);
            }
            
            if(ramp != null && ramp.isVisible()) {
                Rectangle rampBounds = area.getCopy();
                ramp.setBounds(rampBounds.shrink(area.width/4 -ramp.getRampWidth(), area.height/4 - ramp.getRampWidth()));
            }
            
            if(valueLabel != null) {
                
                Dimension labelSize = valueLabel.getPreferredSize();
                
                int lY1 = area.y + area.height/2 -(scale.getInnerRadius() - area.height/5)/2 - labelSize.height/2;
                int lY2 = container.getClientArea().height - labelSize.height - 3;
                
                valueLabel.setBounds(new Rectangle(area.x + area.width/2 - labelSize.width/2,
                        Math.min(lY1, lY2),
                        labelSize.width, labelSize.height));
            }
            
            if(needle != null && scale != null) {
                needlePoints.setPoint (new Point(center.x + area.width/4, center.y - NEEDLE_WIDTH/2 + 3), 0);
                scale.getScaleTickMarks();
                needlePoints.setPoint(new Point(center.x + scale.getInnerRadius() - GAP_BTW_NEEDLE_SCALE, center.y), 1);
                needlePoints.setPoint(new Point(center.x + area.width/4, center.y + NEEDLE_WIDTH/2 - 3), 2);
    
                double valuePosition = 360 - scale.getValuePosition(getCoercedValue(), false);
                if(maximum > minimum){
                    if(value > maximum)
                        valuePosition += 8;
                    else if(value < minimum)
                        valuePosition -=8;
                }else{
                    if(value > minimum)
                        valuePosition -= 8;
                    else if(value < maximum)
                        valuePosition +=8;
                }
                needlePoints.setPoint(PointsUtil.rotate(needlePoints.getPoint(0), valuePosition, center), 0);
                needlePoints.setPoint(PointsUtil.rotate(needlePoints.getPoint(1), valuePosition, center), 1);
                needlePoints.setPoint(PointsUtil.rotate(needlePoints.getPoint(2), valuePosition, center),2);              
                needle.setPoints(needlePoints);         
            }        
        }


        @Override
        public void setConstraint(IFigure child, Object constraint) {
            if(constraint.equals(SCALE))
                scale = (RoundScale)child;
            else if (constraint.equals(RAMP))
                ramp = (RoundScaledRamp) child;
            else if (constraint.equals(NEEDLE))
                needle = (Polygon) child;
            else if (constraint.equals(VALUE_LABEL))
                valueLabel = (Label)child;
        }


        @Override
        protected Dimension calculatePreferredSize(IFigure container, int w,
                int h) {
            Insets insets = container.getInsets();
            Dimension d = new Dimension(container.getClientArea().width, container.getClientArea().height);
            d.expand(insets.getWidth(), insets.getHeight());
            return d;
        }       
    }
    
    private final static Font DEFAULT_LABEL_FONT = XYGraphMediaFactory.getInstance().getFont(
            new FontData("Arial", 10, SWT.BOLD));
    
    private Needle needle;
    
    private Label valueLabel;
    
    private final static double SPACE_ANGLE = 45;  
    
    public static final int NEEDLE_WIDTH = 16;   
    
    public final static double ALPHA = SPACE_ANGLE * Math.PI/180;
    public final static double HW_RATIO = (1- Math.sin(ALPHA)/2)/(2*Math.cos(ALPHA));
    public ComponentMeterFigure() {
        super();
        setTransparent(false);
        scale.setScaleLineVisible(false);
        
        ((RoundScale)scale).setStartAngle(180-SPACE_ANGLE);
        ((RoundScale)scale).setEndAngle(SPACE_ANGLE);
        ramp.setRampWidth(12);
        setLoColor(XYGraphMediaFactory.getInstance().getColor(XYGraphMediaFactory.COLOR_YELLOW));
        setHiColor(XYGraphMediaFactory.getInstance().getColor(XYGraphMediaFactory.COLOR_YELLOW));
        
        valueLabel = new Label();       
        valueLabel.setFont(DEFAULT_LABEL_FONT);
        needle = new Needle();
        needle.setFill(true);
        needle.setOutline(false);
        
        setLayoutManager(new XMeterLayout());
        add(ramp, XMeterLayout.RAMP);
        add(scale, XMeterLayout.SCALE);         
        add(needle, XMeterLayout.NEEDLE);
        add(valueLabel, XMeterLayout.VALUE_LABEL);
        
        addFigureListener(new FigureListener() {            
            public void figureMoved(IFigure source) {
                ramp.setDirty(true);
                revalidate();   
            }
        }); 
        
    }
    
    @Override
    public BeanInfo getBeanInfo() throws IntrospectionException {
        return new MeterIntrospector().getBeanInfo(this.getClass());
    }
    
    
    /**
     * @return color of the needle.
     */
    public Color getNeedleColor(){
        return needle.getBackgroundColor();
    }
    
    @Override
    public void setBounds(Rectangle rect) {
        
        super.setBounds(rect);
    }

    /**
     * @param needleColor the needleColor to set
     */
    public void setNeedleColor(Color needleColor) {
        needle.setBackgroundColor(needleColor);
    }
    
    @Override
    public void setShowMarkers(boolean showMarkers) {
        super.setShowMarkers(showMarkers);      
        ramp.setVisible(showMarkers);   
    }
    
    @Override
    public void setValue(double value) {
        super.setValue(value);
        valueLabel.setText(SWTUtil.getPrettyString(value) + "%");         
    }

    public void setValueLabelVisibility(boolean visible) {
        valueLabel.setVisible(visible);
    }
}