//
//  EqualizerWindowController.m
//  Cog
//
//  Created by Christopher Snowhill on 2/13/22.
//

#import "EqualizerWindowController.h"

#import "json.h"

#import "Logging.h"

static const NSString *equalizerGenre = @"";
static const NSString *equalizerDefaultGenre = @"Flat";
static NSArray *equalizer_presets_processed = nil;
static NSDictionary *equalizer_presets_by_name = nil;
static json_value *equalizer_presets = NULL;

static NSString *_cog_equalizer_type = @"Cog EQ library file v1.0";

static NSArray *_cog_equalizer_items() {
	return @[@"name", @"hz32", @"hz64", @"hz128", @"hz256", @"hz512", @"hz1000", @"hz2000", @"hz4000", @"hz8000", @"hz16000", @"preamp"];
}

static NSArray *_cog_equalizer_band_settings() {
	return @[@"eqPreamp", @"eq20Hz", @"eq25Hz", @"eq31p5Hz", @"eq40Hz", @"eq50Hz", @"eq63Hz", @"eq80Hz", @"eq100Hz", @"eq125Hz", @"eq160Hz", @"eq200Hz", @"eq250Hz", @"eq315Hz", @"eq400Hz", @"eq500Hz", @"eq630Hz", @"eq800Hz", @"eq1kHz", @"eq1p2kHz", @"eq1p6kHz", @"eq2kHz", @"eq2p5kHz", @"eq3p1kHz", @"eq4kHz", @"eq5kHz", @"eq6p3kHz", @"eq8kHz", @"eq10kHz", @"eq12kHz", @"eq16kHz", @"eq20kHz"];
}

static const float cog_equalizer_bands[10] = { 32, 64, 128, 256, 512, 1000, 2000, 4000, 8000, 16000 };
static NSArray *cog_equalizer_items = nil;
static NSArray *cog_equalizer_band_settings = nil;
static NSString *cog_equalizer_extra_genres = @"altGenres";

static const float apple_equalizer_bands[31] = { 20, 25, 31.5, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000, 1200, 1600, 2000, 2500, 3100, 4000, 5000, 6300, 8000, 10000, 12000, 16000, 20000 };

static inline float interpolatePoint(const NSDictionary *preset, float freqTarget) {
	if(!cog_equalizer_items)
		cog_equalizer_items = _cog_equalizer_items();

	// predict extra bands! lpc was too broken, quadra was broken, let's try simple linear steps
	if(freqTarget < cog_equalizer_bands[0]) {
		float work[14];
		float work_freq[14];
		for(unsigned int i = 0; i < 10; ++i) {
			work[9 - i] = [[preset objectForKey:[cog_equalizer_items objectAtIndex:1 + i]] floatValue];
			work_freq[9 - i] = cog_equalizer_bands[i];
		}
		for(unsigned int i = 10; i < 14; ++i) {
			work[i] = work[i - 1] + (work[i - 1] - work[i - 2]) * 1.05;
			work_freq[i] = work_freq[i - 1] + (work_freq[i - 1] - work_freq[i - 2]) * 1.05;
		}
		for(unsigned int i = 0; i < 13; ++i) {
			if(freqTarget >= work_freq[13 - i] &&
			   freqTarget < work_freq[12 - i]) {
				float freqLow = work_freq[13 - i];
				float freqHigh = work_freq[12 - i];
				float valueLow = work[13 - i];
				float valueHigh = work[12 - i];

				float delta = (freqTarget - freqLow) / (freqHigh - freqLow);

				return valueLow + (valueHigh - valueLow) * delta;
			}
		}

		return work[13];
	} else if(freqTarget > cog_equalizer_bands[9]) {
		float work[14];
		float work_freq[14];
		for(unsigned int i = 0; i < 10; ++i) {
			work[i] = [[preset objectForKey:[cog_equalizer_items objectAtIndex:1 + i]] floatValue];
			work_freq[i] = cog_equalizer_bands[i];
		}
		for(unsigned int i = 10; i < 14; ++i) {
			work[i] = work[i - 1] + (work[i - 1] - work[i - 2]) * 1.05;
			work_freq[i] = work_freq[i - 1] + (work_freq[i - 1] - work_freq[i - 2]) * 1.05;
		}
		for(unsigned int i = 0; i < 13; ++i) {
			if(freqTarget >= work_freq[i] &&
			   freqTarget < work_freq[i + 1]) {
				float freqLow = work_freq[i];
				float freqHigh = work_freq[i + 1];
				float valueLow = work[i];
				float valueHigh = work[i + 1];

				float delta = (freqTarget - freqLow) / (freqHigh - freqLow);

				return valueLow + (valueHigh - valueLow) * delta;
			}
		}

		return work[13];
	}

	// Pick the extremes
	if(freqTarget == cog_equalizer_bands[0])
		return [[preset objectForKey:[cog_equalizer_items objectAtIndex:1]] floatValue];
	else if(freqTarget == cog_equalizer_bands[9])
		return [[preset objectForKey:[cog_equalizer_items objectAtIndex:10]] floatValue];

	// interpolation time! linear is fine for this

	for(size_t i = 0; i < 9; ++i) {
		if(freqTarget >= cog_equalizer_bands[i] &&
		   freqTarget < cog_equalizer_bands[i + 1]) {
			float freqLow = cog_equalizer_bands[i];
			float freqHigh = cog_equalizer_bands[i + 1];
			float valueLow = [[preset objectForKey:[cog_equalizer_items objectAtIndex:i + 1]] floatValue];
			float valueHigh = [[preset objectForKey:[cog_equalizer_items objectAtIndex:i + 2]] floatValue];

			float delta = (freqTarget - freqLow) / (freqHigh - freqLow);

			return valueLow + (valueHigh - valueLow) * delta;
		}
	}

	return 0.0;
}

