mod horse;
mod joke;
mod react;
mod starboard;
mod sus;
mod xbasic;

use crate::handlers::horse::HorseHandler;
use crate::handlers::joke::*;
use crate::handlers::react::*;
use crate::handlers::starboard::StarboardHandler;
use crate::handlers::sus::*;
use crate::handlers::xbasic::*;

use diesel::{Connection, PgConnection};
use dotenv::dotenv;
use serenity::async_trait;
use serenity::model::channel::Message;
use serenity::model::prelude::{Reaction, Ready};
use serenity::prelude::*;
use std::env;
use std::sync::Arc;
use tokio::sync::Mutex;

#[async_trait]
pub(crate) trait LineHandler: Send + Sync {
	async fn message(&self, ctx: &Context, msg: &Message) {
		for line in msg.content.split('\n') {
			self.line(ctx, msg, line).await
		}
	}

	async fn line(&self, _ctx: &Context, _msg: &Message, _line: &str) {}
}

#[async_trait]
pub(crate) trait ReactionHandler: Send + Sync {
	async fn reaction_add(&self, ctx: &Context, reaction: &Reaction);
	async fn reaction_del(&self, ctx: &Context, reaction: &Reaction);
}

pub(crate) struct Dispatcher {
	handlers: Vec<Box<dyn LineHandler>>,
	reacts: Vec<Box<dyn ReactionHandler>>,
}

#[async_trait]
impl EventHandler for Dispatcher {
	async fn message(&self, ctx: Context, msg: Message) {
		for h in &self.handlers {
			h.message(&ctx, &msg).await;
		}
	}

	async fn reaction_add(&self, ctx: Context, reaction: Reaction) {
		for r in &self.reacts {
			r.reaction_add(&ctx, &reaction).await;
		}
	}

	async fn reaction_remove(&self, ctx: Context, reaction: Reaction) {
		for r in &self.reacts {
			r.reaction_del(&ctx, &reaction).await;
		}
	}

	async fn ready(&self, _: Context, ready: Ready) {
		println!("{} is connected", ready.user.name);
	}
}

impl Default for Dispatcher {
	fn default() -> Self {
		let conn = Arc::new(Mutex::new(establish_connection()));

		Self {
			handlers: vec![
				Box::new(XbasicHandler::new(conn.clone())),
				Box::new(JokeHandler::default()),
				Box::new(ReactHandler::default()),
				Box::new(SusHandler::default()),
				Box::new(HorseHandler::default()),
			],
			reacts: vec![Box::new(StarboardHandler::new(conn))],
		}
	}
}

fn establish_connection() -> PgConnection {
	dotenv().ok();

	let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
	PgConnection::establish(&database_url).expect("Error connecting to database")
}