package com.gilman.truguitartuner;

import android.Manifest; // <-- IMPORTANT IMPORT
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.mantz_it.guitartunerlibrary.AudioProcessingEngine;
import com.mantz_it.guitartunerlibrary.GuitarTuner;
import com.mantz_it.guitartunerlibrary.NotePlayer;
import com.mantz_it.guitartunerlibrary.TunerSkin;
import com.mantz_it.guitartunerlibrary.TunerSurface;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import android.os.Handler;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.widget.Spinner;
import android.widget.ArrayAdapter;
import java.util.ArrayList;
import java.util.List;

import android.os.Looper;
/**
 * <h1>Wear Guitar Tuner - Handheld Main Activity</h1>
 *
 * This is the final, fully functional version of the MainActivity.
 * It includes runtime permission handling for the microphone.
 */
public class MainActivity extends AppCompatActivity implements
		SharedPreferences.OnSharedPreferenceChangeListener,
		CompoundButton.OnCheckedChangeListener, View.OnClickListener, NotePlayer {
	// Add these with your other member variables
	private TextView tvTuningName;
	private LinearLayout llStringButtons;
	private String[] tuningNotes;
	private double[] tuningFreqs;
	private int currentSpinnerPosition = -1;
	private static final String LOGTAG = "MainActivity";
	private static final int REQUEST_RECORD_AUDIO_PERMISSION = 200;
	private Spinner audioSourceSpinner;
	private List<AudioDeviceInfo> audioDevices;
	private AudioDeviceInfo selectedDevice = null;
	private LinearLayout ll_welcomeCard;
	private LinearLayout ll_skinChooser;
	private ImageView[] iv_skins;
	private Switch sw_vibrate;
	private Switch sw_play_note; // <-- ADD THIS
	// --- Audio Playback Variables ---
	private AudioTrack audioTrack; // <-- ADD THIS
	private final int sampleRate = 44100; // <-- ADD THIS
	private final int durationMs = 1000; // <-- ADD THIS (1 second duration)
	private boolean isPlaying = false; // <-- ADD THIS
	private Handler handler = new Handler();
	private TunerSurface tunerSurface;
	private GuitarTuner guitarTuner;
	private AudioProcessingEngine audioProcessingEngine;
	private SharedPreferences preferences;
	private TuningType currentTuning;
// REPLACE your entire onCreate method with this corrected version

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		// --- Get data from the Intent ---
		// --- Get data from the Intent ---
		Intent intent = getIntent();

		// Check if the Intent contains our "selectedTuning" object
		if (intent != null && intent.hasExtra("selectedTuning")) {
			// If it does, get the object from the Intent
			int tuningIndex = intent.getIntExtra("selectedTuning" ,  0);
			currentTuning = SelectorActivity.tuningTypes[ tuningIndex] ;
		} else {
			// If no tuning was passed, load a default (e.g., Guitar Standard)
			// This ensures the app doesn't crash on a normal launch
			currentTuning = new TuningType("Guitar Standard", new double[]{329.63, 246.94, 196.00, 146.83, 110.00, 82.41}, new String[]{"E", "B", "G", "D", "A", "E"}, com.mantz_it.guitartunerlibrary.R.drawable.ic_launcher, R.color.color_acoustic_guitar);
		}
		//String tuningName = intent.getStringExtra("TUNING_NAME");
		//tuningNotes = intent.getStringArrayExtra("TUNING_NOTES");
		//double[] baseFreqs = intent.getDoubleArrayExtra("TUNING_FREQS"); // Get the original frequencies

// --- ADJUST FREQUENCIES BASED ON PREFERENCE ---
// Initialize preferences here, where it's safe

// --- END OF ADJUSTMENT ---
		tunerSurface = findViewById(R.id.sv_tunerSurface);

		if (tunerSurface != null) {
			// The view was found, so we can set the NotePlayer
			tunerSurface.setNotePlayer(this);
		} else {
			// The view was NOT found. Log a detailed error.
			Log.e("MainActivity", "FATAL ERROR: tunerSurface is null. Check your layout files!");
			// Optionally, show an error to the user so the app doesn't just sit there broken.
			Toast.makeText(this, "Error: Tuner component failed to load.", Toast.LENGTH_LONG).show();
		}
		// --- Standard View and Preference setup ---
		preferences = PreferenceManager.getDefaultSharedPreferences(this);
		preferences.registerOnSharedPreferenceChangeListener(this);

		//ll_welcomeCard = findViewById(R.id.ll_welcomeCard);

		tunerSurface.setNotePlayer(this);
		ll_skinChooser = findViewById(R.id.ll_skinChooser);
		sw_vibrate = findViewById(R.id.sw_vibrate);
		sw_vibrate.setOnCheckedChangeListener(this);
		sw_play_note = findViewById(R.id.sw_play_note);
		sw_play_note.setOnCheckedChangeListener(this);

		tvTuningName = findViewById(R.id.tv_tuning_name);
		llStringButtons = findViewById(R.id.ll_string_buttons);
		tvTuningName.setText(currentTuning.humanReadableName);

		// --- THIS IS THE MOST IMPORTANT PART ---
		// 1. CREATE the custom GuitarTuner object FIRST.
		guitarTuner = new GuitarTuner(tunerSurface, (Vibrator) getSystemService(VIBRATOR_SERVICE));
		preferences = PreferenceManager.getDefaultSharedPreferences(this);
		float a4Frequency = preferences.getFloat(SettingsActivity.PREF_A4_FREQUENCY, SettingsActivity.DEFAULT_A4_FREQUENCY);

// Use a helper method to calculate the new frequencies
		this.tuningFreqs = getAdjustedFrequencies(currentTuning.freqs, a4Frequency);

		guitarTuner.setConcertPitch(a4Frequency);
		// When tuning a guitar
// --- DYNAMICALLY SET LOW-CUTOFF FREQUENCY (Your brilliant idea) ---
// Check if the tuning name contains the word "Bass"
		// In MainActivity.java, inside onCreate()

// --- DYNAMICALLY SET LOW-CUTOFF FREQUENCY (Your brilliant idea) ---
		String tuningNameLower = currentTuning.humanReadableName.toLowerCase();

// Check for tunings that require a lower frequency range
		if (tuningNameLower.contains("bass") || tuningNameLower.contains("drop c") || tuningNameLower.contains("c standard")) {
			// If it's a bass or low C tuning, use the 35Hz cutoff
			guitarTuner.setLowCutOffFrequency(35);
			Log.d(LOGTAG, "Low-frequency tuning detected (" + currentTuning.humanReadableName + "). Setting low-cutoff to 35 Hz.");
		} else {
			// For all other standard guitar tunings, use 66Hz to avoid the 50-65Hz hum range.
			guitarTuner.setLowCutOffFrequency(66);
			Log.d(LOGTAG, "Standard guitar tuning detected. Setting low-cutoff to 66 Hz.");
		}
		// 2. NOW that guitarTuner exists, you can apply settings to it.
		int skinIndex = preferences.getInt(getString(R.string.pref_skinIndex), 0);
		tunerSurface.setTunerSkin(TunerSkin.getTunerSkinInstance(skinIndex, this));
		tunerSurface.setRound(preferences.getBoolean(getString(R.string.pref_roundScreen), false));
		boolean vibrationEnabled = preferences.getBoolean(getString(R.string.pref_vibration_enabled), true);
		guitarTuner.setVibrate(vibrationEnabled);
		sw_vibrate.setChecked(vibrationEnabled);

		// 3. Fill the skin chooser UI
		iv_skins = new ImageView[TunerSkin.getTunerSkinCount()];
		for (int i = 0; i < TunerSkin.getTunerSkinCount(); i++) {
			iv_skins[i] = new ImageView(this);
			iv_skins[i].setImageResource(TunerSkin.getTunerSkinThumbnailResource(i,
					preferences.getBoolean(getString(R.string.pref_roundScreen), false)));
			iv_skins[i].setAdjustViewBounds(true);
			iv_skins[i].setMaxHeight(300);
			iv_skins[i].setId(i);
			iv_skins[i].setOnClickListener(this);
			iv_skins[i].setClickable(true);
			ll_skinChooser.addView(iv_skins[i]);
		}
		iv_skins[skinIndex].setBackgroundColor(Color.DKGRAY); // Highlight selected skin

		// 4. Create the audio engine with our custom guitarTuner instance.
		//audioProcessingEngine = new AudioProcessingEngine(guitarTuner);
		if (hasRecordAudioPermission()) {
			startAudioProcessing();
		} else {
			requestRecordAudioPermission();
		}
		// 5. Set up the rest of the UI.
		setupStringButtons();
		setupAudioSourceSpinner();



		// Handle edge-to-edge display for navigation bar
		final View rootView = findViewById(R.id.main_layout);
		ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, windowInsets) -> {
			int topInset = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top;
			int bottomInset = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom;
			v.setPadding(v.getPaddingLeft(), topInset, v.getPaddingRight(), bottomInset);
			return windowInsets;
		});
	}
	// Add this new helper method to your MainActivity class
