use std::{cmp::min, convert::identity, iter};
use crate::{window_size::WindowSize, buffer::Buffer, cursor::Cursor};

#[derive(Debug)]
pub enum EditAction {
	// this is a hacky workaround to allow disjoint borrows for
	// undoing/redoing edits. because the undo/redo operations need
	// to borrow an EditAction from edit_history, but should never touch
	// edit_history themselves, we can swap out the action in question,
	// then swap it back in once the undo/redo is done
	Placeholder,
	Delete {
		primary_cursor: Cursor,
		cursors: Vec<Cursor>,
		
		primary_old_data: Vec<u8>,
		old_data: Vec<Vec<u8>>
	},
	Replace {
		primary_cursor: Cursor,
		cursors: Vec<Cursor>,
		
		primary_old_data: Vec<u8>,
		old_data: Vec<Vec<u8>>,
		
		new_byte: u8
	},
	// Insert {
	// 	primary_cursor: Cursor,
	// 	cursors: Vec<Cursor>,
	//  which side of cursor? append/insert
	// 	new_data: Vec<u8>
	// }
}

impl Buffer {
	pub fn execute_and_add(&mut self, edit_action: EditAction, window_size: WindowSize) {
		assert!(!matches!(edit_action, EditAction::Placeholder));
		
		self.execute_edit(&edit_action, window_size);
		
		if let Some(date) = self.time_traveling {
			self.edit_history.truncate(date);
			self.time_traveling = None;
			
			if self.last_saved_at.is_some_and(|it| it > date) {
				self.last_saved_at = None;
			}
		}
		
		self.edit_history.push(edit_action);
	}
	
	pub fn execute_edit(&mut self, edit_action: &EditAction, window_size: WindowSize) {
		match edit_action {
			EditAction::Placeholder => unreachable!(),
			EditAction::Delete { primary_cursor, cursors, .. } => {
				self.delete_at(*primary_cursor, cursors, window_size);
			},
			EditAction::Replace {
				primary_cursor, cursors, primary_old_data: _, old_data: _, new_byte
			} => self.replace_at_with(*primary_cursor, cursors, *new_byte, window_size),
		}
	}
	
	pub fn undo_edit(&mut self, edit_action: &EditAction, window_size: WindowSize) {
		match edit_action {
			EditAction::Placeholder => unreachable!(),
			EditAction::Delete {
				primary_cursor, cursors, primary_old_data, old_data
			} => self.undo_delete_at(
				*primary_cursor,
				cursors,
				primary_old_data,
				old_data,
				window_size
			),
			EditAction::Replace {
				primary_cursor, cursors, primary_old_data, old_data, ..
			} => self.undo_replace_at_with(
				*primary_cursor,
				cursors,
				primary_old_data,
				old_data,
				window_size
			),
		}
	}
	
	fn delete_at(
		&mut self,
		primary_cursor: Cursor,
		cursors: &[Cursor],
		window_size: WindowSize
	) {
		let mut bytes_deleted_so_far = 0;
		
		for cursor in cursors_in_order(primary_cursor, cursors) {
			self.contents.drain(
				(cursor.lower_bound() - bytes_deleted_so_far)..=
				(cursor.upper_bound() - bytes_deleted_so_far)
			);
			
			bytes_deleted_so_far += cursor.len();
		}
		
		self.primary_cursor.head = min(
			min(primary_cursor.head, primary_cursor.tail),
			self.max_contents_index()
		);
		self.primary_cursor.collapse();
		
		self.cursors = cursors
			.iter()
			.map(|cursor| Cursor::at(min(cursor.lower_bound(), self.max_contents_index())))
			.collect();
		 
		self.combine_cursors_if_overlapping();
		self.clamp_screen_to_primary_cursor(window_size);
	}
	
	fn undo_delete_at(
		&mut self,
		primary_cursor: Cursor,
		cursors: &[Cursor],
		primary_old_data: &[u8],
		old_data: &[Vec<u8>],
		window_size: WindowSize
	) {
		let primary_cursor_start = primary_cursor.lower_bound();
		
		self.contents.splice(
			primary_cursor_start..primary_cursor_start,
			primary_old_data.iter().copied()
		);
		
		for (cursor, old_data) in cursors.iter().zip(old_data) {
			let cursor_start = cursor.lower_bound();
			
			self.contents.splice(
				cursor_start..cursor_start,
				old_data.iter().copied()
			);
		}
		
		self.primary_cursor = primary_cursor;
		self.cursors = cursors.to_vec();
		self.clamp_screen_to_primary_cursor(window_size);
	}
	
	fn replace_at_with(
		&mut self,
		primary_cursor: Cursor,
		cursors: &[Cursor],
		new_byte: u8,
		window_size: WindowSize
	) {
		self.contents[primary_cursor.range()].fill(new_byte);
		
		for cursor in cursors {
			self.contents[cursor.range()].fill(new_byte);
		}
		
		self.primary_cursor = primary_cursor;
		self.cursors = cursors.to_vec();
		self.clamp_screen_to_primary_cursor(window_size);
	}
	
	fn undo_replace_at_with(
		&mut self,
		primary_cursor: Cursor,
		cursors: &[Cursor],
		primary_old_data: &[u8],
		old_data: &[Vec<u8>],
		window_size: WindowSize
	) {
		self.contents.splice(
			primary_cursor.range(),
			primary_old_data.iter().copied()
		);
		
		for (cursor, old_data) in cursors.iter().zip(old_data) {
			self.contents.splice(
				cursor.range(),
				old_data.iter().copied()
			);
		}
		
		self.primary_cursor = primary_cursor;
		self.cursors = cursors.to_vec();
		self.clamp_screen_to_primary_cursor(window_size);
	}
}

fn cursors_in_order(
	primary_cursor: Cursor,
	cursors: &[Cursor]
) -> impl Iterator<Item=Cursor> {
	let primary_cursor_index = cursors
		.binary_search_by_key(&primary_cursor.head, |cursor| cursor.head)
		.unwrap_or_else(identity);
	
	cursors.iter()
		.copied()
		.take(primary_cursor_index)
		.chain(iter::once(primary_cursor))
		.chain(cursors.iter().copied().skip(primary_cursor_index))
}