static void interpolateBands(float *out, const NSDictionary *preset) {
	for(size_t i = 0; i < 31; ++i) {
		out[i] = interpolatePoint(preset, apple_equalizer_bands[i]);
	}
}

static float getPreamp(const NSDictionary *preset) {
	return [[preset objectForKey:[cog_equalizer_items objectAtIndex:11]] floatValue];
}

static void loadPresets() {
	if([equalizer_presets_processed count]) return;

	NSURL *url = [[NSBundle mainBundle] URLForResource:@"Cog.q1" withExtension:@"json"];

	NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:[url path]];
	if(fileHandle) {
		NSError *err;
		NSData *data;
		if(@available(macOS 10.15, *)) {
			data = [fileHandle readDataToEndOfFileAndReturnError:&err];
		} else {
			data = [fileHandle readDataToEndOfFile];
			err = nil;
		}
		if(!err && data) {
			equalizer_presets = json_parse(data.bytes, data.length);

			if(equalizer_presets->type == json_object &&
			   equalizer_presets->u.object.length == 2 &&
			   strncmp(equalizer_presets->u.object.values[0].name, "type", equalizer_presets->u.object.values[0].name_length) == 0 &&
			   equalizer_presets->u.object.values[0].value->type == json_string &&
			   strncmp(equalizer_presets->u.object.values[0].value->u.string.ptr, [_cog_equalizer_type UTF8String], equalizer_presets -> u.object.values[0].value->u.string.length) == 0 &&
			   strncmp(equalizer_presets->u.object.values[1].name, "presets", equalizer_presets->u.object.values[1].name_length) == 0 &&
			   equalizer_presets->u.object.values[1].value->type == json_array) {
				// Got the array of presets
				NSMutableArray *array = [[NSMutableArray alloc] init];
				NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];

				size_t count = equalizer_presets->u.object.values[1].value->u.array.length;
				json_value **values = equalizer_presets->u.object.values[1].value->u.array.values;

				cog_equalizer_items = _cog_equalizer_items();

				const size_t cog_object_minimum = [cog_equalizer_items count];

				for(size_t i = 0; i < count; ++i) {
					if(values[i]->type == json_object) {
						NSMutableArray<NSString *> *extraGenres = [[NSMutableArray alloc] init];
						size_t object_items = values[i]->u.object.length;
						json_object_entry *object_entry = values[i]->u.object.values;
						size_t requiredItemsPresent = 0;
						if(object_items >= cog_object_minimum) {
							NSMutableDictionary *equalizerItem = [[NSMutableDictionary alloc] init];
							for(size_t j = 0; j < object_items; ++j) {
								NSString *key = [NSString stringWithUTF8String:object_entry[j].name];
								NSInteger index = [cog_equalizer_items indexOfObject:key];
								if(index != NSNotFound) {
									if(index == 0 && object_entry[j].value->type == json_string) {
										NSString *name = [NSString stringWithUTF8String:object_entry[j].value->u.string.ptr];
										[equalizerItem setObject:name forKey:key];
										++requiredItemsPresent;
									} else if(object_entry[j].value->type == json_integer) {
										int64_t value = object_entry[j].value->u.integer;
										float floatValue = ((value <= 401 && value >= 1) ? ((float)(value - 201) / 10.0) : 0.0);
										[equalizerItem setObject:@(floatValue) forKey:key];
										++requiredItemsPresent;
									}
								} else if([key isEqualToString:cog_equalizer_extra_genres]) {
									// Process alternate genre matches
									if(object_entry[j].value->type == json_array) {
										size_t value_count = object_entry[j].value->u.array.length;
										json_value **values = object_entry[j].value->u.array.values;
										for(size_t k = 0; k < value_count; ++k) {
											if(values[k]->type == json_string) {
												[extraGenres addObject:[NSString stringWithUTF8String:values[i]->u.string.ptr]];
											}
										}
									}
								}
							}

							if(requiredItemsPresent == cog_object_minimum) {
								// Add the base item
								NSDictionary *outItem = [NSDictionary dictionaryWithDictionary:equalizerItem];
								[array addObject:outItem];
								[dict setObject:outItem forKey:[outItem objectForKey:@"name"]];

								// Add the alternate genres, if any
								for(NSString *genre in extraGenres) {
									[dict setObject:outItem forKey:genre];
								}
							}
						}
					}
				}

				equalizer_presets_processed = [NSArray arrayWithArray:array];
				equalizer_presets_by_name = [NSDictionary dictionaryWithDictionary:dict];
			}
		}
		[fileHandle closeFile];

		json_value_free(equalizer_presets);
		equalizer_presets = NULL;
	}
}