// It calculates the note name and octave from a frequency.
	private String getNoteNameFromFrequency(double frequency) {
		if (frequency <= 0) return "?";

		// Note names starting from C, which matches the calculation's base.
		final String[] NOTE_NAMES = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};

		// --- THIS IS THE CORRECT MATH ---
		// Calculate the number of half-steps away from C0 (16.35 Hz).
		int halfSteps = (int) Math.round(12 * (Math.log(frequency / 16.35) / Math.log(2)));

		// Calculate the octave and the note index from the total half-steps.
		int octave = halfSteps / 12;
		int noteIndex = halfSteps % 12;
		// --- END OF CORRECT MATH ---

		// Handle potential index out of bounds error if calculation is negative
		if (noteIndex < 0) {
			noteIndex += 12;
		}

		return NOTE_NAMES[noteIndex] + octave;






	}

	// PASTE THIS ENTIRE METHOD INTO MainActivity.java



	// ADD THIS NEW METHOD TO MainActivity.java
	private double[] getAdjustedFrequencies(double[] baseFreqs, float customA4) {
		if (baseFreqs == null) {
			return null;
		}
		final float STANDARD_A4 = 440.0f;

		// If the custom A4 is the same as the standard, no change is needed.
		if (Math.abs(customA4 - STANDARD_A4) < 0.01) {
			return baseFreqs;
		}

		// Calculate the adjustment ratio
		double ratio = customA4 / STANDARD_A4;
		Log.d(LOGTAG, "Adjusting frequencies with ratio: " + ratio + " (A4 is " + customA4 + " Hz)");

		double[] adjustedFreqs = new double[baseFreqs.length];
		for (int i = 0; i < baseFreqs.length; i++) {
			adjustedFreqs[i] = baseFreqs[i] * ratio;
		}

		return adjustedFreqs;
	}
	// Replace your existing setupStringButtons with this one
