use super::{Repl, InterpreterDirectiveOutput};
use crate::repl::help::help;
use crate::language::{LangMetaRequest, LangMetaResponse, DebugAsk, DebugResponse}; 
use itertools::Itertools;
use std::fmt::Write as FmtWrite;

#[derive(Debug, Clone)]
pub enum DirectiveAction {
  Null,
  Help,
  QuitProgram,
  ListPasses,
  ShowImmediate,
  Show,
  Hide,
  TotalTimeOff,
  TotalTimeOn,
  StageTimeOff,
  StageTimeOn,
  Doc,
}

impl DirectiveAction {
  pub fn perform(&self, repl: &mut Repl, arguments: &[&str]) -> InterpreterDirectiveOutput {
    use DirectiveAction::*;
    match self {
      Null => None,
      Help => help(repl, arguments),
      QuitProgram => {
        repl.save_before_exit();
        ::std::process::exit(0)
      },
      ListPasses => {
        let language_state = repl.get_cur_language_state();
        let pass_names = match language_state.request_meta(LangMetaRequest::StageNames) {
          LangMetaResponse::StageNames(names) => names,
          _ => vec![],
        };

        let mut buf = String::new();
        for pass in pass_names.iter().map(|name| Some(name)).intersperse(None) {
          match pass {
            Some(pass) => write!(buf, "{}", pass).unwrap(),
            None => write!(buf, " -> ").unwrap(),
          }
        }
        Some(buf)
      },
      ShowImmediate => {
        let cur_state = repl.get_cur_language_state();
        let stage_name = match arguments.get(0) {
          Some(s) => s.to_string(),
          None => return Some(format!("Must specify a thing to debug")),
        };
        let meta = LangMetaRequest::ImmediateDebug(DebugAsk::ByStage { stage_name: stage_name.clone(), token: None });
        let meta_response = cur_state.request_meta(meta);

        let response = match meta_response {
          LangMetaResponse::ImmediateDebug(DebugResponse { ask, value }) => match  ask {
            DebugAsk::ByStage { stage_name: ref this_stage_name, ..} if *this_stage_name == stage_name => value,
            _ => return Some(format!("Wrong debug stage"))
          },
          _ => return Some(format!("Invalid language meta response")),
        };
        Some(response)
      },
      Show => {
        let this_stage_name = match arguments.get(0) {
          Some(s) => s.to_string(),
          None => return Some(format!("Must specify a stage to show")),
        };
        let token = arguments.get(1).map(|s| s.to_string());
        repl.options.debug_asks.retain(|ask| match ask {
          DebugAsk::ByStage { stage_name, .. } if *stage_name == this_stage_name => false,
          _ => true
        });

        let ask = DebugAsk::ByStage { stage_name: this_stage_name, token };
        repl.options.debug_asks.insert(ask);
        None
      },
      Hide => {
        let stage_name_to_remove = match arguments.get(0) {
          Some(s) => s.to_string(),
          None => return Some(format!("Must specify a stage to hide")),
        };
        repl.options.debug_asks.retain(|ask| match ask {
          DebugAsk::ByStage { stage_name, .. } if *stage_name == stage_name_to_remove => false,
          _ => true
        });
        None
      },
      TotalTimeOff => total_time_off(repl, arguments),
      TotalTimeOn => total_time_on(repl, arguments),
      StageTimeOff => stage_time_off(repl, arguments),
      StageTimeOn => stage_time_on(repl, arguments),
      Doc => doc(repl, arguments),
    }
  }
}

fn total_time_on(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
  repl.options.show_total_time = true;
  None
}

fn total_time_off(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
  repl.options.show_total_time = false;
  None
}

fn stage_time_on(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
  repl.options.show_stage_times = true;
  None
}

fn stage_time_off(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
  repl.options.show_stage_times = false;
  None
}

fn doc(repl: &mut Repl, arguments: &[&str]) -> InterpreterDirectiveOutput {
  arguments.get(0).map(|cmd| {
    let source = cmd.to_string();
    let meta = LangMetaRequest::Docs { source };
    let cur_state = repl.get_cur_language_state();
    match cur_state.request_meta(meta) {
      LangMetaResponse::Docs { doc_string } => Some(doc_string),
      _ => Some(format!("Invalid doc response"))
    }
  }).unwrap_or(Some(format!(":docs needs an argument")))
}