void equalizerApplyGenre(AudioUnit au, const NSString *genre) {
	equalizerGenre = genre;
	if([[NSUserDefaults standardUserDefaults] boolForKey:@"GraphicEQtrackgenre"]) {
		loadPresets();

		NSDictionary *preset = [equalizer_presets_by_name objectForKey:genre];
		if(!preset) {
			// Find a match
			if(genre && ![genre isEqualToString:@""]) {
				NSUInteger matchLength = 0;
				NSString *lowerCaseGenre = [genre lowercaseString];
				for(NSString *key in [equalizer_presets_by_name allKeys]) {
					NSString *lowerCaseKey = [key lowercaseString];
					if([lowerCaseGenre containsString:lowerCaseKey]) {
						if([key length] > matchLength) {
							matchLength = [key length];
							preset = [equalizer_presets_by_name objectForKey:key];
						}
					}
				}
			}

			if(!preset) {
				preset = [equalizer_presets_by_name objectForKey:equalizerDefaultGenre];
			}
		}
		if(preset) {
			NSInteger index = [equalizer_presets_processed indexOfObject:preset];
			[[NSUserDefaults standardUserDefaults] setInteger:index forKey:@"GraphicEQpreset"];

			equalizerApplyPreset(au, preset);
		}
	}
}

void equalizerLoadPreset(AudioUnit au) {
	NSInteger index = [[NSUserDefaults standardUserDefaults] integerForKey:@"GraphicEQpreset"];
	if(index >= 0 && index < [equalizer_presets_processed count]) {
		NSDictionary *preset = [equalizer_presets_processed objectAtIndex:index];
		equalizerApplyPreset(au, preset);
	} else if(au) {
		@synchronized(cog_equalizer_band_settings) {
			if(!cog_equalizer_band_settings)
				cog_equalizer_band_settings = _cog_equalizer_band_settings();
		}

		float preamp = [[NSUserDefaults standardUserDefaults] floatForKey:[cog_equalizer_band_settings objectAtIndex:0]];

		AudioUnitSetParameter(au, kGraphicEQParam_NumberOfBands, kAudioUnitScope_Global, 0, 1, 0);
		for(NSInteger i = 1; i < [cog_equalizer_band_settings count]; ++i) {
			float value = [[NSUserDefaults standardUserDefaults] floatForKey:[cog_equalizer_band_settings objectAtIndex:i]];
			AudioUnitSetParameter(au, (int)(i - 1), kAudioUnitScope_Global, 0, value + preamp, 0);
		}
	}
}