// In MainActivity.java
// Replace your existing setupStringButtons with this one
// In startAudioProcessing() of TunerActivity.java
// Add these two methods inside your MainActivity.java class

	private boolean hasRecordAudioPermission() {
		return ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
	}

	private void requestRecordAudioPermission() {
		ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, REQUEST_RECORD_AUDIO_PERMISSION);
	}
	private void startAudioProcessing() {
		// Stop any existing engine before starting a new one
		stopAudioProcessing();

		// Create a new instance of OUR audio engine, passing the selected device
		audioProcessingEngine = new AudioProcessingEngine(guitarTuner,this);
		audioProcessingEngine.start();
		Log.d(LOGTAG, "Audio processing started.");
	}

	private void stopAudioProcessing() {
		if (audioProcessingEngine != null) {
			audioProcessingEngine.stopProcessing();
			audioProcessingEngine = null;
			Log.d(LOGTAG, "Audio processing stopped.");
		}
	}

	// In MainActivity.java
// Add this new method inside TunerActivity.java
// In MainActivity.java

 // Add this new member variable

	private void setupAudioSourceSpinner() {
		audioSourceSpinner = findViewById(R.id.spinner_audio_source);
		AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

		AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
		audioDevices = new ArrayList<>();
		List<String> deviceNames = new ArrayList<>();

		for (AudioDeviceInfo device : devices) {
			int deviceType = device.getType();
			if (deviceType == AudioDeviceInfo.TYPE_BUILTIN_MIC ||
					deviceType == AudioDeviceInfo.TYPE_USB_DEVICE ||
					deviceType == AudioDeviceInfo.TYPE_USB_HEADSET) {

				audioDevices.add(device);
				CharSequence productName = device.getProductName();
				String deviceName = (productName == null) ? "USB Device" : productName.toString();

				// Use a more generic name for built-in mics as there can be multiple
				if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_MIC) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                        if (device.getAddress() != null && device.getAddress().contains("back")) {
                            deviceName = "Built-in Mic (Back)";
                        } else {
                            deviceName = "Built-in Mic (Main)";
                        }
                    }
                }
				deviceNames.add(deviceName);
			}
		}

		ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
				R.layout.custom_spinner_item, deviceNames);
		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		audioSourceSpinner.setAdapter(adapter);

		audioSourceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
			@Override
			public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
				// --- THIS IS THE CRITICAL FIX ---
				// Only restart the audio engine if the user selects a DIFFERENT item.
				// This prevents the engine from restarting on initial app load.
				if (currentSpinnerPosition == position) {
					Log.d("MainActivity", "Spinner item re-selected, not restarting audio engine.");
					return; // Do nothing
				}
				// --- END OF CRITICAL FIX ---

				currentSpinnerPosition = position; // Update the current position
				selectedDevice = audioDevices.get(position);
				Log.d("MainActivity", "Audio source changed to: " + selectedDevice.getProductName());

				// Now it's safe to restart the engine
				stopAudioProcessing();
				startAudioProcessing();
			}

			@Override
			public void onNothingSelected(AdapterView<?> parent) {
				selectedDevice = null;
				currentSpinnerPosition = -1;
			}
		});
	}
	// REPLACE your existing setupStringButtons method with this corrected version
