// Copyright (c) 2010, Freddy Martens (http://atstechlab.wordpress.com), 
// MindTheRobot (http://mindtherobot.com/blog/)  and contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met:
//
//	* Redistributions of source code must retain the above copyright notice, 
//	  this list of conditions and the following disclaimer.
//	* Redistributions in binary form must reproduce the above copyright notice, 
//	  this list of conditions and the following disclaimer in the documentation 
//	  and/or other materials provided with the distribution.
//	* Neither the name of Ondrej Zara nor the names of its contributors may be used 
//	  to endorse or promote products derived from this software without specific 
//	  prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package nl.digitalthings.mebarista;

import nl.digitalthings.mebarista.R;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.content.res.TypedArray;

public final class Gauge extends View {

	private static final String TAG = Gauge.class.getSimpleName();
	
	// drawing tools
	private RectF rimRect;
	private Paint rimPaint;
	private Paint rimCirclePaint;
	
	private RectF faceRect;
	private Bitmap faceTexture;
	private Paint facePaint;
	private Paint rimShadowPaint;
	
	private Paint scalePaint;
	private RectF scaleRect;

	private RectF valueRect;
	private RectF rangeRect;

	private Paint rangeOkPaint;
	private Paint rangeWarningPaint;
	private Paint rangeErrorPaint;
	private Paint rangeAllPaint;
	
	private Paint valueOkPaint;
	private Paint valueWarningPaint;
	private Paint valueErrorPaint;
	private Paint valueAllPaint;
		
	private Paint unitPaint;	
	private Path  unitPath;
	private RectF unitRect;

	private Paint lowerTitlePaint;	
	private Paint upperTitlePaint;	
	private Path  lowerTitlePath;
	private Path  upperTitlePath;
	private RectF titleRect;

	private Paint handPaint;
	private Path handPath;

	private Paint handScrewPaint;
	
	private Paint backgroundPaint; 
	// end drawing tools
	
	private Bitmap background; // holds the cached static part
	
	// scale configuration
	// Values passed as property. Defaults are set here.
	private boolean showHand                 = true;
	private boolean showGauge                = false;
	private boolean showRange                = false;
	
	private int totalNotches                 = 120; // Total number of notches on the scale. 
	private int incrementPerLargeNotch       = 10;
	private int incrementPerSmallNotch       = 2;

	private int scaleColor                   = 0x9f004d0f;
	private int scaleCenterValue             = 0; // the one in the top center (12 o'clock), this corresponds with -90 degrees
	private int scaleMinValue                = -90;
	private int scaleMaxValue                = 120;

    private int angleMinValue                = -120;
    private int angleMaxValue                = 120;

    private float degreeMinValue             = 0;
	private float degreeMaxValue             = 0;

	private int rangeOkColor                 = 0x9f00ff00;
	private int rangeOkMinValue              = scaleMinValue;
	private int rangeOkMaxValue              = 45;
	private float degreeOkMinValue           = 0;
	private float degreeOkMaxValue           = 0;

	private int rangeWarningColor            = 0x9fff8800;
	private int rangeWarningMinValue         = rangeOkMaxValue;
	private int rangeWarningMaxValue         = 80;
	private float degreeWarningMinValue      = 0;
	private float degreeWarningMaxValue      = 0;

	private int rangeErrorColor              = 0x9fff0000;
	private int rangeErrorMinValue           = rangeWarningMaxValue;
	private int rangeErrorMaxValue           = 120;
	private float degreeErrorMinValue        = 0;
	private float degreeErrorMaxValue        = 0;
	
	private String lowerTitle                = "www.ats-global.com";
	private String upperTitle                = "Visit http://atstechlab.wordpress.com";
	private String unitTitle                 = "\u2103";

	// Fixed values.
	private static float scalePosition = 0.10f;  // The distance from the rim to the scale
	private static float valuePosition = 0.285f; // The distance from the rim to the ranges
	private static float rangePosition = 0.122f; // The distance from the rim to the ranges
	private static float titlePosition = 0.145f; // The Distance from the rim to the titles
	private static float unitPosition  = 0.300f; // The distance from the rim to the unit
	private static float rimSize       = 0.02f;