void equalizerApplyPreset(AudioUnit au, const NSDictionary *preset) {
	if(au && preset) {
		@synchronized(cog_equalizer_band_settings) {
			if(!cog_equalizer_band_settings)
				cog_equalizer_band_settings = _cog_equalizer_band_settings();
		}

		AudioUnitParameterValue paramValue = 0;
		if(AudioUnitGetParameter(au, kGraphicEQParam_NumberOfBands, kAudioUnitScope_Global, 0, &paramValue))
			return;

		float presetValues[31];
		interpolateBands(presetValues, preset);

		float preamp = getPreamp(preset);

		[[NSUserDefaults standardUserDefaults] setFloat:preamp forKey:[cog_equalizer_band_settings objectAtIndex:0]];
		AudioUnitSetParameter(au, kGraphicEQParam_NumberOfBands, kAudioUnitScope_Global, 0, 1, 0);
		for(unsigned int i = 0; i < 31; ++i) {
			[[NSUserDefaults standardUserDefaults] setFloat:presetValues[i] forKey:[cog_equalizer_band_settings objectAtIndex:i + 1]];
			AudioUnitSetParameter(au, i, kAudioUnitScope_Global, 0, presetValues[i], 0);
		}
	}
}

@implementation EqPresetBehaviorArrayController

- (void)awakeFromNib {
	[self removeObjects:[self arrangedObjects]];

	loadPresets();

	for(NSDictionary *preset in equalizer_presets_processed) {
		[self addObject:@{ @"name": [preset objectForKey:@"name"], @"preference": [preset objectForKey:@"name"] }];
	}

	[self addObject:@{ @"name": @"Custom", @"preference": @"Custom" }];
}

@end

@implementation EqualizerSlider

- (void)awakeFromNib {
	if(@available(macOS 10.12.2, *)) {
		[self setTrackFillColor:[NSColor systemGrayColor]];
	}
}

@end

@interface EqualizerWindowController ()

@end

@implementation EqualizerWindowController

+ (void)initialize {
	@synchronized(cog_equalizer_band_settings) {
		if(!cog_equalizer_band_settings)
			cog_equalizer_band_settings = _cog_equalizer_band_settings();
	}
}

- (id)init {
	return [super initWithWindowNibName:@"Equalizer"];
}

- (void)windowDidLoad {
	[super windowDidLoad];

	[self changePreset:presetSelector];

	[self handleMouseEvents];
}

- (void)setEQ:(AudioUnit)au {
	self->au = au;
}

- (IBAction)toggleWindow:(id)sender {
	if([[self window] isVisible])
		[[self window] orderOut:self];
	else
		[self showWindow:self];
}

- (IBAction)toggleEnable:(id)sender {
}

- (IBAction)toggleTracking:(id)sender {
	equalizerApplyGenre(au, equalizerGenre);

	[self changePreset:presetSelector];
}

- (IBAction)flattenEQ:(id)sender {
	NSDictionary *preset = [equalizer_presets_by_name objectForKey:equalizerDefaultGenre];
	NSInteger index = [equalizer_presets_processed indexOfObject:preset];

	[presetSelector selectItemAtIndex:index];
	[[NSUserDefaults standardUserDefaults] setInteger:index forKey:@"GraphicEQpreset"];

	[self changePreset:presetSelector];
}