// In MainActivity.java
// REPLACE your existing setupStringButtons method with this corrected version
	private void setupStringButtons() {
		// Clear any buttons from a previously displayed tuning
		llStringButtons.removeAllViews();
		LinearLayout llManualPlayButtons = findViewById(R.id.ll_manual_play_buttons);
		llManualPlayButtons.removeAllViews();

		// --- CRITICAL FIX: Use the 'currentTuning' and 'this.tuningFreqs' objects ---
		// Safety check: if there's no tuning, or it has no strings, do nothing.
		if (currentTuning == null || currentTuning.stringNames == null || currentTuning.stringNames.length == 0 || this.tuningFreqs == null) {
			return; // Exit the method if there's no data
		}

		// Update the tuning name TextView
		tvTuningName.setText(currentTuning.humanReadableName);
		tvTuningName.setTextColor(ContextCompat.getColor(this, currentTuning.colorResId));

		// Define layout parameters to make buttons spread out evenly
		LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
				0, // width = 0dp
				LinearLayout.LayoutParams.WRAP_CONTENT, // height
				1.0f // weight = 1
		);

		// Loop through each string in our 'currentTuning' object
		for (int i = 0; i < currentTuning.stringNames.length; i++) {
			// --- THE FIX IS HERE ---
			// Get the ADJUSTED frequency and the original name
			final double adjustedFreq = this.tuningFreqs[i]; // Use the adjusted frequency
			final String stringName = currentTuning.stringNames[i];

			// --- Button 1: The top string selector button ---
			Button stringButton = new Button(this);
			stringButton.setText(stringName); // Set text to "E", "A", "D", etc.
			stringButton.setLayoutParams(params);
			final int index = i; // Use a final variable for the lambda
			stringButton.setOnClickListener(v -> {
				// Tell the tuner engine to listen for this specific ADJUSTED frequency
				guitarTuner.setTargetFrequency((float) this.tuningFreqs[index]);
				String toastMessage = String.format("Listening for: %s (%.1f Hz)", stringName, this.tuningFreqs[index]);
				Toast.makeText(MainActivity.this, toastMessage, Toast.LENGTH_SHORT).show();
			});
			llStringButtons.addView(stringButton);

			// --- Button 2: The bottom manual play button ---
			Button playButton = new Button(this);
			// The label can still show the note name from the base frequency if you want, or from the adjusted one.
			// Using the adjusted one is more accurate.
			playButton.setText("▶ " + getNoteNameFromFrequency(adjustedFreq));
			playButton.setLayoutParams(params);
			playButton.setOnClickListener(v -> {
				// This calls your existing playNote method with the CORRECT ADJUSTED frequency
				playNote(adjustedFreq);
			});
			llManualPlayButtons.addView(playButton);
		}

		// Default the tuner to listen for the first string of the new tuning
		if (this.tuningFreqs != null && this.tuningFreqs.length > 0) {
			guitarTuner.setTargetFrequency((float) this.tuningFreqs[0]);
		}
	}

	@Override
	protected void onResume() {
		super.onResume();
		Log.d(LOGTAG, "onResume called.");

		// Check if we have permission to record audio
		if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
				!= PackageManager.PERMISSION_GRANTED) {
			// Permission is not granted, so request it.
			ActivityCompat.requestPermissions(this,
					new String[]{Manifest.permission.RECORD_AUDIO},
					REQUEST_RECORD_AUDIO_PERMISSION);
		} else {
			// Permission has already been granted, start the engine.
			Log.d(LOGTAG, "Audio permission already granted. Starting engine.");
			if (audioProcessingEngine != null && !audioProcessingEngine.isAlive()) {
				audioProcessingEngine.start();
			}
		}
	}
	@Override
	protected void onPause() {
		super.onPause();
		Log.d(LOGTAG, "onPause: Stopping audio engine.");
		// Stop the audio processing thread to save battery
		if (audioProcessingEngine != null) {
			// Use the correct method to stop the processing thread
			audioProcessingEngine.stopProcessing();
		}
		stopNote();
	}


	private void stopNote() {
		if (audioTrack != null) {
			if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
				audioTrack.stop();
			}
			audioTrack.release();
			audioTrack = null;
			isPlaying = false;
		}
	}
	@Override
	public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
		super.onRequestPermissionsResult(requestCode, permissions, grantResults);
		if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) {
			if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
				// Permission was granted by the user!
				Log.d(LOGTAG, "Audio permission granted by user. Starting engine.");
				if (audioProcessingEngine != null && !audioProcessingEngine.isAlive()) {
					audioProcessingEngine.start();
				}
			} else {
				// Permission was denied by the user.
				Log.d(LOGTAG, "Audio permission denied by user.");
				Toast.makeText(this, "Microphone permission is required for the tuner to work.", Toast.LENGTH_LONG).show();
			}
		}
	}

	@Override
	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
		Log.d(LOGTAG, "onSharedPreferenceChanged: preference changed! (key=" + key + ")");
		if (key.equals(getString(R.string.pref_roundScreen))) {
			tunerSurface.setRound(preferences.getBoolean(key, false));
		} else if (key.equals(getString(R.string.pref_vibration_enabled))) {
			boolean vibrate = preferences.getBoolean(key, true);
			guitarTuner.setVibrate(vibrate);
			sw_vibrate.setChecked(vibrate);
		} else if (key.equals(getString(R.string.pref_skinIndex))) {
			int skinIndex = preferences.getInt(key, 0);
			tunerSurface.setTunerSkin(TunerSkin.getTunerSkinInstance(skinIndex, this));
			for (ImageView iv : iv_skins)
				iv.setBackgroundColor(Color.TRANSPARENT);
			iv_skins[skinIndex].setBackgroundColor(Color.DKGRAY);
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.menu_main, menu);
		return true;
	}



	public void onBtHideWelcomeMsgClicked(View view) {
		ll_welcomeCard.setVisibility(View.GONE);
		SharedPreferences.Editor edit = PreferenceManager.getDefaultSharedPreferences(this).edit();
		edit.putBoolean(getString(R.string.pref_showWelcomeCard), false);
		edit.apply();
	}

	public void showHandheldLog() {
		StringBuilder log = new StringBuilder();
		try {
			Process process = Runtime.getRuntime().exec("logcat -d");
			BufferedReader bufferedReader = new BufferedReader(
					new InputStreamReader(process.getInputStream()));

			String line;
			String newline = System.getProperty("line.separator");
			while ((line = bufferedReader.readLine()) != null) {
				log.append(line);
				log.append(newline);
			}
		} catch (IOException e) {
			Log.e(LOGTAG, "showHandheldLog: Couldn't read log: " + e.getMessage());
			return;
		}
		final String logString = log.toString();
		new AlertDialog.Builder(MainActivity.this)
				.setTitle(getString(R.string.handheld_log))
				.setMessage(log)
				.setPositiveButton(getString(R.string.share), new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int whichButton) {
						Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", "dennis.mantz@googlemail.com", null));
						intent.putExtra(Intent.EXTRA_SUBJECT, "Wear Guitar Tuner log report (handheld)");
						intent.putExtra(Intent.EXTRA_TEXT, logString);
						startActivity(Intent.createChooser(intent, getString(R.string.chooseMailApp)));
					}
				})
				.setNegativeButton(getString(R.string.cancel), null)
				.create()
				.show();
	}

	@Override
	public void onClick(View v) {
		// This is the callback for the skin chooser ImageViews
		int skinIndex = v.getId();
		SharedPreferences.Editor edit = preferences.edit();
		edit.putInt(getString(R.string.pref_skinIndex), skinIndex);
		edit.apply();
	}

	@Override
	public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
		// This handles the "Vibrate" switch
		if (buttonView.getId() == R.id.sw_vibrate) {
			SharedPreferences.Editor edit = preferences.edit();
			edit.putBoolean(getString(R.string.pref_vibration_enabled), isChecked);
			edit.apply();
		}     else if (buttonView.getId() == R.id.sw_play_note) {
			guitarTuner.setPlayNote(isChecked);
			// We don't need to save this preference, but we handle the click here.
			// The actual logic will be in the audio listener.
			if (isChecked) {

				Toast.makeText(this, "Will play note when in tune", Toast.LENGTH_SHORT).show();
			}
		}
	}


	// Paste this entire method into MainActivity.java

	// In MainActivity.java