	private  float degreesPerNotch      = 360.0f/totalNotches;
	private static final int centerDegrees   =  -90; // the one in the top center (12 o'clock), this corresponds with -90 degrees

	// hand dynamics 
	private boolean dialInitialized         = false;
	private float currentValue              = scaleCenterValue;
	private float targetValue               = scaleCenterValue;
	private float dialVelocity              = 0.0f;
	private float dialAcceleration          = 0.0f;
	private long lastDialMoveTime           = -1L;
	
	
	public Gauge(Context context) {
		super(context);
		init(context, null);
	}

	public Gauge(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context, attrs);
	}

	public Gauge(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init(context, attrs);
	}
	
	@Override
	protected void onAttachedToWindow() {
		super.onAttachedToWindow();
	}

	@Override
	protected void onDetachedFromWindow() {
		super.onDetachedFromWindow();
	}
	
	@Override
	protected void onRestoreInstanceState(Parcelable state) {
		Bundle bundle = (Bundle) state;
		Parcelable superState = bundle.getParcelable("superState");
		super.onRestoreInstanceState(superState);
		
		dialInitialized  = bundle.getBoolean("dialInitialized");
		currentValue     = bundle.getFloat("currentValue");
		targetValue       = bundle.getFloat("targetValue");
		dialVelocity     = bundle.getFloat("dialVelocity");
		dialAcceleration = bundle.getFloat("dialAcceleration");
		lastDialMoveTime = bundle.getLong("lastDialMoveTime");
	}

	@Override
	protected Parcelable onSaveInstanceState() {
		Parcelable superState = super.onSaveInstanceState();
		
		Bundle state = new Bundle();
		state.putParcelable("superState", superState);
		state.putBoolean("dialInitialized", dialInitialized);
		state.putFloat("currentValue", currentValue);
		state.putFloat("targetValue", targetValue);
		state.putFloat("dialVelocity", dialVelocity);
		state.putFloat("dialAcceleration", dialAcceleration);
		state.putLong("lastDialMoveTime", lastDialMoveTime);
		return state;
	}

	private void init(Context context, AttributeSet attrs) {
		// Get the properties from the resource file.
		// setLayerType(View.LAYER_TYPE_SOFTWARE, null);

		if (context != null && attrs != null){
			TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Dial);
			showRange              = a.getBoolean(R.styleable.Dial_showRange,          showRange);
			showGauge              = a.getBoolean(R.styleable.Dial_showGauge,          showGauge);
			//showHand               = a.getBoolean(R.styleable.Dial_showHand,           showHand);

			totalNotches           = a.getInt(R.styleable.Dial_totalNotches,           totalNotches);
			incrementPerLargeNotch = a.getInt(R.styleable.Dial_incrementPerLargeNotch, incrementPerLargeNotch);
			incrementPerSmallNotch = a.getInt(R.styleable.Dial_incrementPerSmallNotch, incrementPerSmallNotch);
			scaleCenterValue       = a.getInt(R.styleable.Dial_scaleCenterValue,       scaleCenterValue);
			scaleColor             = a.getInt(R.styleable.Dial_scaleColor,             scaleColor);
			scaleMinValue          = a.getInt(R.styleable.Dial_scaleMinValue,          scaleMinValue);
			scaleMaxValue          = a.getInt(R.styleable.Dial_scaleMaxValue,          scaleMaxValue);
            angleMinValue          = a.getInt(R.styleable.Dial_angleMinValue,          angleMinValue);
            angleMaxValue          = a.getInt(R.styleable.Dial_angleMaxValue,          angleMaxValue);

            rangeOkColor           = a.getInt(R.styleable.Dial_rangeOkColor,           rangeOkColor);
			rangeOkMinValue        = a.getInt(R.styleable.Dial_rangeOkMinValue,        rangeOkMinValue);
			rangeOkMaxValue        = a.getInt(R.styleable.Dial_rangeOkMaxValue,        rangeOkMaxValue);
			rangeWarningColor      = a.getInt(R.styleable.Dial_rangeWarningColor,      rangeWarningColor);
			rangeWarningMinValue   = a.getInt(R.styleable.Dial_rangeWarningMinValue,   rangeWarningMinValue);
			rangeWarningMaxValue   = a.getInt(R.styleable.Dial_rangeWarningMaxValue,   rangeWarningMaxValue);
			rangeErrorColor        = a.getInt(R.styleable.Dial_rangeErrorColor,        rangeErrorColor);
			rangeErrorMinValue     = a.getInt(R.styleable.Dial_rangeErrorMinValue,     rangeErrorMinValue);
			rangeErrorMaxValue     = a.getInt(R.styleable.Dial_rangeErrorMaxValue,     rangeErrorMaxValue);
			String unitTitle       = a.getString(R.styleable.Dial_unitTitle);
			String lowerTitle      = a.getString(R.styleable.Dial_lowerTitle);
			String upperTitle      = a.getString(R.styleable.Dial_upperTitle);
			if (unitTitle != null) this.unitTitle = unitTitle;
			if (lowerTitle != null) this.lowerTitle = lowerTitle;
			if (upperTitle != null) this.upperTitle = upperTitle;
		}
		degreeMinValue        = valueToAngle(scaleMinValue)        + centerDegrees;
		degreeMaxValue        = valueToAngle(scaleMaxValue)        + centerDegrees;
		degreeOkMinValue      = valueToAngle(rangeOkMinValue)      + centerDegrees;
		degreeOkMaxValue      = valueToAngle(rangeOkMaxValue)      + centerDegrees;
		degreeWarningMinValue = valueToAngle(rangeWarningMinValue) + centerDegrees;
		degreeWarningMaxValue = valueToAngle(rangeWarningMaxValue) + centerDegrees;
		degreeErrorMinValue   = valueToAngle(rangeErrorMinValue)   + centerDegrees;
		degreeErrorMaxValue   = valueToAngle(rangeErrorMaxValue)   + centerDegrees;

        degreesPerNotch      = ( angleMaxValue - angleMinValue )*1.0f / totalNotches;

        // initDrawingTools();
	}

	private void initDrawingTools() {
		// All drawing has been scaled to actual pixels because of :
		// https://groups.google.com/forum/#!msg/android-developers/eTxV4KPy1G4/tAe2zUPCjMcJ

		int w = getWidth(), h = getHeight();
		rimRect = new RectF(0.0f, 0.0f, getWidth() /* 1.0f */, getHeight() /* 1.0f */);

		scalePosition = 0.10f * getWidth();  // The distance from the rim to the scale
		valuePosition = 0.285f * getWidth(); // The distance from the rim to the ranges
		rangePosition = 0.122f * getWidth(); // The distance from the rim to the ranges
		titlePosition = 0.145f * getWidth(); // The Distance from the rim to the titles
		unitPosition  = 0.300f * getWidth(); // The distance from the rim to the unit
		rimSize       = 0.02f * getWidth();


		faceRect = new RectF();
		faceRect.set(rimRect.left  + rimSize, rimRect.top    + rimSize, 
			         rimRect.right - rimSize, rimRect.bottom - rimSize);		

		scaleRect = new RectF();
		scaleRect.set(faceRect.left + scalePosition, faceRect.top + scalePosition,
					  faceRect.right - scalePosition, faceRect.bottom - scalePosition);

		rangeRect = new RectF();
		rangeRect.set(faceRect.left  + rangePosition, faceRect.top    + rangePosition,
				      faceRect.right - rangePosition, faceRect.bottom - rangePosition);
		
		valueRect = new RectF();
		valueRect.set(faceRect.left  + valuePosition, faceRect.top    + valuePosition,
				      faceRect.right - valuePosition, faceRect.bottom - valuePosition);

		titleRect = new RectF();
		titleRect.set(faceRect.left  + titlePosition, faceRect.top    + titlePosition,
					  faceRect.right - titlePosition, faceRect.bottom - titlePosition);

		unitRect = new RectF();
		unitRect.set(faceRect.left  + unitPosition, faceRect.top    + unitPosition,
					 faceRect.right - unitPosition, faceRect.bottom - unitPosition);
		
		faceTexture = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.plastic);
		BitmapShader paperShader = new BitmapShader(faceTexture, 
												    Shader.TileMode.MIRROR, 
												    Shader.TileMode.MIRROR);
		Matrix paperMatrix = new Matrix();
		paperMatrix.setScale(1.0f / faceTexture.getWidth(),
				1.0f / faceTexture.getHeight());

		paperShader.setLocalMatrix(paperMatrix);

		rimShadowPaint = new Paint();
		rimShadowPaint.setShader(new RadialGradient(0.5f * getWidth(), 0.5f * getHeight(), faceRect.width() / 2.0f,
				new int[]{0x00000000, 0x00000500, 0x50000500},
				new float[]{0.96f, 0.96f, 0.99f},
				Shader.TileMode.MIRROR));
		rimShadowPaint.setStyle(Paint.Style.FILL);

		// the linear gradient is a bit skewed for realism
		rimPaint = new Paint();
		rimPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
		rimPaint.setShader(new LinearGradient(0.40f * getWidth(), 0.0f, 0.60f * getWidth(), 1.0f * getHeight(),
										   Color.rgb(0xf0, 0xf5, 0xf0),
										   Color.rgb(0x30, 0x31, 0x30),
										   Shader.TileMode.CLAMP));		

		rimCirclePaint = new Paint();
		//rimCirclePaint.setAntiAlias(true);
		rimCirclePaint.setStyle(Paint.Style.STROKE);
		rimCirclePaint.setColor(Color.argb(0x4f, 0x33, 0x36, 0x33));
		rimCirclePaint.setStrokeWidth(0.005f * getWidth() );
		
		facePaint = new Paint();
		facePaint.setFilterBitmap(true);
		facePaint.setStyle(Paint.Style.FILL);
		facePaint.setShader(paperShader);

		scalePaint = new Paint();
		scalePaint.setStyle(Paint.Style.STROKE);
		scalePaint.setColor(scaleColor);
		scalePaint.setStrokeWidth(0.005f * getWidth() );
		scalePaint.setAntiAlias(true);
		//
		// scalePaint.setTextSize(0.045f);

        // http://stackoverflow.com/questions/13974129/android-4-2-1-wrong-character-kerning-spacing
        scalePaint.setTextSize( 0.045f * getWidth() );
        scalePaint.setLinearText(true);

        scalePaint.setTypeface(Typeface.SANS_SERIF);
		scalePaint.setTextScaleX(0.8f);
		scalePaint.setTextAlign(Paint.Align.CENTER);
		
		rangeOkPaint = new Paint();
		rangeOkPaint.setStyle(Paint.Style.STROKE);
		rangeOkPaint.setColor(rangeOkColor);
		rangeOkPaint.setStrokeWidth(0.012f * getWidth() );
		rangeOkPaint.setAntiAlias(true);

		rangeWarningPaint = new Paint();
		rangeWarningPaint.setStyle(Paint.Style.STROKE);
		rangeWarningPaint.setColor(rangeWarningColor);
		rangeWarningPaint.setStrokeWidth(0.012f * getWidth() );
		rangeWarningPaint.setAntiAlias(true);

		rangeErrorPaint = new Paint();
		rangeErrorPaint.setStyle(Paint.Style.STROKE);
		rangeErrorPaint.setColor(rangeErrorColor);
		rangeErrorPaint.setStrokeWidth(0.012f * getWidth() );
		rangeErrorPaint.setAntiAlias(true);

		rangeAllPaint = new Paint();
		rangeAllPaint.setStyle(Paint.Style.STROKE);
		rangeAllPaint.setColor(0xcff8f8f8);
		rangeAllPaint.setStrokeWidth(0.012f * getWidth() );
		rangeAllPaint.setAntiAlias(true);
		rangeAllPaint.setShadowLayer(0.005f, -0.002f, -0.002f, 0x7f000000);

		valueOkPaint = new Paint();
		valueOkPaint.setStyle(Paint.Style.STROKE);
		valueOkPaint.setColor(rangeOkColor);
		valueOkPaint.setStrokeWidth(0.20f * getWidth() );
		valueOkPaint.setAntiAlias(true);

		valueWarningPaint = new Paint();
		valueWarningPaint.setStyle(Paint.Style.STROKE);
		valueWarningPaint.setColor(rangeWarningColor);
		valueWarningPaint.setStrokeWidth(0.20f * getWidth() );
		valueWarningPaint.setAntiAlias(true);

		valueErrorPaint = new Paint();
		valueErrorPaint.setStyle(Paint.Style.STROKE);
		valueErrorPaint.setColor(rangeErrorColor);
		valueErrorPaint.setStrokeWidth(0.20f * getWidth() );
		valueErrorPaint.setAntiAlias(true);

		valueAllPaint = new Paint();
		valueAllPaint.setStyle(Paint.Style.STROKE);
		valueAllPaint.setColor(0xcff8f8f8);
		valueAllPaint.setStrokeWidth(0.20f * getWidth() );
		valueAllPaint.setAntiAlias(true);
		valueAllPaint.setShadowLayer(0.005f, -0.002f, -0.002f, 0x7f000000);

		unitPaint = new Paint();
		unitPaint.setColor(0xaf0c0c0c);
		unitPaint.setAntiAlias(true);
		unitPaint.setTypeface(Typeface.DEFAULT_BOLD);
		unitPaint.setTextAlign(Paint.Align.CENTER);
		unitPaint.setTextSize(0.05f * getWidth() );
		unitPaint.setTextScaleX(0.8f);

		upperTitlePaint = new Paint();
		upperTitlePaint.setColor(0xaf0c0c0c);
		upperTitlePaint.setAntiAlias(true);
		upperTitlePaint.setTypeface(Typeface.DEFAULT_BOLD);
		upperTitlePaint.setTextAlign(Paint.Align.CENTER);
		upperTitlePaint.setTextSize(0.09f * getWidth() );
		upperTitlePaint.setTextScaleX(0.8f);

		lowerTitlePaint = new Paint();
		lowerTitlePaint.setColor(0xaf0c0c0c);
		lowerTitlePaint.setAntiAlias(true);
		lowerTitlePaint.setTypeface(Typeface.DEFAULT_BOLD);
		lowerTitlePaint.setTextAlign(Paint.Align.CENTER);
		lowerTitlePaint.setTextSize(0.04f * getWidth() );
		lowerTitlePaint.setTextScaleX(0.8f);


		handPaint = new Paint();
		handPaint.setAntiAlias(true);

		handPaint.setColor(0xff392f2c);
		//handPaint.setShadowLayer(0.01f, -0.005f, -0.005f, 0x7f000000);
		handPaint.setStyle(Paint.Style.FILL);
        handPaint.setStrokeWidth(0.1f);
        //handPaint.setColor(android.graphics.Color.RED);
        //handPaint.setStyle(Paint.Style.STROKE);

		handScrewPaint = new Paint();

		handScrewPaint.setAntiAlias(true);
		//handScrewPaint.setColor(0xff493f3c);
		handScrewPaint.setColor(0xffff3f3c);
		handScrewPaint.setStyle(Paint.Style.FILL);
		
		backgroundPaint = new Paint();
		backgroundPaint.setFilterBitmap(true);

		unitPath = new Path();
		unitPath.addArc(unitRect, 180.0f, 180.0f);

		upperTitlePath = new Path();
		upperTitlePath.addArc(titleRect, 180.0f, 180.0f);

		lowerTitlePath = new Path();
		lowerTitlePath.addArc(titleRect, -180.0f, -180.0f);

		// The hand is drawn with the tip facing up. That means when the image is not rotated, the tip 
		// faces north. When the the image is rotated -90 degrees, the tip is facing west and so on.
		handPath = new Path();
		// handPath.setFillType(Path.FillType.EVEN_ODD);//   X      Y

		handPath.moveTo(0.5f * w, (0.5f + 0.2f) * h);                                 // 0.500, 0.700

		handPath.lineTo( ( 0.5f - 0.010f ) * w, ( 0.5f + 0.2f - 0.007f ) * h);               // 0.490, 0.630

		handPath.lineTo( ( 0.5f - 0.002f ) * w, ( 0.5f - 0.40f ) * h);                       // 0.498, 0.100

		handPath.lineTo( ( 0.5f + 0.002f ) * w, ( 0.5f - 0.40f ) * h);                       // 0.502, 0.100

		handPath.lineTo((0.5f + 0.010f) * w, (0.5f + 0.2f - 0.007f) * h);               // 0.510, 0.630

		//handPath.lineTo(0.5f * w, 0.5f + 0.2f * h);                                 // 0.500, 0.700

		handPath.addCircle(0.5f * w, 0.5f * h, 0.025f * w, Path.Direction.CW);

		handPath.close();

    }

    @Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		//Log.d(TAG, "Width spec: " + MeasureSpec.toString(widthMeasureSpec));
		//Log.d(TAG, "Height spec: " + MeasureSpec.toString(heightMeasureSpec));
		
		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		
		int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		int heightSize = MeasureSpec.getSize(heightMeasureSpec);
		
		int chosenWidth = chooseDimension(widthMode, widthSize);
		int chosenHeight = chooseDimension(heightMode, heightSize);
		
		int chosenDimension = Math.min(chosenWidth, chosenHeight);
		
		setMeasuredDimension(chosenDimension, chosenDimension);
	}
	
	private int chooseDimension(int mode, int size) {
		if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
			return size;
		} else { // (mode == MeasureSpec.UNSPECIFIED)
			return getPreferredSize();
		} 
	}
	
	// in case there is no size specified
	private int getPreferredSize() {
		return 250;
	}

	private void drawRim(Canvas canvas) {
		// first, draw the metallic body
		canvas.drawOval(rimRect, rimPaint);
		// now the outer rim circle
		canvas.drawOval(rimRect, rimCirclePaint);
	}
	
	private void drawFace(Canvas canvas) {		
		canvas.drawOval(faceRect, facePaint);
		// draw the inner rim circle

		// was: canvas.drawOval(faceRect, rimCirclePaint);
		canvas.drawOval(faceRect, rimCirclePaint);

		// draw the rim shadow inside the face
		canvas.drawOval(faceRect, rimShadowPaint); // boosdoener
	}


	private void drawBackground(Canvas canvas) {
		if (background == null) {
			Log.w(TAG, "Background not created");
		} else {
			canvas.drawBitmap(background, 0, 0, backgroundPaint);
		}
	}

    private void fa( Canvas canvas, String text, Paint p, float x, float y ) {
        Path mTextOutlinePath = new Path();

        // adjust the x,y text origin to your personal needs
        p.getTextPath(text, 0, text.length(), x /* getPaddingLeft() */, y /* getBaseline() */, mTextOutlinePath);
		canvas.drawPath(mTextOutlinePath, p);
        // canvas.drawPath(mTextOutlinePath, mStrokePaint);

    }
	private void drawScale(Canvas canvas) {
		// Draw the circle
		canvas.drawOval(scaleRect, scalePaint);

		for (int i = 0; i <= totalNotches; ++i) {

			canvas.save(Canvas.MATRIX_SAVE_FLAG);
			canvas.rotate(degreeOkMinValue + 90 + degreesPerNotch * i, 0.5f * getWidth(), 0.5f * getHeight());

			float y1 = scaleRect.top;
			float y2 = y1 - 0.015f * getHeight();
			float y3 = y1 - 0.025f * getHeight();
			
			int value = notchToValue(i);

			if (i % (incrementPerLargeNotch/incrementPerSmallNotch) == 0) {
				/* if (value >= scaleMinValue && value <= scaleMaxValue) { */
					// draw a nick
					canvas.drawLine(0.5f * getWidth(), y1, 0.5f * getWidth(), y3, scalePaint);

					String valueString = Integer.toString(value);

                    canvas.drawText(valueString, 0.5f * getWidth(), y3 - 0.015f * getWidth(), scalePaint);

				/* } */
			}
			else{
				/* if (value >= scaleMinValue && value <= scaleMaxValue) { */
					// draw a nick
					canvas.drawLine(0.5f * getWidth(), y1, 0.5f * getWidth(), y2, scalePaint);
				/* } */
			}

			canvas.restore();
		}

	}
	
	private void drawScaleRanges(Canvas canvas) {

		canvas.drawArc(rangeRect, degreeMinValue,        degreeMaxValue -        degreeMinValue, false, rangeAllPaint);
		canvas.drawArc(rangeRect, degreeOkMinValue, degreeOkMaxValue - degreeOkMinValue, false, rangeOkPaint);
		canvas.drawArc(rangeRect, degreeWarningMinValue, degreeWarningMaxValue - degreeWarningMinValue, false, rangeWarningPaint);
		canvas.drawArc(rangeRect, degreeErrorMinValue, degreeErrorMaxValue - degreeErrorMinValue, false, rangeErrorPaint);

	}

	private void drawTitle(Canvas canvas) {
		// Use a vertical offset when printing the upper title. The upper and lower title
		// use the same rectangular but the spacing  between the title and the ranges
		// is not equal for the upper and lower title and therefore, the upper title is 
		// moved downwards.
		canvas.drawTextOnPath(upperTitle, upperTitlePath, 0.0f, 0.02f * getHeight(), upperTitlePaint);
		canvas.drawTextOnPath(lowerTitle, lowerTitlePath, 0.0f, 0.0f,  lowerTitlePaint);				
		canvas.drawTextOnPath(unitTitle,  unitPath,       0.0f, 0.0f,  unitPaint);				
	}

	int centerx, centery;

	private void drawHand(Canvas canvas) {
		if (true || dialInitialized) {

			float angle = valueToAngle(currentValue );
         	canvas.save(Canvas.MATRIX_SAVE_FLAG);
			canvas.rotate(angle, centerx, centery);
			canvas.drawPath(handPath, handPaint);
			canvas.restore();
			
			canvas.drawCircle(0.5f * getWidth() , 0.5f * getHeight() , 0.01f * getWidth(), handScrewPaint);
		}
	}
	
	private void drawGauge(Canvas canvas) {
		if (dialInitialized) {
			// When currentValue is not rotated, the tip of the hand points
			// to n -90 degrees.
			float angle = valueToAngle(currentValue) - 90;
	
			if(targetValue <= rangeOkMaxValue){
				canvas.drawArc(valueRect, degreeMinValue, angle - degreeMinValue, false, valueOkPaint);
			}
			if((targetValue > rangeOkMaxValue) && (targetValue <= rangeWarningMaxValue)){
				canvas.drawArc(valueRect, degreeMinValue, degreeOkMaxValue - degreeMinValue, false, valueOkPaint);
				canvas.drawArc(valueRect, degreeWarningMinValue, angle - degreeWarningMinValue, false, valueWarningPaint);
			}
			if((targetValue > rangeWarningMaxValue) && (targetValue <= rangeErrorMaxValue)){
				canvas.drawArc(valueRect, degreeMinValue, degreeOkMaxValue - degreeMinValue, false, valueOkPaint);
				canvas.drawArc(valueRect, degreeWarningMinValue, degreeWarningMaxValue - degreeWarningMinValue, false, valueWarningPaint);
				canvas.drawArc(valueRect, degreeErrorMinValue, angle - degreeErrorMinValue, false, valueErrorPaint);
			}
		}
	}

	private void drawBezel(Canvas canvas) {
		// Draw the bevel in which the value is draw.
		canvas.save(Canvas.MATRIX_SAVE_FLAG);
		canvas.drawArc(valueRect, degreeMinValue, degreeMaxValue - degreeMinValue, false, valueAllPaint);
		canvas.restore();		
	}

	/* Translate a notch to a value for the scale.
	 * The notches are evenly spread across the scale, half of the notches on the left hand side
	 * and the other half on the right hand side.
	 * The raw value calculation uses a constant so that each notch represents a value n + 2.
	 */
	private int notchToValue(int notch) {
		//int rawValue = ((notch < totalNotches / 2) ? notch : (notch - totalNotches)) * incrementPerSmallNotch;
		//int shiftedValue = rawValue + scaleCenterValue;
		//return shiftedValue;
        
        return notch * ( scaleMaxValue - scaleMinValue ) / totalNotches;
	}
	
	private float valueToAngle(float value) {
		// scaleCenterValue represents an angle of -90 degrees.


		//return (value - scaleCenterValue) / 2.0f * degreesPerNotch;


        // jaja

        float frac = ( value - scaleMinValue ) / ( scaleMaxValue - scaleMinValue );

        return angleMinValue + frac * ( angleMaxValue - angleMinValue );

        //return degreeOkMinValue - ( value / scaleMaxValue ) * ( degreeOkMinValue + 90 );
	}

    private float valueToAngle2(float value) {
		// scaleCenterValue represents an angle of -90 degrees.
        // return (value - scaleCenterValue) / 2.0f * degreesPerNotch;
        return valueToAngle(value);

        //return degreeOkMinValue - ( value / scaleMaxValue ) * 2 * ( degreeOkMinValue + 90 );
    }

	// Canvas backgroundCanvas ;

	@Override
	protected void onDraw(Canvas canvas) {

        drawBackground(canvas);

		// Draw the needle using the updated value
		if (showHand){
		drawHand( canvas );

		}

		// Calculate a new current value.
		calculateCurrentValue();

	}



	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		// Log.d(TAG, "Size changed to " + w + "x" + h);

		initDrawingTools();
		regenerateBackground();

		centerx = w / 2;
		centery = h / 2;
	}
	
	private void regenerateBackground() {
		// free the old bitmap
		if (background != null) {
			background.recycle();
		}
		
		background = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
		Canvas backgroundCanvas = new Canvas(background);

		drawRim(backgroundCanvas);
		drawFace(backgroundCanvas);

		drawScale(backgroundCanvas);
		if (showRange){
			drawScaleRanges(backgroundCanvas);
		}
		if (showGauge) {
			drawBezel(backgroundCanvas);
		}

		drawTitle(backgroundCanvas);		
	}

	// Move the hand slowly to the new position.
	private void calculateCurrentValue() {
		if (!(Math.abs(currentValue - targetValue) > 0.01f)) {
			return;
		}
		
		if (lastDialMoveTime != -1L) {
			long currentTime = System.currentTimeMillis();
			float delta = (currentTime - lastDialMoveTime) / 1000.0f;

			float direction = Math.signum(dialVelocity);
			if (Math.abs(dialVelocity) < 90.0f) {
				dialAcceleration = 5.0f * (targetValue - currentValue);
			} else {
				dialAcceleration = 0.0f;
			}
			currentValue += dialVelocity * delta;
			dialVelocity += dialAcceleration * delta;
			if ((targetValue - currentValue) * direction < 0.01f * direction) {
				currentValue = targetValue;
				dialVelocity = 0.0f;
				dialAcceleration = 0.0f;
				lastDialMoveTime = -1L;
			} else {
				lastDialMoveTime = System.currentTimeMillis();				
			}
			invalidate();
		} else {
			lastDialMoveTime = System.currentTimeMillis();
			calculateCurrentValue();
		}
	}
	
	public void setValue(float value) {
		if      (value < scaleMinValue) value = scaleMinValue;
		else if (value > scaleMaxValue) value = scaleMaxValue;

		targetValue = value;
		dialInitialized = true;
		
		invalidate(); // forces onDraw() to be called.
	}

	public float getValue() {
		return targetValue;
	}

}
