package com.codeaffine.extras.jdt.internal.junitstatus;

import java.util.Objects;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;

import com.codeaffine.extras.jdt.internal.junitstatus.TextAnimation.TextAnimationPainter;

public class JUnitProgressBar extends Canvas implements TextAnimationPainter {

  private static final int ARC_SIZE = 3;
  private static final int DEFAULT_WIDTH = 160;
  private static final int DEFAULT_HEIGHT = 18;

  private final TextAnimation textAnimation;
  private String text;
  private int textAlignment;
  private int maximum;
  private int selection;
  private Color barColor;

  public JUnitProgressBar( Composite parent ) {
    super( parent, SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED | SWT.NO_FOCUS );
    text = "";
    textAlignment = SWT.LEFT;
    textAnimation = new TextAnimation( this, this );
    registerListeners();
  }

  public void setValues( String text, int textAlignment, Color barColor, int selection, int maximum ) {
    if( valuesChanged( text, textAlignment, barColor, selection, maximum ) ) {
      this.text = text;
      this.textAnimation.setText( text );
      this.textAlignment = textAlignment;
      this.barColor = barColor;
      this.selection = selection;
      this.maximum = maximum;
      redraw();
    }
  }

  public void setMaximum( int maximum ) {
    setValues( text, textAlignment, barColor, selection, maximum );
  }

  public int getMaximum() {
    return maximum;
  }

  public void setSelection( int selection ) {
    setValues( text, textAlignment, barColor, selection, maximum );
  }

  public int getSelection() {
    return selection;
  }

  public void setBarColor( Color barColor ) {
    setValues( text, textAlignment, barColor, selection, maximum );
  }

  public Color getBarColor() {
    return barColor;
  }

  public void setText( String text ) {
    setValues( text, textAlignment, barColor, selection, maximum );
  }

  public String getText() {
    return text;
  }

  public void setTextAlignment( int textAlignment ) {
    setValues( text, textAlignment, barColor, selection, maximum );
  }

  public int getTextAlignment() {
    return textAlignment;
  }

  @Override
  public void drawText( TextAnimation textAnimation ) {
    redraw();
  }

  @Override
  public Point computeSize( int wHint, int hHint, boolean changed ) {
    checkWidget();
    Point result = new Point( DEFAULT_WIDTH, DEFAULT_HEIGHT );
    if( wHint != SWT.DEFAULT ) {
      result.x = wHint;
    }
    if( hHint != SWT.DEFAULT ) {
      result.y = hHint;
    }
    return result;
  }

  private void registerListeners() {
    addListener( SWT.Resize, event -> redraw() );
    addListener( SWT.Paint, event -> paint( event.gc ) );
  }

  private void paint( GC gc ) {
    int style = ( maximum > 0 ? SWT.BORDER : SWT.NONE ) | textAlignment;
    String text = textAnimation.getAnimatedText();
    new Painter( gc, getClientArea(), text, barColor, getBarWidth(), style  ).paint();
  }

  int getBarWidth() {
    int result = selection;
    Rectangle clientArea = getClientArea();
    if( maximum > 0 ) {
      if( clientArea.width != 0 ) {
        result = Math.max( 0, selection * ( clientArea.width - 2 ) / maximum );
      }
    } else {
      result = 0;
    }
    return Math.min( clientArea.width - 2, result );
  }

  private boolean valuesChanged( String text, int textAlignment, Color barColor, int selection, int maximum ) {
    return !Objects.equals( this.text, text )
        || this.textAlignment != textAlignment
        || !Objects.equals( this.barColor, barColor )
        || this.selection != selection
        || this.maximum != maximum;
  }

  private static class Painter {
    private final GC gc;
    private final Rectangle clientArea;
    private final String text;
    private final Color barColor;
    private final int barWidth;
    private final int style;

    Painter( GC gc, Rectangle clientArea, String text, Color barColor, int barWidth, int style ) {
      this.gc = gc;
      this.clientArea = clientArea;
      this.text = text;
      this.barColor = barColor;
      this.barWidth = barWidth;
      this.style = style;
    }

    void paint() {
      prepareGC();
      drawBorder();
      drawBar();
      drawText();
    }

    private void prepareGC() {
      if( gc.getAdvanced() ) {
        gc.setTextAntialias( SWT.ON );
      }
      gc.fillRectangle( clientArea );
    }

    private void drawBorder() {
      if( ( style & SWT.BORDER ) != 0 ) {
        int x = clientArea.x;
        int y = clientArea.y;
        int width = clientArea.width - 1;
        int height = clientArea.height - 1 - 1;
        gc.setAlpha( 255 );
        gc.setForeground( getSystemColor( SWT.COLOR_WIDGET_NORMAL_SHADOW ) );
        gc.drawRoundRectangle( x, y, width, height, ARC_SIZE, ARC_SIZE );
      }
    }

    private void drawBar() {
      if( barColor != null ) {
        gc.setAlpha( 200 );
        gc.setBackground( barColor );
        gc.fillRoundRectangle( 1, 1, barWidth, clientArea.height - 2 - 1, ARC_SIZE, ARC_SIZE );
      }
    }

    private void drawText() {
      gc.setAlpha( 255 );
      Rectangle rect = insideBorderArea();
      Point textLocation = getTextLocation( rect );

      Color defaultForeground = getSystemColor( SWT.COLOR_WIDGET_FOREGROUND );
      drawText( textLocation, defaultForeground, null );
      Color barForeground = getBarForegroundColor();
      if( !equals( defaultForeground, barForeground ) ) {
        Rectangle clipping = new Rectangle( rect.x, rect.y, barWidth, rect.height );
        drawText( textLocation, barForeground, clipping );
      }
    }

    private void drawText( Point location, Color foreground, Rectangle clipping ) {
      Rectangle previousClipping = gc.getClipping();
      if( clipping != null ) {
        gc.setClipping( clipping );
      }
      gc.setForeground( foreground );
      gc.drawText( text, location.x, location.y, SWT.DRAW_TRANSPARENT );
      if( clipping != null ) {
        gc.setClipping( previousClipping );
      }
    }

    private Point getTextLocation( Rectangle clientArea ) {
      Point textSize = gc.textExtent( text );
      int x = 3;
      if( ( style & SWT.CENTER ) != 0 ) {
        x = ( clientArea.width - textSize.x ) / 2;
      }
      int y = ( clientArea.height - textSize.y ) / 2 + 1;
      return new Point( x, y );
    }

    private Color getBarForegroundColor() {
      Color result = getSystemColor( SWT.COLOR_WIDGET_FOREGROUND );
      if( barColor != null ) {
        int red = barColor.getRed();
        int green = barColor.getGreen();
        int blue = barColor.getBlue();
        double y = ( 299 * red + 587 * green + 114 * blue ) / 1000;
        result = y >= 128 ? getSystemColor( SWT.COLOR_BLACK ) : getSystemColor( SWT.COLOR_WHITE );
      }
      return result;
    }

    private Color getSystemColor( int id ) {
      return gc.getDevice().getSystemColor( id );
    }

    private Rectangle insideBorderArea() {
      return new Rectangle( clientArea.x + 1, clientArea.y + 1, clientArea.width - 2, clientArea.height - 2 );
    }

    private static boolean equals( Color color1, Color color2 ) {
      return color1.getRGB().equals( color2.getRGB() );
    }
  }

}