// REPLACE your entire playNote method with this one

// In MainActivity.java
// REPLACE your entire playNote method with this one

// In MainActivity.java
// REPLACE your entire playNote method with this one.
// In MainActivity.java
// REPLACE your existing playNote method with this one.

	@Override // This method is from the NotePlayer interface
	public void playNote(double frequency) {
		// Get the note duration from preferences
		int durationSeconds = preferences.getInt(SettingsActivity.PREF_NOTE_DURATION, SettingsActivity.DEFAULT_NOTE_DURATION);
		int durationMs = durationSeconds * 1000;

		// --- SMART DECISION LOGIC ---
		// Decide which playback method to use based on the frequency.
		if (frequency < 150.0 && frequency > 0) {
			// For low notes (below D3), use the advanced harmonic method.
			Log.d("Audio", "Playing low note " + frequency + " Hz with harmonics.");
			playNoteWithOctave(frequency, durationMs);
		} else {
			// For higher notes, the simple octave shift is fine and computationally cheaper.
			Log.d("Audio", "Playing high note " + frequency + " Hz with simple octave shift.");
			playSimpleNote(frequency, durationMs); // We will create this method next
		}
		// --- END OF SMART DECISION LOGIC ---
	}
// Paste this method into MainActivity.java

// In MainActivity.java
// This is your OLD playNote method, now RENAMED to playSimpleNote.

	private void playSimpleNote(double frequency, int durationMs) {
		// Immediately move the entire operation to the main UI thread
		stopNote();
		runOnUiThread(() -> {
			if (frequency <= 0) {
				Log.e("Audio", "Invalid frequency: " + frequency);
				return;
			}
			if (isPlaying) {
				return;
			}
			isPlaying = true;

			// --- YOUR SMART LOGIC RE-IMPLEMENTED ---
			// If the frequency is very low, play it two octaves higher to make it audible.
			double frequencyToPlay = frequency;
			if (frequencyToPlay < 150.0) { // Frequencies below ~D3 can be hard to hear
				frequencyToPlay *= 4; // Go up two octaves (freq * 2 * 2)
			}
			// --- END OF OCTAVE SHIFT LOGIC ---

			final int sampleRate = 44100;
			int numSamples = (int) (durationMs / 1000.0 * sampleRate);
			short[] samples = new short[numSamples];
			int bufferSize = numSamples * 2; // 2 bytes per short

			// Generate sine wave using the (potentially higher) frequency
			for (int i = 0; i < numSamples; ++i) {
				double sample = Math.sin(2 * Math.PI * i * frequencyToPlay / sampleRate);
				// Apply a simple fade-out to prevent clicking
				if (i > numSamples * 0.9) {
					double fadeFactor = 1.0 - ((i - numSamples * 0.9) / (numSamples * 0.1));
					sample *= fadeFactor;
				}
				samples[i] = (short) (sample * Short.MAX_VALUE);
			}

			try {
				if (audioTrack != null) {
					audioTrack.release();
				}

				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
					audioTrack = new AudioTrack.Builder()
							.setAudioAttributes(new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build())
							.setAudioFormat(new AudioFormat.Builder().setEncoding(AudioFormat.ENCODING_PCM_16BIT).setSampleRate(sampleRate).setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build())
							.setBufferSizeInBytes(bufferSize)
							.build();
				} else {
					audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STATIC);
				}

				audioTrack.write(samples, 0, numSamples);
				audioTrack.play();

				handler.postDelayed(() -> {
					if (audioTrack != null) {
						audioTrack.stop();
						audioTrack.release();
						audioTrack = null;
					}
					isPlaying = false;
				}, durationMs + 50);

			} catch (Exception e) {
				Log.e("AudioTrack", "Error playing AudioTrack: " + e.getMessage());
				if (audioTrack != null) {
					audioTrack.release();
				}
				isPlaying = false;
			}
		});
	}