- (EqualizerSlider *)sliderForIndex:(NSInteger)index {
	switch(index) {
		case 0:
			return eqPreamp;
		case 1:
			return eq20Hz;
		case 2:
			return eq25Hz;
		case 3:
			return eq31p5Hz;
		case 4:
			return eq40Hz;
		case 5:
			return eq50Hz;
		case 6:
			return eq63Hz;
		case 7:
			return eq80Hz;
		case 8:
			return eq100Hz;
		case 9:
			return eq125Hz;
		case 10:
			return eq160Hz;
		case 11:
			return eq200Hz;
		case 12:
			return eq250Hz;
		case 13:
			return eq315Hz;
		case 14:
			return eq400Hz;
		case 15:
			return eq500Hz;
		case 16:
			return eq630Hz;
		case 17:
			return eq800Hz;
		case 18:
			return eq1kHz;
		case 19:
			return eq1p2kHz;
		case 20:
			return eq1p6kHz;
		case 21:
			return eq2kHz;
		case 22:
			return eq2p5kHz;
		case 23:
			return eq3p1kHz;
		case 24:
			return eq4kHz;
		case 25:
			return eq5kHz;
		case 26:
			return eq6p3kHz;
		case 27:
			return eq8kHz;
		case 28:
			return eq10kHz;
		case 29:
			return eq12kHz;
		case 30:
			return eq16kHz;
		case 31:
			return eq20kHz;
		default:
			return nil;
	}
}

- (IBAction)levelPreamp:(id)sender {
	float preamp = [eqPreamp floatValue];

	float maxValue = 0.0;
	for(NSInteger i = 1; i < [cog_equalizer_band_settings count]; ++i) {
		float value = [[self sliderForIndex:i] floatValue];
		if(value > maxValue) maxValue = value;
	}

	if(maxValue > 0.0 && preamp != -maxValue) {
		[presetSelector selectItemAtIndex:[equalizer_presets_processed count]];
		[[NSUserDefaults standardUserDefaults] setInteger:[equalizer_presets_processed count] forKey:@"GraphicEQpreset"];

		[eqPreamp setFloatValue:-maxValue];
		[[NSUserDefaults standardUserDefaults] setFloat:-maxValue forKey:[cog_equalizer_band_settings objectAtIndex:0]];
	}
}

- (IBAction)adjustSlider:(id)sender {
	NSInteger tag = [sender tag];

	NSInteger count = [equalizer_presets_processed count];
	if([[NSUserDefaults standardUserDefaults] integerForKey:@"GraphicEQpreset"] != count) {
		[[NSUserDefaults standardUserDefaults] setInteger:count forKey:@"GraphicEQpreset"];
		[presetSelector selectItemAtIndex:count];
	}

	if(tag == 0) {
		float preamp = [eqPreamp floatValue];
		[[NSUserDefaults standardUserDefaults] setFloat:preamp forKey:[cog_equalizer_band_settings objectAtIndex:0]];
	} else if(tag < [cog_equalizer_band_settings count]) {
		float value = [sender floatValue];
		[[NSUserDefaults standardUserDefaults] setFloat:value forKey:[cog_equalizer_band_settings objectAtIndex:tag]];
		if(au)
			AudioUnitSetParameter(au, (int)(tag - 1), kAudioUnitScope_Global, 0, value, 0);
	}
}

- (void)changePreset:(id)sender {
	NSInteger index = [sender indexOfSelectedItem];

	if(index >= 0 && index < [equalizer_presets_processed count]) {
		NSDictionary *preset = [equalizer_presets_processed objectAtIndex:index];

		equalizerApplyPreset(au, preset);
	}
}

- (void)handleMouseEvents {
	[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskRightMouseDown | NSEventMaskRightMouseDragged
	                                      handler:^NSEvent *_Nullable(NSEvent *_Nonnull theEvent) {
		                                      if([theEvent window] == [self window]) {
			                                      NSPoint event_location = [theEvent locationInWindow];
			                                      NSPoint local_point = [self.window.contentView convertPoint:event_location fromView:nil];

			                                      for(NSInteger i = 0; i < [cog_equalizer_band_settings count]; ++i) {
				                                      NSSlider *slider = [self sliderForIndex:i];
				                                      if(NSPointInRect(local_point, [slider frame])) {
					                                      float sliderPosition = (MAX(MIN(local_point.y, 344.0), 40.0) - 40.0) / 152.0 - 1.0;
					                                      [slider setFloatValue:sliderPosition * 20.0];
					                                      [self adjustSlider:slider];
					                                      break;
				                                      }
			                                      }
		                                      }

		                                      return theEvent;
	                                      }];
}

@end
