/*  Copyright (C) 2013  Two Big Ears Ltd.
 
	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.
	
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	
	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the Free Software Foundation, Inc.,
	51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package com.twobigears.circlesynth;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.regex.Pattern;

import org.puredata.android.io.AudioParameters;
import org.puredata.android.service.PdService;
import org.puredata.android.utils.PdUiDispatcher;
import org.puredata.core.PdBase;
import org.puredata.core.PdListener;
import org.puredata.core.utils.IoUtils;

import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PGraphics;
import processing.core.PImage;
import android.app.DialogFragment;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.AssetManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Environment;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.widget.Toast;

import com.google.analytics.tracking.android.EasyTracker;
import com.google.analytics.tracking.android.Tracker;
import com.twobigears.circlesynth.BpmPicker.OnBpmChangedListener;
import com.twobigears.circlesynth.RecordDialog.OnRecordingListener;

public class SynthCircle extends PApplet implements OnBpmChangedListener,
		OnSharedPreferenceChangeListener, SensorEventListener,
		OnRecordingListener {

	public static final String TAG = "CircleSynth";

	Context context;
	SharedPreferences prefs;

	protected PdUiDispatcher dispatcher;
	private Tracker tracker;

	public int sketchWidth() {
		return displayWidth;
	}

	public int sketchHeight() {
		return displayHeight;
	}

	public String sketchRenderer() {
		return OPENGL;
	}

	private PdService pdService = null;
	float pd = 0;

	ArrayList<Dot> dots;
	ArrayList<String> stored;
	FileDialog fileDialog;
	String fName;

	DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance();

	public int maxCircle = 10;
	String CHECK[] = null;
	String SAVE[] = null;
	String baseDir = Environment.getExternalStorageDirectory()
			.getAbsolutePath();

	boolean fxCheck = false;

	private SensorManager mSensorManager;
	private Sensor mAccelerometer;
	boolean accel;
	float currentAccelx = 0;
	float currentAccely = 0;
	static final float ALPHA = 0.15f;
	float oldAccel = 0;

	float pX, pY;
	public float density;
	DecimalFormat df, df1;

	int effect;
	int col;

	boolean moveflag = false;
	boolean headerflag = false;
	float scanline;

	CountDownTimer timer;

	PGraphics header, sketchBG, scanSquare, dragFocus;

	PImage fxFilledImg, innerCircleImg, outerCircleImg, lineCircleImg;

	Toolbar toolbar;

	FxCircleDrag fxCircleDrag;

	int mainHeadHeight, headerHeight, buttonPad1, buttonPad2, buttonFxPad;

	float outerCircSize, dragDeleteBoundary;

	String resSuffix = "_x100";

	int bpm = 120;

	final int bgCol = color(28, 28, 28);
	final int headCol = color(41, 41, 41);
	final int buttonInActCol = color(175);
	final int col1 = color(255, 68, 68);
	final int col2 = color(255, 187, 51);
	final int col3 = color(153, 204, 0);
	final int col4 = color(170, 102, 204);
	final int col5 = color(51, 181, 229);

	/**
	 * setting up libPd as a background service the initPdService() method binds
	 * the service to the background thread. call initPdService in onCreate() to
	 * start the service.
	 */

	protected final ServiceConnection pdConnection = new ServiceConnection() {
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			pdService = ((PdService.PdBinder) service).getService();

			try {
				initPd();
				loadPatch();
			} catch (IOException e) {
				Log.e(TAG, e.toString());
				finish();
			}

		}

		@Override
		public void onServiceDisconnected(ComponentName name) {
			// Never called

		}
	};

	/* Bind pd service */

	private void initPdService() {

		new Thread() {
			@Override
			public void run() {
				bindService(new Intent(SynthCircle.this, PdService.class),
						pdConnection, BIND_AUTO_CREATE);
			}
		}.start();
	}

	/* initialise pd, also setup listeners here */
	protected void initPd() throws IOException {

		// Configure the audio glue
		int sampleRate = AudioParameters.suggestSampleRate();

		// reducing sample rate based on no. of cores
		if (getNumCores() == 1)
			sampleRate = sampleRate / 4;
		else
			sampleRate = sampleRate / 2;

		pdService.initAudio(sampleRate, 0, 2, 10.0f);
		
		pdService.startAudio(new Intent(this, SynthCircle.class),
				R.drawable.notif_icon, "CircleSynth", "Return to Circle Synth");

		dispatcher = new PdUiDispatcher();
		PdBase.setReceiver(dispatcher);

		dispatcher.addListener("scan", new PdListener.Adapter() {
			@Override
			public void receiveFloat(String source, final float x) {
				scanline = x;
				detect();
				sendPdValue();
			}
		});

	}

	protected void loadPatch() throws IOException {

		if (pd == 0) {
			File dir = getFilesDir();
			IoUtils.extractZipResource(
					getResources().openRawResource(
							com.twobigears.circlesynth.R.raw.vnsequencer), dir,
					true);
			File patchFile = new File(dir, "vnsequencer.pd");
			pd = PdBase.openPatch(patchFile.getAbsolutePath());

			// send initial data to the patch from preferences
			initialisepatch();

		}
	}

	private Toast toast = null;

	/**
	 * creating a handy toast notification message method here
	 * 
	 * @param msg
	 *            String MESSAGE_TO_BE_DISPLAYED_AS_TOAST
	 */
	private void toast(final String msg) {
		runOnUiThread(new Runnable() {
			@Override
			public void run() {
				if (toast == null) {
					toast = Toast.makeText(getApplicationContext(), "",
							Toast.LENGTH_SHORT);
				}
				toast.setText(msg);
				toast.show();
			}
		});
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// Setting up Google analytics. GA parameters in the analytics.xml file
		EasyTracker.getInstance().setContext(getApplicationContext());
		// Instantiate the Tracker
		tracker = EasyTracker.getTracker();

		/**
		 * IMPORTANT : if using decimal format, remember to set the decimal
		 * separator explicitly, else your app will crash when used in locales
		 * which use ',' as the decimal separator.
		 */
		symbols.setDecimalSeparator('.');
		df = new DecimalFormat("#.##", symbols);
		df1 = new DecimalFormat("#.#", symbols);

		// Accessing default Shared Preferences
		prefs = PreferenceManager.getDefaultSharedPreferences(this);

		// Call and setup the accelerometer which we will be using later
		mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
		mAccelerometer = mSensorManager
				.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		mSensorManager.registerListener(this, mAccelerometer, 100000);

		// make sure the screen doesn't turn off
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

		initPdService();

		/*
		 * IMPORTANT:make sure you have the READ_PHONE_STATE permission
		 * specified to use the method below. This method will ensure that
		 * pdservice is paused during a phone call.
		 */
		initSystemServices();

	}

	private void initSystemServices() {
		TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
		telephonyManager.listen(new PhoneStateListener() {
			@Override
			public void onCallStateChanged(int state, String incomingNumber) {
				if (pdService == null)
					return;
				if (state == TelephonyManager.CALL_STATE_IDLE) {
					pdService.startAudio();
				} else {
					pdService.stopAudio();
				}
			}
		}, PhoneStateListener.LISTEN_CALL_STATE);
	}

	@Override
	public void onDestroy() {
		super.onDestroy();

		// release all resources called by pdservice
		dispatcher.release();
		unbindService(pdConnection);

		// unregister listener for shared preference clicks and accelerometer
		prefs.unregisterOnSharedPreferenceChangeListener(this);
		mSensorManager.unregisterListener(this);

	}

	@Override
	protected void onStart() {
		super.onStart();
		// this gives analytics the cue to start tracking
		EasyTracker.getInstance().activityStart(this); // Add this method
	}

	@Override
	protected void onStop() {
		super.onStop();
		// stop GA tracking
		EasyTracker.getInstance().activityStop(this); // Add this method
	}

	/**
	 * Gets the number of cores available in this device, across all processors.
	 **/

	private int getNumCores() {
		// Private Class to display only CPU devices in the directory listing
		class CpuFilter implements FileFilter {
			@Override
			public boolean accept(File pathname) {
				// Check if filename is "cpu", followed by a single digit number
				if (Pattern.matches("cpu[0-9]", pathname.getName())) {
					return true;
				}
				return false;
			}
		}

		try {
			// Get directory containing CPU info
			File dir = new File("/sys/devices/system/cpu/");
			// Filter to only list the devices we care about
			File[] files = dir.listFiles(new CpuFilter());
			// Return the number of cores (virtual CPU devices)
			return files.length;
		} catch (Exception e) {
			// Default to return 1 core
			return 1;
		}
	}

	// processing code begins//

	public void setup() {

		frameRate(60);

		dots = new ArrayList<Dot>();
		stored = new ArrayList<String>();

		background(bgCol);
		smooth(8);
		CHECK = new String[10];
		SAVE = new String[10];
		for (int i = 0; i < maxCircle; i++) {
			CHECK[i] = "0 5 5 5 5 0";
		}

		// get android screen density
		DisplayMetrics dm = new DisplayMetrics();
		getWindowManager().getDefaultDisplay().getMetrics(dm);
		float densityR = dm.density;

		// set denity scale value and suffix for image resources
		if (densityR <= 1.2) {
			density = 1f;
			resSuffix = "_x100";
		} else if (densityR > 1.2 && densityR <= 1.6) {
			density = 1.5f;
			resSuffix = "_x150";
		} else if (densityR > 1.6 && densityR <= 2.5) {
			density = 2f;
			resSuffix = "_x200";
		} else if (densityR > 2.5 && densityR < 3.5) {
			density = 3f;
			resSuffix = "_x300";
		} else if (densityR >= 3.5) {
			density = 4f;
			resSuffix = "_x400";
		}

		// load images
		innerCircleImg = loadImage("inner_circle" + resSuffix + ".png");
		outerCircleImg = loadImage("outer_circle" + resSuffix + ".png");
		lineCircleImg = loadImage("line_circle" + resSuffix + ".png");
		fxFilledImg = loadImage("fxFilled" + resSuffix + ".png");

		toolbar = new Toolbar();

		fxCircleDrag = new FxCircleDrag(this);
		fxCircleDrag.emptyCircle = outerCircleImg;
		fxCircleDrag.filledCircle = fxFilledImg;

		// header values
		mainHeadHeight = (int) (40 * density);
		final int shadowHeight = (int) (density);
		final int scanSquareY = (int) (3 * density);
		headerHeight = mainHeadHeight + scanSquareY + shadowHeight;
		buttonPad1 = (int) (10 * density);
		buttonPad2 = (int) (20 * density);
		buttonFxPad = (int) (1 * density);

		// drag to delete boundary thickness
		dragDeleteBoundary = 10 * density;

		// use later in the code for hit detection
		outerCircSize = outerCircleImg.width;

		// create header as PGraphic
		header = createGraphics(width, headerHeight);
		header.beginDraw();
		header.noStroke();
		header.background(headCol);
		header.fill(buttonInActCol, 40);
		header.rect(0, mainHeadHeight, width, scanSquareY);
		header.fill(10);
		header.rect(0, mainHeadHeight + scanSquareY, width, shadowHeight);
		header.endDraw();

		// sketch background as PGraphic, instead of using background()
		sketchBG = createGraphics(width, height - headerHeight);
		sketchBG.beginDraw();
		sketchBG.background(bgCol);
		sketchBG.endDraw();

		// scan square that moves across the timeline
		scanSquare = createGraphics((int) (10 * density), scanSquareY);
		scanSquare.beginDraw();
		scanSquare.noStroke();
		scanSquare.fill(120);
		scanSquare.rect(0, 0, 7 * density, 3 * density, 7 * density);
		scanSquare.endDraw();

		// the sketch border when a circle/node is dragged
		dragFocus = createGraphics(width, height - headerHeight);
		dragFocus.beginDraw();
		dragFocus.noFill();
		dragFocus.stroke(255, 30);
		dragFocus.strokeWeight(dragDeleteBoundary);
		dragFocus.rect(0, 0, dragFocus.width, dragFocus.height);
		dragFocus.endDraw();

	}

	int save_preset, save_bpm, save_scale, save_octTrans, save_noteTrans;

	// We need to have pd ready with presets when the app is first loaded.
	private void initialisepatch() {

		/*
		 * Since the presets sent to pd are gathered from persistent storage,
		 * check if there are any pre-saved settings. if there aren't, allocate
		 * default settings.
		 */
		String value = prefs.getString("preset", null);

		if (value == null) {

			Editor editor = prefs.edit();
			editor.putString("preset", "1");
			editor.putString("scale", "1");
			editor.putString("transposeOct", "3");
			editor.putString("transposeNote", "0");
			editor.putBoolean("accel", true);
			editor.putBoolean("delay", true);
			editor.putBoolean("reverb", true);
			editor.putBoolean("first_tutorial", false);
			editor.commit();
		}

		String pres = prefs.getString("preset", "1");
		int presf = Integer.valueOf(pres);
		PdBase.sendFloat("pd_presets", presf);
		save_preset = presf;

		String val = prefs.getString("scale", "1");
		int valf = Integer.valueOf(val);
		PdBase.sendFloat("pd_scales", valf);
		save_scale = valf;

		String tran = prefs.getString("transposeOct", "3");
		int tranf = Integer.valueOf(tran);
		tranf = tranf - 3;
		PdBase.sendFloat("pd_octTrans", tranf);
		save_octTrans = tranf;

		String tran1 = prefs.getString("transposeNote", "0");
		int tranf1 = Integer.valueOf(tran1);
		PdBase.sendFloat("pd_noteTrans", tranf1);
		save_noteTrans = tranf1;

		boolean accely = prefs.getBoolean("accel", false);
		if (accely)
			PdBase.sendFloat("pd_accelToggle", 1);
		else
			PdBase.sendFloat("pd_accelToggle", 0);

		boolean delayy = prefs.getBoolean("delay", false);
		if (delayy)
			PdBase.sendFloat("pd_delayToggle", 1);
		else
			PdBase.sendFloat("pd_delayToggle", 0);

		boolean reverby = prefs.getBoolean("reverb", false);
		if (reverby)
			PdBase.sendFloat("pd_reverbToggle", 1);
		else
			PdBase.sendFloat("pd_reverbToggle", 0);

		boolean tuts = prefs.getBoolean("first_tutorial", false);

		// If this is the first time the app is laoded, then..
		if (!tuts) {
			// display tutorial dialog
			TutorialDialog.showTutorialDialog(SynthCircle.this);
			tuts = true;
			Editor editor = prefs.edit();
			editor.putBoolean("first_tutorial", true);
			editor.commit();

			// copy asset files and paste them to the demos folder
			String basepath = Environment.getExternalStorageDirectory()
					.toString() + "/circlesynth";
			File demodir = new File(basepath + "/demos/");
			if (!demodir.exists()) {
				demodir.mkdirs();
				copyDemos();
			}

			File circledir = new File(basepath + "/sketches");
			if (!circledir.exists())
				circledir.mkdirs();
		}

	}

	// call this method to apply settings along with a saved sketch, like
	// presets, scales etc.
	public void loadSettings() {
		PdBase.sendFloat("pd_presets", save_preset);
		PdBase.sendFloat("pd_scales", save_scale);
		PdBase.sendFloat("pd_octTrans", save_octTrans);
		PdBase.sendFloat("pd_noteTrans", save_noteTrans);
		PdBase.sendFloat("pd_bpm", save_bpm);
		bpm = save_bpm;

		Editor editor = prefs.edit();

		editor.putString("preset", String.valueOf(save_preset));
		editor.putString("scale", String.valueOf(save_scale));
		editor.putString("transposeOct", String.valueOf(save_octTrans + 3));
		editor.putString("transposeNote", String.valueOf(save_noteTrans));
		editor.apply();

	}

	public void draw() {

		image(sketchBG, 0, headerHeight);

		// draw dots on the screen based on touch data stored in Dot class
		drawThis();

		image(header, 0, 0);
		image(scanSquare, scanline * width, mainHeadHeight);

		if (moveflag)
			image(dragFocus, 0, headerHeight);

		toolbar.drawIt();

		fxCircleDrag.drawIt();
	}

	/*
	 * This method detects where the scanline is, sees if there are any dots
	 * which need to be animated and sets the corresponding selected_ flag for
	 * that dot object. When processing is drawing each frame, depending on the
	 * selected_ flag, it animated the dots accordingly. Gives the illusion of
	 * the dots animating as the scanline moves along.
	 */
	public void detect() {

		if (dots.size() > 0) {
			for (int i = 0; i < dots.size(); i++) {
				Dot d = dots.get(i);
				if (!d.touched2) {
					if (!toolbar.reverseToggleB.state) {
						if (Math.abs(scanline
								- Float.parseFloat(df.format(d.xDown / width))) <= .01)
							d.selected1 = true;

						if (Math.abs(scanline
								- Float.parseFloat(df.format(d.xDown / width))) >= .03)
							d.selected1 = false;
					} else {
						if (Math.abs(scanline
								- Float.parseFloat(df.format(d.xUp / width))) <= .03)
							d.selected1 = true;

						if (Math.abs(scanline
								- Float.parseFloat(df.format(d.xDown / width))) >= .03)
							d.selected1 = false;
					}

				} else {
					float dxd = Float.parseFloat(df.format(d.xDown / width));
					float dxu = Float.parseFloat(df.format(d.xUp / width));
					if (!toolbar.reverseToggleB.state) {
						if (Math.abs(scanline - dxd) <= .01) {
							d.selected1 = d.selected2 = true;
						}

					}
					if (toolbar.reverseToggleB.state) {
						if (Math.abs(scanline - dxu) <= .01) {
							d.selected1 = d.selected2 = true;
						}

						if (Math.abs(scanline - dxu) >= (dxu - dxd + .03)) {
							d.selected1 = d.selected2 = false;
						}

					}
					if (Math.abs(scanline
							- Float.parseFloat(df.format(d.xDown / width))) >= (dxu
							- dxd + .03)) {
						d.selected1 = d.selected2 = false;
					}
				}

			}

		}
	}

	// Associate touch drawing data with the dot ArrayList
	private void drawThis() {

		for (int i = 0; i < dots.size(); i++) {

			Dot d = (Dot) dots.get(i);

			// turning lights off
			if (!toolbar.playToggleB.state)
				d.selected1 = d.selected2 = false;

			// for line
			if (d.touched3)
				d.drawLine();

			// single dot
			if (d.touched1)
				d.drawCircleOne();

			// second circle
			if (d.touched2)
				d.drawCircleTwo();

		}
	}

	/*
	 * This is the main method where all the dot object information is sent to
	 * pd. Each dot represents a pd dot module which makes the corresponding
	 * sound. Here the screen coordinates are normalised, and saved as a string.
	 * Decimal format is used here since the pd modules respond to a maximum of
	 * 2 decimal places. For non-existing dots, a value of 5 is sent to the
	 * module, which in turn switches it off. For example, if there are 3 active
	 * dots on the screen, 3 modules will be on and the remaining 7(since the
	 * max number of circles is 10 in the current version) will be sent a value
	 * of 5 for the screen coordinates, which automatically shuts them off.
	 * 
	 * The string format is as follows :
	 * index_xvalue1_yvalue1_xvalue2_yvalue2_fxvalue(int)
	 * 
	 * Run as a separate thread to increase efficiency and responsiveness of the
	 * UI.
	 */
	public void sendPdValue() {
		new Thread() {
			@Override
			public void run() {
				String TAG1;
				String TAG2;
				String FINAL = null;

				for (int i = 0; i < maxCircle; i++) {
					if (i < dots.size()) {
						Dot d = (Dot) dots.get(i);
						double dxd = d.xDown / width;
						double dyd = 1.0 - ((d.yDown - mainHeadHeight) / (height - mainHeadHeight));
						double dxu = d.xUp / width;
						double dyu = 1.0 - ((d.yUp - mainHeadHeight) / (height - mainHeadHeight));
						if (dxu == dxd)
							dxu = dxd + .03;
						TAG1 = df.format(dxd) + " " + df.format(dyd);
						TAG2 = df.format(dxu) + " " + df.format(dyu);
						FINAL = String.valueOf(i) + " " + TAG1 + " " + TAG2
								+ " " + String.valueOf(d.doteffect);

					} else {
						FINAL = String.valueOf(i) + " " + "5 5 5 5 0";

					}

					// make sure a module doesn't keep receiving the same
					// values.
					if (CHECK[i] != FINAL) {
						// Log.d("pdsend",FINAL);
						// send list of dot values to pd
						String[] pieces = FINAL.split(" ");
						Object[] list = new Object[pieces.length];
						for (int j = 0; j < pieces.length; j++) {
							try {
								list[j] = Float.parseFloat(pieces[j]);
							} catch (NumberFormatException e) {

							}
						}

						PdBase.sendList("pd_dots", list);

					}
					CHECK[i] = FINAL;
				}
			}
		}.start();

	}

	// parse the string contents to populate the dot arraylist. Also extract the
	// sketch params here.
	public void splitString(String string) {

		String[] pieces = string.split(" ");

		int index = Integer.parseInt(pieces[0]);
		if (index < 10) {
			Dot d = dots.get(index);

			if (Float.parseFloat(pieces[1]) <= 1) {
				d.xDown = (Float.parseFloat(pieces[1]) * width);
				d.yDown = (Float.parseFloat(pieces[2]) * height);
				d.xLine = d.xUp = (Float.parseFloat(pieces[3]) * width);
				d.yLine = d.yUp = (Float.parseFloat(pieces[4]) * height);
				d.doteffect = Integer.parseInt(pieces[5]);
				d.dotcol = Integer.parseInt(pieces[6]);
				int t1 = Integer.parseInt(pieces[7]);
				int t2 = Integer.parseInt(pieces[8]);
				int t3 = Integer.parseInt(pieces[9]);
				if (t1 == 1)
					d.touched1 = true;
				else
					d.touched1 = false;
				if (t2 == 1) {
					d.touched2 = true;
					// d.hasLine=true;
				} else {
					d.touched2 = false;
					// d.hasLine=false;
				}

				if (t3 == 1) {
					d.touched3 = true;
					if (d.xDown != d.xUp)
						d.hasLine = true;
					else
						d.hasLine = false;
				} else {
					d.touched3 = false;
					d.hasLine = false;
				}

			} else {
				d.xDown = (Float.parseFloat(pieces[1]));
				d.yDown = (Float.parseFloat(pieces[2]));
				d.xLine = d.xUp = (Float.parseFloat(pieces[3]));
				d.yLine = d.yUp = (Float.parseFloat(pieces[4]));
			}
		} else {
			save_preset = (int) Float.parseFloat(pieces[1]);
			save_scale = (int) Float.parseFloat(pieces[2]);
			save_octTrans = (int) Float.parseFloat(pieces[3]);
			save_noteTrans = (int) Float.parseFloat(pieces[4]);
			save_bpm = (int) Float.parseFloat(pieces[5]);
			loadSettings();

		}

	}

	/*
	 * if the screen is touched where a dot already exists, it return the index
	 * of that dot object, else returns -1.
	 */
	public int delCheck(float mX, float mY) {
		int checkdelete = -1;
		float disp1, disp2;
		disp1 = disp2 = 0;
		if (dots.size() > 0) {
			for (int i = 0; i < dots.size(); i++) {
				Dot d = (Dot) dots.get(i);
				disp1 = distancecalc(d.xDown, d.yDown, mX, mY);
				disp2 = distancecalc(d.xUp, d.yUp, mX, mY);
				if (disp1 < outerCircSize || disp2 < outerCircSize) {
					checkdelete = i;
					i = dots.size();
				}
			}
		}
		return checkdelete;

	}

	// calculates the displacement between two coordinates
	public float distancecalc(float x1, float y1, float x2, float y2) {

		// calculating the px distance between two coordinates (x1,y1) and
		// (x2,y2)
		float xdist = x2 - x1;
		float ydist = y2 - y1;
		double squareX1 = Math.pow(xdist, 2);
		double squareY1 = Math.pow(ydist, 2);
		float distance = (float) Math.sqrt(squareX1 + squareY1);
		return distance;
	}

	/*
	 * The main Dot class Contains parameters to define where the dots or nodes
	 * will be drawn. The class has two main sections, the create(Circle/line)_
	 * methods and the drawCircle_ methods, the former assigning screen
	 * coordinates to the circles and the other responsible for actual drawing
	 * by processing. Also contains the FX parameters to be passed on to libPd.
	 */

	public class Dot {

		float xDown, xUp, yDown, yUp, xLine, yLine, posX, posY;
		boolean touched1, touched2, touched3, selected1, selected2, isMoving,
				isDeleted, hasLine;
		int doteffect;
		int dotcol = color(255, 68, 68);
		private Animations circle1InnerAnim, circle1OuterAnim,
				circle2InnerAnim, circle2OuterAnim;
		private PGraphics lineBuffer;
		private float angle, dist;
		private int lineImgWidth, outerCircleWidth;
		boolean node1, node2;
		boolean isLocked;

		Dot() {
			touched1 = touched2 = touched3 = selected1 = selected2 = isMoving = isDeleted = hasLine = false;
			effect = 0;
			col = col1;

			// initialise animations, each with a duration of 20 frames
			circle1InnerAnim = new Animations(20);
			circle1OuterAnim = new Animations(20);
			circle2InnerAnim = new Animations(20);
			circle2OuterAnim = new Animations(20);

			// PGraphic for line connecting two dots
			lineBuffer = createGraphics(20, lineCircleImg.height);
			lineImgWidth = (int) (lineCircleImg.width - density);

			outerCircleWidth = (int) (outerCircleImg.width - density);
		}

		public void fxClear() {
			// effects : 0 - none, 1 - 4 are corresponding fx
			this.doteffect = 0;
			this.dotcol = color(255, 68, 68);
		}

		// assign coordinates for first circle
		public void createCircle1(float mX1, float mY1) {
			xDown = mX1;
			yDown = mY1;
			xUp = xDown;
			yUp = yDown;
			touched1 = true;
			touched2 = false;
			touched3 = false;
			node1 = true;

		}

		// assign coordinates for second circle
		public void createCircle2(float mX2, float mY2) {
			if (Float.parseFloat(df.format(mX2 / width)) < Float.parseFloat(df
					.format(this.xDown / width))) {
				xUp = this.xDown;
				yUp = this.yDown;
				this.xDown = mX2;
				this.yDown = mY2;
			} else {
				xUp = mX2;
				yUp = mY2;
			}

			xLine = xUp;
			yLine = yUp;
			touched2 = true;
			node2 = true;
		}

		// assign coordinates for line
		public void createLine(float mX3, float mY3) {
			xLine = mX3;
			yLine = mY3;
			hasLine = true;
			touched3 = true;
		}

		// draw the first circle using image resources
		public void drawCircleOne() {
			pushMatrix();
			pushStyle();
			translate(xDown, yDown);
			imageMode(CENTER);

			// the inner circle image is tinted based on assigned fx value
			pushMatrix();
			if (selected1)
				tint(dotcol);
			else
				noTint();
			circle1OuterAnim.animate();
			scale(circle1OuterAnim.animateValue);
			image(outerCircleImg, 0, 0);
			popMatrix();

			pushMatrix();
			if (selected1)
				scale(1.5f);
			else
				scale(1);
			tint(dotcol);
			if (circle1OuterAnim.animateValue > 0.5)
				circle1InnerAnim.animate();
			scale(circle1InnerAnim.animateValue);
			image(innerCircleImg, 0, 0);
			popMatrix();

			popStyle();
			popMatrix();
		}

		// draw the second circle using image resources
		public void drawCircleTwo() {
			pushMatrix();
			pushStyle();
			translate(xUp, yUp);
			imageMode(CENTER);

			pushMatrix();
			if (selected2)
				tint(dotcol);
			else
				noTint();
			circle2OuterAnim.animate();
			scale(circle2OuterAnim.animateValue);
			image(outerCircleImg, 0, 0);
			popMatrix();

			pushMatrix();
			if (selected2)
				scale(1.5f);
			else
				scale(1);
			tint(dotcol);
			if (circle2OuterAnim.animateValue > 0.5)
				circle2InnerAnim.animate();
			scale(circle2InnerAnim.animateValue);
			image(innerCircleImg, 0, 0);
			popMatrix();

			popStyle();
			popMatrix();
		}

		// computer and draw line into PGraphic only when dot coordinates are
		// changed
		private void computeLine(float distanceVal) {
			int countMax = (int) (distanceVal / lineImgWidth);
			if (countMax > 0)
				lineBuffer = createGraphics((int) distanceVal,
						lineCircleImg.height);
			pushStyle();
			lineBuffer.beginDraw();
			lineBuffer.noTint();
			lineBuffer.imageMode(CORNER);
			for (int i = 0; i < countMax; i++) {
				lineBuffer.image(lineCircleImg, lineImgWidth * i, 0);
				if (i == countMax - 1)
					dist = distanceVal;
			}
			lineBuffer.endDraw();
			popStyle();
		}

		// draw line from PGraphic
		public void drawLine() {

			float deltaX = xLine - xDown;
			float deltaY = yLine - yDown;

			// some trigonometry
			angle = atan(deltaY / deltaX);
			if (deltaX < 0)
				angle += PI;

			float tempDist = (float) (sqrt((deltaX * deltaX)
					+ (deltaY * deltaY)) - (outerCircleWidth - density * 3));

			if (tempDist != dist)
				computeLine(tempDist);

			pushMatrix();
			pushStyle();
			translate(xDown, yDown);
			rotate(angle);
			if (selected1 && selected2)
				tint(dotcol);
			else
				noTint();
			imageMode(CORNER);
			image(lineBuffer, (float) ((outerCircleWidth * 0.5) - density),
					(float) (-lineCircleImg.height * 0.5));
			popStyle();
			popMatrix();

		}

		// assign fx value
		public void fx(int f, int col) {
			this.doteffect = f;
			this.dotcol = col;
		}

		public int getNodes() {
			int x = 0;
			if (touched1 && touched2)
				x = 2;
			else if (touched1 && !touched2)
				x = 1;
			else
				x = 0;
			return x;
		}

		public void updateCircles(float mX, float mY) {
			float deltaX = Math.abs(mX - xDown);
			float deltaY = Math.abs(mY - yDown);

			double dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);

			if (dist < outerCircSize / 2) {
				xDown = mX;
				yDown = mY;
				if (hasLine) {
					xLine = xUp;
					yLine = yUp;
				} else {
					xUp = xDown;
					yUp = yDown;
				}

			} else {
				xLine = xUp = mX;
				yLine = yUp = mY;

			}

			if (xDown > xUp) {
				float tempX = xDown;
				float tempY = yDown;
				xDown = xUp;
				yDown = yUp;
				xUp = tempX;
				yUp = tempY;
			}

		}

	}

	int checkdelete;
	int fxcheckdelete;

	/**
	 * This is where the magic happens. All touch events are sent here, and then
	 * worked on accordingly.
	 */
	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {

		// System.out.println(String.valueOf(moveflag));

		width = displayWidth;
		height = displayHeight;
		float x = (event.getX());
		float y = (event.getY());
		int action = event.getActionMasked();
		// int checkdelete = -1;
		if (dots != null) {
			fxcheckdelete = delCheck(x, y);
			// Log.d("checkdelete", String.valueOf(checkdelete));

			fxCircleDrag.setXY(x, y);

			switch (action) {
			case MotionEvent.ACTION_DOWN:

				checkdelete = delCheck(x, y);

				if (y > mainHeadHeight) {
					dots.add(new Dot());
					if (checkdelete < 0) {
						Dot d = (Dot) dots.get(dots.size() - 1);
						if (dots.size() <= maxCircle) {
							d.createCircle1(x, y);
							d.isLocked = false;
							pX = x;
							pY = y;
							d.isMoving = false;
							moveflag = false;
						}

					}
					if (checkdelete >= 0) {
						moveflag = true;

						Dot d = (Dot) dots.get(checkdelete);
						d.isMoving = true;
						d.isLocked = true;
						dots.remove(dots.size() - 1);
					}
					headerflag = false;
				} else
					headerflag = true;

				break;
			case MotionEvent.ACTION_UP:

				if (!headerflag) {
					if (dots.size() > 0) {
						Dot d1 = (Dot) dots.get(dots.size() - 1);
						if (checkdelete < 0) {
							// if (moveflag)
							// dots.remove(dots.size() - 1);
							if (y < mainHeadHeight && !moveflag) {
								dots.remove(dots.size() - 1);
								toast(getString(R.string.cant_draw));
							}

							if (dots.size() > maxCircle) {
								toast(getString(R.string.limit_Reached));
								dots.remove(dots.size() - 1);
							}
							if (d1.hasLine == true && !d1.isLocked) {

								d1.createCircle2(x, y);
								d1.isMoving = false;

							}

						}
						if (checkdelete >= 0 && dots.size() > 0) {
							Dot d = (Dot) dots.get(dots.size() - 1);
							if (x < dragDeleteBoundary
									|| x > width - dragDeleteBoundary
									|| y > height - dragDeleteBoundary
									|| y < mainHeadHeight + dragDeleteBoundary) {
								dots.remove(checkdelete);
								toast(getString(R.string.gone));
							}

							if (d.hasLine && !d.touched2)
								dots.remove(dots.size() - 1);

						}

					}

				}

				// assign fx dragged in and dropped
				if (fxcheckdelete >= 0 && fxCheck) {
					Dot d = dots.get(fxcheckdelete);
					d.fx(effect, col);
				}

				// reset moveflag to false
				moveflag = false;

				break;
			case MotionEvent.ACTION_POINTER_DOWN:

				break;
			case MotionEvent.ACTION_POINTER_UP:

				break;
			case MotionEvent.ACTION_MOVE:

				if (!headerflag && dots.size() > 0) {
					if (checkdelete >= 0) {
						Dot dnew = (Dot) dots.get(checkdelete);
						if (dnew.isMoving) {
							int historySize = event.getHistorySize();
							if (historySize > 0) {
								for (int i = 0; i < historySize; i++) {
									float historicalX = event.getHistoricalX(i);
									float historicalY = event.getHistoricalY(i);
									dnew.updateCircles(historicalX, historicalY);
								}
							} else
								dnew.updateCircles(x, y);
						}
					}

					else if (!moveflag) {
						Dot d11 = (Dot) dots.get(dots.size() - 1);
						if (d11.node1
								&& distanceChecker(d11.xDown, d11.yDown, x, y))
							d11.createLine(x, y);

					}
				}

				// set fx assign color/value based on move
				if (toolbar.fx1ToggleB.state) {
					effect = 1;
					col = col2;
					fxCheck = true;
				} else if (toolbar.fx2ToggleB.state) {
					effect = 2;
					col = col3;
					fxCheck = true;
				} else if (toolbar.fx3ToggleB.state) {
					effect = 3;
					col = col4;
					fxCheck = true;
				} else if (toolbar.fx4ToggleB.state) {
					effect = 4;
					col = col5;
					fxCheck = true;
				} else if (toolbar.fxEmptyToggleB.state) {
					effect = 0;
					col = col1;
					fxCheck = true;
				} else
					fxCheck = false;

				break;
			}

		}

		return super.dispatchTouchEvent(event);
	}

	// return true if the distance between two dots meets a certain limit
	public boolean distanceChecker(float x1, float y1, float x2, float y2) {
		boolean check = false;

		double dist = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
		if (dist < outerCircSize)
			check = false;
		else
			check = true;

		return check;
	}

	// interface method from the bpm dialog.
	@Override
	public void bpmChanged(int t) {
		bpm = t;
		PdBase.sendFloat("pd_bpm", bpm);
	}

	@Override
	protected void onResume() {
		super.onResume();
	}

	// Changes in the settings screen are sent to pd here
	@Override
	public void onSharedPreferenceChanged(SharedPreferences pref, String key) {

		Editor editor = prefs.edit();

		if (key.equals("preset")) {
			String pres = prefs.getString("preset", "1");
			int presf = Integer.valueOf(pres);
			PdBase.sendFloat("pd_presets", presf);
			save_preset = presf;
			editor.putString("preset", String.valueOf(presf));
			editor.commit();
		}

		if (key.equals("scale")) {
			String val = prefs.getString("scale", "1");
			int valf = Integer.valueOf(val);
			PdBase.sendFloat("pd_scales", valf);
			editor.putString("scale", String.valueOf(valf));
			editor.commit();
			save_scale = valf;
		}
		// Octave transpose preference

		if (key.equals("transposeOct")) {
			String tran = prefs.getString("transposeOct", "3");
			int tranf = Integer.valueOf(tran);
			tranf = tranf - 3;
			PdBase.sendFloat("pd_octTrans", tranf);
			editor.putString("transposeOct", String.valueOf(tranf + 3));
			editor.commit();
			save_octTrans = tranf;
		}

		// Note transpose preference

		if (key.equals("transposeNote")) {
			String tran1 = prefs.getString("transposeNote", "0");
			int tranf1 = Integer.valueOf(tran1);
			PdBase.sendFloat("pd_noteTrans", tranf1);
			editor.putString("transposeNote", String.valueOf(tranf1));
			editor.commit();
			save_noteTrans = tranf1;
		}

		if (key.equals("accel")) {
			boolean accely = prefs.getBoolean("accel", false);
			if (accely)
				PdBase.sendFloat("pd_accelToggle", 1);

			else
				PdBase.sendFloat("pd_accelToggle", 0);
			editor.putBoolean("accel", accely);
			editor.commit();

		}
		if (key.equals("delay")) {
			boolean delayy = prefs.getBoolean("delay", true);
			if (delayy)
				PdBase.sendFloat("pd_delayToggle", 1);

			else
				PdBase.sendFloat("pd_delayToggle", 0);
			editor.putBoolean("delay", delayy);
			editor.commit();

		}
		if (key.equals("reverb")) {
			boolean reverby = prefs.getBoolean("reverb", true);
			if (reverby)
				PdBase.sendFloat("pd_reverbToggle", 1);

			else
				PdBase.sendFloat("pd_reverbToggle", 0);
			editor.putBoolean("reverb", reverby);
			editor.commit();

		}

	}

	/*
	 * low pass filtering based on the accelerometer tilting the device in
	 * either x or y axes generates the filter params used in pd in the lores~
	 * object
	 */

	@Override
	public void onSensorChanged(SensorEvent event) {

		if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
			return;
		SharedPreferences getPrefs1 = PreferenceManager
				.getDefaultSharedPreferences(getBaseContext());
		accel = getPrefs1.getBoolean("accel", true);
		currentAccelx = lowPass(event.values[0]);
		currentAccely = lowPass(event.values[1]);
		float y = Math.abs(currentAccelx / 10);
		float z = Math.abs(currentAccely / 10);

		if (y > z)
			z = y;
		else
			y = z;

		if (accel == true)
			PdBase.sendFloat("pd_accely", (1 - y));

	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {

	}

	DecimalFormat dfnew = new DecimalFormat("#");

	// smoothening accelerometer data

	// lowpass filter function
	protected float lowPass(float input) {

		double accel = 0;
		accel = (float) (ALPHA * accel + (1.0 - ALPHA) * oldAccel);
		oldAccel = input;
		// accel = Float.parseFloat(dfnew.format(accel));
		accel = Math.round(accel);
		return (float) accel;
	}

	// clearing blank dots after load
	private void dotcleanup() {

		for (int i = dots.size() - 1; i >= 0; i--) {
			Dot d = (Dot) dots.get(i);
			if (d.xDown == 5 && d.yDown == 5)
				dots.remove(i);
		}

	}

	// sharing intent
	private void shareIt() {
		String content = "Hey! Check out my awesome sketch!! In Circle Synth, click on Load and navigate to the folder where you downloaded the sketch to open it.";
		Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
		sharingIntent.setType("audio/mpeg3");

		String shareBody = content;
		sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
		sharingIntent.putExtra(Intent.EXTRA_SUBJECT, "Circle Synth sketch");
		String root = Environment.getExternalStorageDirectory().toString();
		File myDir = new File(root + "/circlesynth/sketches");
		myDir.mkdirs();
		int count = new File(root + "/circlesynth/sketches").listFiles().length;
		if (count == 0)
			fName = null;

		if (fName != null) {
			File file = new File(myDir, fName);
			Uri uri = Uri.fromFile(file);

			sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);

			startActivity(Intent.createChooser(sharingIntent, "Share via"));
		} else
			toast(getString(R.string.save_sketch));
	}

	// The toolbar including all buttons and animations
	public class Toolbar {

		// Images for buttons
		PImage shareImg, playImg, stopImg, revImg, forImg, clearOnImg,
				clearOffImg, loadOffImg, loadOnImg, saveOffImg, saveOnImg,
				shareOffImg, shareOnImg, settingsOnImg, settingsOffImg,
				fxCircleToggleImg, fxEmptyToggleImg, fxClearOffImg,
				fxClearOnImg, moreToggleImg, lessToggleImg;

		PFont robotoFont, robotoSmallFont;

		// All button classes
		PlayToggle playToggleB;
		ReverseToggle reverseToggleB;
		FxToggle fxToggleB;
		BpmButton bpmButtonB;
		ClearButton clearButtonB;
		LoadButton loadButtonB;
		SaveButton saveButtonB;
		ShareButton shareButtonB;
		SettingsButton settingsButtonB;
		Fx1Toggle fx1ToggleB;
		Fx2Toggle fx2ToggleB;
		Fx3Toggle fx3ToggleB;
		Fx4Toggle fx4ToggleB;
		FxEmptyToggle fxEmptyToggleB;
		FxClearButton fxClearButtonB;
		MoreToggle moreToggleB;
		RecordToggle recordToggleB;

		// For toolbar slide animation
		Animations toolbarAnimate;

		// Animation slide value
		private float slideX;

		Toolbar() {

			// Doing the needful. Initialisation time.
			robotoFont = createFont("Roboto-Thin-12", 24 * density, true);
			robotoSmallFont = createFont("Roboto-Thin-12", 20 * density, true);

			playImg = loadImage("play" + resSuffix + ".png");
			stopImg = loadImage("stop" + resSuffix + ".png");
			revImg = loadImage("reverse" + resSuffix + ".png");
			forImg = loadImage("forward" + resSuffix + ".png");
			clearOnImg = loadImage("clearOn" + resSuffix + ".png");
			clearOffImg = loadImage("clearOff" + resSuffix + ".png");
			loadOffImg = loadImage("loadOff" + resSuffix + ".png");
			loadOnImg = loadImage("loadOn" + resSuffix + ".png");
			saveOffImg = loadImage("saveOff" + resSuffix + ".png");
			saveOnImg = loadImage("saveOn" + resSuffix + ".png");
			shareOffImg = loadImage("shareOff" + resSuffix + ".png");
			shareOnImg = loadImage("shareOn" + resSuffix + ".png");
			settingsOffImg = loadImage("settingsOff" + resSuffix + ".png");
			settingsOnImg = loadImage("settingsOn" + resSuffix + ".png");
			fxCircleToggleImg = loadImage("fxCircleToggle" + resSuffix + ".png");
			fxEmptyToggleImg = loadImage("fxCircleEmpty" + resSuffix + ".png");
			fxClearOffImg = loadImage("fxClearOff" + resSuffix + ".png");
			fxClearOnImg = loadImage("fxClearOn" + resSuffix + ".png");
			moreToggleImg = loadImage("more" + resSuffix + ".png");
			lessToggleImg = loadImage("less" + resSuffix + ".png");

			playToggleB = new PlayToggle(SynthCircle.this);
			playToggleB.load(playImg, stopImg);
			reverseToggleB = new ReverseToggle(SynthCircle.this);
			reverseToggleB.load(revImg, forImg);
			fxToggleB = new FxToggle(SynthCircle.this);
			fxToggleB.load(robotoFont);
			fxToggleB.setSize(revImg.width, revImg.height);
			bpmButtonB = new BpmButton(SynthCircle.this);
			bpmButtonB.load(robotoFont);
			bpmButtonB.setSize(revImg.width, revImg.height);
			clearButtonB = new ClearButton(SynthCircle.this);
			clearButtonB.load(clearOffImg, clearOnImg);
			loadButtonB = new LoadButton(SynthCircle.this);
			loadButtonB.load(loadOffImg, loadOnImg);
			saveButtonB = new SaveButton(SynthCircle.this);
			saveButtonB.load(saveOffImg, saveOnImg);
			shareButtonB = new ShareButton(SynthCircle.this);
			shareButtonB.load(shareOffImg, shareOnImg);
			settingsButtonB = new SettingsButton(SynthCircle.this);
			settingsButtonB.load(settingsOffImg, settingsOnImg);
			fx1ToggleB = new Fx1Toggle(SynthCircle.this, false, true);
			fx1ToggleB.load(fxCircleToggleImg, fxCircleToggleImg);
			fx2ToggleB = new Fx2Toggle(SynthCircle.this, false, true);
			fx2ToggleB.load(fxCircleToggleImg, fxCircleToggleImg);
			fx3ToggleB = new Fx3Toggle(SynthCircle.this, false, true);
			fx3ToggleB.load(fxCircleToggleImg, fxCircleToggleImg);
			fx4ToggleB = new Fx4Toggle(SynthCircle.this, false, true);
			fx4ToggleB.load(fxCircleToggleImg, fxCircleToggleImg);
			fxEmptyToggleB = new FxEmptyToggle(SynthCircle.this, false, true);
			fxEmptyToggleB.load(fxEmptyToggleImg, fxEmptyToggleImg);
			fxClearButtonB = new FxClearButton(SynthCircle.this, false);
			fxClearButtonB.load(fxClearOffImg, fxClearOnImg);
			moreToggleB = new MoreToggle(SynthCircle.this);
			moreToggleB.load(moreToggleImg, lessToggleImg);
			recordToggleB = new RecordToggle(SynthCircle.this);
			recordToggleB.load(robotoSmallFont);
			recordToggleB.setSize(revImg.width, revImg.height);
			recordToggleB.offColor = color(120, 120, 120);
			recordToggleB.onColor = color(255, 0, 0);

			// Init animation with animation time in frames
			toolbarAnimate = new Animations(30);

		}

		// Draw it all
		public void drawIt() {

			// Toggle slide animation
			if (moreToggleB.state)
				toolbarAnimate.accelerateUp();
			else
				toolbarAnimate.accelerateDown();

			pushMatrix();

			// Animation value * distance needed to animate in px
			slideX = (playToggleB.getWidth() * 3) + (buttonPad1 * 3);
			float xAnimate = toolbarAnimate.animateValue * -slideX;

			// All buttons
			playToggleB.drawIt(buttonPad1 + xAnimate, 0);
			reverseToggleB.drawIt((playToggleB.getWidth() + buttonPad2)
					+ xAnimate, 0);
			bpmButtonB.drawIt(String.valueOf(bpm),
					((playToggleB.getWidth() + buttonPad2) * 2) + xAnimate, 0);
			clearButtonB.drawIt(((playToggleB.getWidth() + buttonPad2) * 3)
					+ xAnimate, 0);
			fxToggleB.drawIt("FX", ((playToggleB.getWidth() + buttonPad2) * 4)
					+ xAnimate, 0);

			moreToggleB.drawIt((width - (playToggleB.getWidth() + buttonPad1))
					+ xAnimate, 0);
			settingsButtonB.drawIt(
					(width - ((playToggleB.getWidth() + buttonPad1) * 2))
							+ xAnimate, 0);
			recordToggleB.drawIt(REC_TEXT,
					(width - ((playToggleB.getWidth() + buttonPad1) * 3))
							+ xAnimate, 0);

			shareButtonB.drawIt((width) + xAnimate, 0);
			saveButtonB.drawIt(
					(width + ((playToggleB.getWidth()) + (buttonPad1)))
							+ xAnimate, 0);
			loadButtonB.drawIt(
					(width + ((playToggleB.getWidth() * 2) + (buttonPad1 * 2)))
							+ xAnimate, 0);

			fx1ToggleB.drawIt(buttonFxPad + xAnimate, 0);
			fx2ToggleB.drawIt((fx1ToggleB.getWidth() + buttonFxPad) + xAnimate,
					0);
			fx3ToggleB.drawIt(((fx1ToggleB.getWidth()) * 2 + buttonFxPad)
					+ xAnimate, 0);
			fx4ToggleB.drawIt(((fx1ToggleB.getWidth()) * 3 + buttonFxPad)
					+ xAnimate, 0);
			fxEmptyToggleB.drawIt(((fx1ToggleB.getWidth()) * 4 + buttonFxPad)
					+ xAnimate, 0);
			fxClearButtonB.drawIt(((fx1ToggleB.getWidth()) * 5 + buttonFxPad)
					+ xAnimate, 0);

			popMatrix();
		}

		// String for record button
		String REC_TEXT = "REC";

		// Button specs below, extending/overriding UI classes
		class PlayToggle extends UiImageToggle {

			PlayToggle(PApplet p) {
				super(p);
			}

			@Override
			public void isTrue() {
				PdBase.sendFloat("pd_playToggle", 1);
				sendPdValue();
			}

			@Override
			public void isFalse() {
				PdBase.sendFloat("pd_playToggle", 0);
				scanline = 0;
			}
		}

		class ReverseToggle extends UiImageToggle {

			ReverseToggle(PApplet p) {
				super(p);
			}

			@Override
			public void isTrue() {
				PdBase.sendFloat("pd_revToggle", 1);
			}

			@Override
			public void isFalse() {
				PdBase.sendFloat("pd_revToggle", 0);
			}

		}

		class FxToggle extends UiTextToggle {

			public FxToggle(PApplet p) {
				super(p);
			}

			// Enable/disable buttons
			@Override
			public void isTrue() {
				playToggleB.isEnabled = reverseToggleB.isEnabled = bpmButtonB.isEnabled = clearButtonB.isEnabled = false;
				fx1ToggleB.isEnabled = fx2ToggleB.isEnabled = fx3ToggleB.isEnabled = fx4ToggleB.isEnabled = fxEmptyToggleB.isEnabled = fxClearButtonB.isEnabled = true;
			}

			@Override
			public void isFalse() {
				playToggleB.isEnabled = reverseToggleB.isEnabled = bpmButtonB.isEnabled = clearButtonB.isEnabled = true;
				fx1ToggleB.isEnabled = fx2ToggleB.isEnabled = fx3ToggleB.isEnabled = fx4ToggleB.isEnabled = fxEmptyToggleB.isEnabled = fxClearButtonB.isEnabled = false;
			}

		}

		class BpmButton extends UiTextButton {

			public BpmButton(PApplet p) {
				super(p);
			}

			@Override
			public void isReleased() {

				// Open BPM popup dialog
				toast(getString(R.string.set_bpm));
				SynthCircle.this.runOnUiThread(new Runnable() {
					public void run() {
						new BpmPicker(SynthCircle.this, SynthCircle.this, bpm)
								.show();
					}
				});

			}
		}

		class ClearButton extends UiImageButton {

			ClearButton(PApplet p) {
				super(p);
			}

			@Override
			public void isReleased() {
				dots.clear();
				stored.clear();
				toast(getString(R.string.clear));
			}
		}

		class LoadButton extends UiImageButton {

			LoadButton(PApplet p) {
				super(p);
			}

			@Override
			public void isReleased() {

				// Load saved text file

				File mPath = new File(Environment.getExternalStorageDirectory()
						+ "/circlesynth/sketches");
				fileDialog = new FileDialog(SynthCircle.this, mPath);
				fileDialog.setFileEndsWith(".txt");
				fileDialog
						.addFileListener(new FileDialog.FileSelectedListener() {
							public void fileSelected(File file) {
								fName = file.getName();
								try {

									dots.clear();
									stored.clear();

									FileInputStream input = new FileInputStream(
											file);
									DataInputStream din = new DataInputStream(
											input);

									for (int i = 0; i <= maxCircle; i++) { // Read
																			// lines
										String line = din.readUTF();
										stored.add(line);
										if (i < maxCircle)
											dots.add(new Dot());
										splitString(stored.get(i));

									}
									din.close();
								} catch (IOException exc) {
									exc.printStackTrace();
								}
								dotcleanup();

							}

						});

				// show the in-app file manager
				SynthCircle.this.runOnUiThread(new Runnable() {
					public void run() {

						fileDialog.showDialog();
					}
				});
			}
		}

		class SaveButton extends UiImageButton {

			SaveButton(PApplet p) {
				super(p);
			}

			@Override
			public void isReleased() {
				// Analytics Tracker

				tracker.sendEvent("ui_Action", "button_press", "save_button",
						0L);

				// Save sketch and settings as text file

				toast(getString(R.string.saved));
				stored.clear();
				int t1 = 0;
				int t2 = 0;
				int t3 = 0;
				int count = 0;
				String SAVE = null;
				for (int i = 0; i < maxCircle; i++) {
					if (i < dots.size()) {
						Dot d = (Dot) dots.get(i);
						if (d.touched1)
							t1 = 1;
						if (d.touched2)
							t2 = 1;
						if (d.touched3)
							t3 = 1;
						SAVE = String.valueOf(i) + " "
								+ String.valueOf(d.xDown / width) + " "
								+ String.valueOf(d.yDown / height) + " "
								+ String.valueOf(d.xUp / width) + " "
								+ String.valueOf(d.yUp / height) + " "
								+ String.valueOf(d.doteffect) + " "
								+ String.valueOf(d.dotcol) + " "
								+ String.valueOf(t1) + " " + String.valueOf(t2)
								+ " " + String.valueOf(t3);
					} else {
						SAVE = String.valueOf(i) + " 5 5 5 5 0 0 0 0 0";
					}
					stored.add(i, SAVE);
					t1 = 0;
					t2 = 0;
					t3 = 0;
					count = i;
				}
				save_bpm = bpm;

				String SAVE_EXTRA = String.valueOf(++count) + " "
						+ String.valueOf(save_preset) + " "
						+ String.valueOf(save_scale) + " "
						+ String.valueOf(save_octTrans) + " "
						+ String.valueOf(save_noteTrans) + " "
						+ String.valueOf(save_bpm);
				stored.add(count, SAVE_EXTRA);

				String root = Environment.getExternalStorageDirectory()
						.toString();
				File myDir = new File(root + "/circlesynth/sketches");
				myDir.mkdirs();
				SimpleDateFormat formatter = new SimpleDateFormat("MMddHHmm");
				Date now = new Date();
				String fileName = formatter.format(now);
				String fname = "sketch_" + fileName + ".txt";
				fName = fname;
				File file = new File(myDir, fname);
				try {
					FileOutputStream output = new FileOutputStream(file);
					DataOutputStream dout = new DataOutputStream(output);

					// Save line count
					for (String line : stored)
						// Save lines
						dout.writeUTF(line);
					dout.flush(); // Flush stream ...
					dout.close(); // ... and close.

				} catch (IOException exc) {
					exc.printStackTrace();
				}

			}
		}

		class ShareButton extends UiImageButton {

			ShareButton(PApplet p) {
				super(p);
			}

			@Override
			public void isReleased() {
				// Analytics Tracker

				tracker.sendEvent("ui_Action", "button_press", "share_button",
						0L);

				shareIt();
			}
		}

		class SettingsButton extends UiImageButton {

			SettingsButton(PApplet p) {
				super(p);
			}

			@SuppressWarnings("deprecation")
			@Override
			public void isReleased() {

				// Open share preferences

				tracker.sendEvent("ui_Action", "button_press",
						"settings_button", 0L);
				Intent intent = new Intent(SynthCircle.this,
						SynthSettingsTwo.class);
				startActivity(intent);
				prefs.registerOnSharedPreferenceChangeListener(SynthCircle.this);
			}
		}

		// FX circle toggles are in "alternate mode" for drag n' drop
		class Fx1Toggle extends UiImageToggle {

			Fx1Toggle(PApplet p, boolean enabled, boolean alternateMode) {
				super(p, enabled, alternateMode);
				tintValue = col2;
			}

			@Override
			public void isTrue() {
				fxCircleDrag.color = col2;
				fxCircleDrag.isEnabled = true;
			}

			@Override
			public void isFalse() {
				fxCircleDrag.isEnabled = false;
			}

		}

		class Fx2Toggle extends UiImageToggle {

			Fx2Toggle(PApplet p, boolean enabled, boolean alternateMode) {
				super(p, enabled, alternateMode);
				tintValue = col3;
			}

			@Override
			public void isTrue() {
				fxCircleDrag.color = col3;
				fxCircleDrag.isEnabled = true;
			}

			@Override
			public void isFalse() {
				fxCircleDrag.isEnabled = false;
			}
		}

		class Fx3Toggle extends UiImageToggle {

			Fx3Toggle(PApplet p, boolean enabled, boolean alternateMode) {
				super(p, enabled, alternateMode);
				tintValue = col4;
			}

			@Override
			public void isTrue() {
				fxCircleDrag.color = col4;
				fxCircleDrag.isEnabled = true;
			}

			@Override
			public void isFalse() {
				fxCircleDrag.isEnabled = false;
			}
		}

		class Fx4Toggle extends UiImageToggle {

			Fx4Toggle(PApplet p, boolean enabled, boolean alternateMode) {
				super(p, enabled, alternateMode);
				tintValue = col5;
			}

			@Override
			public void isTrue() {
				fxCircleDrag.color = col5;
				fxCircleDrag.isEnabled = true;
			}

			@Override
			public void isFalse() {
				fxCircleDrag.isEnabled = false;
			}
		}

		class FxEmptyToggle extends UiImageToggle {

			FxEmptyToggle(PApplet p, boolean enabled, boolean alternateMode) {
				super(p, enabled, alternateMode);
			}

			@Override
			public void isTrue() {
				fxCircleDrag.color = -1;
				fxCircleDrag.isEnabled = true;
			}

			@Override
			public void isFalse() {
				fxCircleDrag.isEnabled = false;
			}
		}

		class FxClearButton extends UiImageButton {

			FxClearButton(PApplet p, boolean enabled) {
				super(p, enabled);
			}

			@Override
			public void isReleased() {
				for (int i = 0; i < dots.size(); i++) {
					Dot d = (Dot) dots.get(i);
					d.fxClear();
				}
				toast(getString(R.string.fx_clear));
			}
		}

		class MoreToggle extends UiImageToggle {

			MoreToggle(PApplet p) {
				super(p);
			}

			@Override
			public void isTrue() {
			}

			@Override
			public void isFalse() {

			}
		}

		long start;
		int countertest;
		Editor edit1;

		/**
		 * 
		 * Pd starts recording the audio into a buffer. It is then either saved,
		 * set as a ringtone or deleted, depending on the user choice. These
		 * actions are triggered based on the state of the REC button.
		 */
		class RecordToggle extends UiTextToggle {

			public RecordToggle(PApplet p) {
				super(p);
			}

			@Override
			public void isTrue() {

				// Note: maximum record time is 30s, this is set independently
				// in Pd
				start = 30000;
				// tell pd to start recording into a temp buffer
				PdBase.sendFloat("pd_record", 1);
				isRecording = true;
				// integer counter to count the rec time. used later to set the
				// rec play button state
				countertest = 0;
				edit1 = prefs.edit();

				// in case the recording button is pressed when the play button
				// is off, start playing
				if (!toolbar.playToggleB.state) {
					toolbar.playToggleB.isTrue();
					toolbar.playToggleB.state = true;
				}

				// run countdowntimer thread
				runOnUiThread(new Runnable() {
					@Override
					public void run() {
						timer = new CountDownTimer(start, 1000) {

							@Override
							public void onFinish() {
								PdBase.sendFloat("pd_record", 0);
								REC_TEXT = "REC";
								toolbar.recordToggleB.state = false;
								System.out.println("Recording length = "
										+ String.valueOf(countertest));
								// save length to prefs
								edit1.putFloat("timer", 30);
								edit1.commit();
								countertest = 0;

								record();
								isRecording = false;

							}

							@Override
							public void onTick(long millisUntilFinished) {
								REC_TEXT = String
										.valueOf(millisUntilFinished / 1000);
								countertest++;
							}

						}.start();
					}
				});

			}

			@Override
			public void isFalse() {

				PdBase.sendFloat("pd_record", 0);
				REC_TEXT = "REC";
				timer.cancel();
				if (isRecording) {
					edit1.putFloat("timer", countertest);
					edit1.commit();

					record();
					System.out.println("Recording length = "
							+ String.valueOf(countertest));

					countertest = 0;
					tracker.sendEvent("ui_Action", "button_press",
							"record_button", 0L);
				}
			}

		}

	}

	boolean isRecording = false;

	public void record() {
		toolbar.playToggleB.isFalse();
		toolbar.playToggleB.state = false;

		PdBase.sendFloat("pd_playToggle", 0);

		SynthCircle.this.runOnUiThread(new Runnable() {
			public void run() {
				DialogFragment dialog = new RecordDialog();
				dialog.show(getFragmentManager(), "recordingfragment");
			}
		});
		isRecording = false;
	}

	// interface methods from the recording dialog
	@Override
	public void onPlayTrue() {
		System.out.println("play called from dialog");
		PdBase.sendFloat("pd_recordPlay", 1);

	}

	@Override
	public void onPlayFalse() {
		System.out.println("stop called from dialog");
		PdBase.sendFloat("pd_recordPlay", 0);

	}

	@Override
	public void onPositiveAction() {
		String root = Environment.getExternalStorageDirectory().toString();
		prepareRecord();
		copyFile(saveFilePath + "/" + saveFileName, root
				+ "/Ringtones/CircleSynthRing.wav");
		setRingtone();
		// new LoadViewTask().execute();
		toast(getString(R.string.ringtone));

	}

	@Override
	public void onNegativeAction() {
		// Log.d("listener","cancel");
		PdBase.sendFloat("pd_recordPlay", 0);
		PdBase.sendBang("pd_recordCancel");
	}

	@Override
	public void onNeutralAction() {
		// Log.d("listener","Save");
		prepareRecord();
		toast(getString(R.string.rec_saved));
	}

	String saveFilePath;
	String saveFileName;

	// prepare the file system to store the recordings
	public void prepareRecord() {

		String root = Environment.getExternalStorageDirectory().toString();
		File myDir = new File(root + "/circlesynth/recordings");
		myDir.mkdirs();
		SimpleDateFormat formatter = new SimpleDateFormat("MMddHHmm");
		Date now = new Date();
		String fileName = formatter.format(now);
		String fname = "recording_" + fileName;
		saveFilePath = myDir.getAbsolutePath();
		saveFileName = fname + ".wav";
		PdBase.sendSymbol("pd_path", myDir + "/" + fname);

	}

	/**
	 * Here we must copy the recording to be set as a ringtone into the user's
	 * /Ringtones folder, and then create the MediaStore entry so that we can
	 * proceed to set it as the user's default ringtone.
	 */
	public void setRingtone() {

		String path = Environment.getExternalStorageDirectory().toString()
				+ "/Ringtones";
		File k = new File(path, "CircleSynthRing.wav");
		ContentValues values = new ContentValues();
		values.put(MediaStore.MediaColumns.DATA, k.getAbsolutePath());
		values.put(MediaStore.MediaColumns.TITLE, "CircleSynthRingtone");
		values.put(MediaStore.MediaColumns.SIZE, k.length());
		values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/vnd.wave");
		values.put(MediaStore.Audio.Media.ARTIST, "CircleSynth");
		values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
		values.put(MediaStore.Audio.Media.IS_NOTIFICATION, false);
		values.put(MediaStore.Audio.Media.IS_ALARM, false);
		values.put(MediaStore.Audio.Media.IS_MUSIC, false);

		System.out.println("getting the right file "
				+ String.valueOf(k.length()));

		// Insert it into the database
		Uri uri = MediaStore.Audio.Media.getContentUriForPath(k.toString());
		// delete previous entries - do this to avoid duplicate entries of the
		// same name
		getContentResolver().delete(
				uri,
				MediaStore.MediaColumns.DATA + "=\"" + k.getAbsolutePath()
						+ "\"", null);
		System.out.println("file in question " + k.getAbsolutePath());

		// insert new values into the DB
		Uri newUri = this.getContentResolver().insert(uri, values);

		// set as default ringtone
		try {
			RingtoneManager.setActualDefaultRingtoneUri(getBaseContext(),
					RingtoneManager.TYPE_RINGTONE, newUri);
		} catch (Throwable t) {
			Log.d(TAG, "catch exception");
			System.out.println("ringtone set exception " + t.getMessage());
		}

	}

	// copying the file from the recordings folder to the Ringtones folder on
	// the sdcard
	public static boolean copyFile(String from, String to) {
		try {
			int bytesum = 0;
			int byteread = 0;
			File oldfile = new File(from);
			File newfile = new File(Environment.getExternalStorageDirectory()
					.toString() + "/Ringtones");
			if (!newfile.exists())
				newfile.mkdir();

			if (oldfile.exists()) {
				InputStream inStream = new FileInputStream(from);
				FileOutputStream fs = new FileOutputStream(to);
				byte[] buffer = new byte[1024];
				while ((byteread = inStream.read(buffer)) != -1) {
					bytesum += byteread;
					fs.write(buffer, 0, byteread);
				}
				inStream.close();
				fs.close();
				// System.out.println("success copy");
			}

			return true;
		} catch (Exception e) {
			System.out.println(e.getMessage());
			return false;
		}
	}

	// unpack the demo sketches from the assets resource folder to the sdcard
	private void copyDemos() {
		String basepath = Environment.getExternalStorageDirectory().toString()
				+ "/circlesynth";
		AssetManager assetManager = getResources().getAssets();
		String[] files = null;
		try {
			files = assetManager.list("demos");
		} catch (Exception e) {
			Log.e("read demo ERROR", e.toString());
			e.printStackTrace();
		}
		for (int i = 0; i < files.length; i++) {
			InputStream in = null;
			OutputStream out = null;
			try {
				in = assetManager.open("demos/" + files[i]);
				out = new FileOutputStream(basepath + "/demos/" + files[i]);
				copyFile(in, out);
				in.close();
				in = null;
				out.flush();
				out.close();
				out = null;
			} catch (Exception e) {
				Log.e("copy demos ERROR", e.toString());
				e.printStackTrace();
			}
		}
	}

	// do the actual copying. HORRIBLE naming. beat yourself up.
	private void copyFile(InputStream in, OutputStream out) throws IOException {
		byte[] buffer = new byte[1024];
		int read;
		while ((read = in.read(buffer)) != -1) {
			out.write(buffer, 0, read);
		}
	}

}