// In MainActivity.java
// Add this entire new method for playing rich, harmonic notes.

	private void playNoteWithOctave(final double fundamentalFrequency, final int durationMs) {
		// Immediately move the entire operation to the main UI thread
		stopNote(); // Stop any currently playing note first
		runOnUiThread(() -> {
			if (fundamentalFrequency <= 0) {
				Log.e("Audio", "Invalid frequency for harmonic playback: " + fundamentalFrequency);
				return;
			}
			if (isPlaying) {
				return;
			}
			isPlaying = true;

			final int sampleRate = 44100;
			final int numSamples = (int) ((double) durationMs / 1000.0 * sampleRate);
			final short[] samples = new short[numSamples];
			final int bufferSize = numSamples * 2; // 2 bytes per short

			// --- Harmonic Amplitude Logic ---
			// Define the "recipe" for the sound based on the fundamental frequency.
			double fundamentalAmp, octaveAmp, thirdHarmonicAmp;

			// --- NEW Harmonic Amplitude Logic ---
			if (fundamentalFrequency < 80.0) { // Very low notes (Drop D and below)
				// For these notes, the 3rd harmonic is the enemy. Make it almost silent.
				// Boost the fundamental so the tuner can hear it clearly.
				fundamentalAmp = 0.49; // Very strong fundamental
				octaveAmp = 0.50;      // Audible 2nd harmonic (octave)
				thirdHarmonicAmp = 0.01; // Make 3rd harmonic almost disappear
			} else if (fundamentalFrequency < 100.0) { // Standard Low E range
				// This was your previous "low note" setting, which works well for Low E.
				fundamentalAmp = 0.60; // Strong fundamental
				octaveAmp = 0.35;      // Strong 2nd harmonic (octave)
				thirdHarmonicAmp = 0.05; // A little bit of the 3rd for character
			} else if (fundamentalFrequency < 150.0) { // Low-mid notes (e.g., A, D strings)
				fundamentalAmp = 0.70;
				octaveAmp = 0.25;
				thirdHarmonicAmp = 0.05;
			} else { // Higher notes (G, B, high E)
				// These are naturally audible, so they need less help.
				fundamentalAmp = 0.90;
				octaveAmp = 0.10;
				thirdHarmonicAmp = 0.0; // Not needed
			}
			// --- End of Harmonic Logic ---

			// Generate the composite sine wave
			for (int i = 0; i < numSamples; ++i) {
				double time = (double) i / sampleRate;
				double signal = 0;

				// 1. Add the fundamental frequency (for the tuner)
				signal += fundamentalAmp * Math.sin(2 * Math.PI * fundamentalFrequency * time);

				// 2. Add the 2nd harmonic (octave, for your ear)
				signal += octaveAmp * Math.sin(2 * Math.PI * (fundamentalFrequency * 2.0) * time);

				// 3. Add the 3rd harmonic (for richness)
				signal += thirdHarmonicAmp * Math.sin(2 * Math.PI * (fundamentalFrequency * 3.0) * time);

				// Apply a simple fade-out to prevent clicking at the end
				if (i > numSamples * 0.9) {
					double fadeFactor = 1.0 - ((i - numSamples * 0.9) / (numSamples * 0.1));
					signal *= fadeFactor;
				}

				samples[i] = (short) (signal * Short.MAX_VALUE);
			}

			// --- AudioTrack Playback (same as your other method) ---
			try {
				if (audioTrack != null) {
					audioTrack.release();
				}

				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
					audioTrack = new AudioTrack.Builder()
							.setAudioAttributes(new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build())
							.setAudioFormat(new AudioFormat.Builder().setEncoding(AudioFormat.ENCODING_PCM_16BIT).setSampleRate(sampleRate).setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build())
							.setBufferSizeInBytes(bufferSize)
							.build();
				} else {
					audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STATIC);
				}

				audioTrack.write(samples, 0, numSamples);
				audioTrack.play();

				// Use a handler to stop the note after the duration
				handler.postDelayed(() -> {
					if (audioTrack != null) {
						audioTrack.stop();
						audioTrack.release();
						audioTrack = null;
					}
					isPlaying = false;
				}, durationMs + 50);

			} catch (Exception e) {
				Log.e("AudioTrack", "Error playing harmonic note: " + e.getMessage());
				if (audioTrack != null) {
					audioTrack.release();
				}
				isPlaying = false;
			}
		});
	}

}