//! Requires the 'framework' feature flag be enabled in your project's
//! `Cargo.toml`.
//!
//! This can be enabled by specifying the feature in the dependency section:
//!
//! ```toml
//! [dependencies.serenity]
//! git = "https://github.com/serenity-rs/serenity.git"
//! features = ["framework", "standard_framework"]
//! ```

extern crate chrono;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_migrations;
extern crate dotenv;
extern crate env_logger;
#[macro_use]
extern crate log;
extern crate api_swgoh_gg;
extern crate numerals;
extern crate serenity;
extern crate time;

use std::collections::HashSet;
use std::env;

use dotenv::dotenv;
use serenity::{
    framework::standard::{
        help_commands, macros::help, Args, CommandGroup, CommandResult, HelpOptions,
        StandardFramework,
    },
    model::{
        event::ResumedEvent,
        gateway::Ready,
        prelude::{Message, UserId},
    },
    prelude::*,
    utils::MessageBuilder,
};

use commands::farming::*;
use commands::general::*;
use commands::unit::*;
use internal::establish_connection;

pub mod commands;
pub mod internal;
pub mod models;
pub mod schema;

embed_migrations!();

#[help]
fn my_help(
    context: &mut Context,
    msg: &Message,
    args: Args,
    help_options: &'static HelpOptions,
    groups: &[&'static CommandGroup],
    owners: HashSet<UserId>,
) -> CommandResult {
    help_commands::with_embeds(context, msg, args, help_options, groups, owners)
}

struct Handler;

impl EventHandler for Handler {
    fn ready(&self, _: Context, ready: Ready) {
        info!("Connected as {}", ready.user.name);
    }

    fn resume(&self, _: Context, _: ResumedEvent) {
        info!("Resumed");
    }
}

fn main() {
    // This will load the environment variables located at `./.env`, relative to
    // the CWD. See `./.env` for an example on how to structure this.
    dotenv().ok();

    // Initialize the logger to use environment variables.
    //
    // In this case, a good default is setting the environment variable
    // `RUST_LOG` to debug`.
    env_logger::init();

    let database_url = if "PROD"
        == env::var("APP_ENV")
            .expect("APP_ENV must be set")
            .to_uppercase()
    {
        info!("Starting bot in prod environment");
        env::var("DATABASE_URL").expect("DATABASE_URL must be set")
    } else {
        info!("Starting bot in dev environment");
        env::var("DATABASE_URL_DEV").expect("DATABASE_URL_DEV must be set")
    };
    debug!("db url: {}", database_url);

    // Initialize diesel
    let conn = establish_connection();

    embedded_migrations::run_with_output(&conn, &mut std::io::stdout()).unwrap();

    let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");

    let mut client = Client::new(&token, Handler).expect("Err creating client");

    // We will fetch your bot's owners and id
    let (owners, bot_id) = match client.cache_and_http.http.get_current_application_info() {
        Ok(info) => {
            let mut owners = HashSet::new();
            owners.insert(info.owner.id);

            (owners, info.id)
        }
        Err(why) => panic!("Could not access application info: {:?}", why),
    };

    client.with_framework(
        StandardFramework::new()
            .configure(|c| {
                c.with_whitespace(true)
                    .on_mention(Some(bot_id))
                    .prefix("!")
                    .owners(owners)
            })
            .unrecognised_command(|ctx, msg, unrecognised_cmd_name| {
                warn!("Unrecognised cmd: {}", unrecognised_cmd_name);
                if let Err(e) = msg
                    .channel_id
                    .say(ctx, format!("Unknown command: {}", unrecognised_cmd_name))
                {
                    error!("{}", e)
                }
            })
            .before(|ctx, msg, command_name| {
                msg.react(ctx, '✋').unwrap_or_else(|e| {
                    error!("[E001] Error during reaction in before cmd: {}", e);
                });

                info!(
                    "Got command '{}' by user '{}'",
                    command_name, msg.author.name
                );

                true // if `before` returns false, command processing doesn't happen.
            })
            .after(|ctx, msg, command_name, error| match error {
                Ok(()) => {
                    msg.react(ctx, '👌').unwrap_or_else(|e| {
                        error!("[E002] Error during reaction in after cmd: {}", e);
                    });

                    info!("Processed command '{}'", command_name)
                }
                Err(why) => {
                    msg.react(&ctx, '☠').unwrap_or_else(|e| {
                        error!("[E003] Error during reaction in after cmd: {}", e);
                    });

                    error!("Command '{}' returned error {:?}", command_name, why);

                    if let Err(e) = msg.channel_id.say(
                        &ctx,
                        MessageBuilder::new()
                            .mention(&msg.author)
                            .push(format!(" {}", why.0))
                            .build(),
                    ) {
                        error!("[E004] Error during send of error: {}", e);
                    }
                }
            })
            .help(&MY_HELP)
            .group(&GENERAL_GROUP)
            .group(&UNITS_GROUP)
            .group(&FARM_GROUP),
    );

    if let Err(why) = client.start() {
        error!("Client error: {:?}", why);
    }
}
