#[macro_use]
extern crate brev;
#[macro_use]
extern crate lazy_static;
extern crate handlebars;
extern crate regex;
extern crate rustc_serialize;

// nine::spec::Message

struct Message {
  name:         String,
  message_type: MessageType,
  number:       u8,
  fields:       Vec<Field>
}

enum MessageType {
  Request,
  Response
}

struct Field {
  name: String,
  times: Option<String>,
  field_type: FieldType
}

#[derive(Debug, Eq, PartialEq, Copy, Clone)]
enum FieldType {
  U8,
  U16,
  U32,
  U64,
  QID,
  Stat,
  Data,
  String,
}

impl FieldType {
  fn numeric(self) -> bool {
    self == FieldType::U8 ||
    self == FieldType::U16 ||
    self == FieldType::U32 ||
    self == FieldType::U64
  }
}

impl Message {
  fn full_name(&self) -> String {
    let mut full = String::new();
    full.push(match self.message_type {
      MessageType::Request => 'T',
      MessageType::Response => 'R',
    });
    full.push_str(&self.name);
    full
  }
}

impl std::fmt::Display for Message {
  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    try!(write!(f, "size[4] {}:{} tag[2]", self.full_name(), self.number));
    for field in &self.fields {
      try!(write!(f, " {}", field))
    }
    Ok(())
  }
}

impl std::fmt::Display for Field {
  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    match self.times {
      Some(ref times) => write!(f, "{}*({}[{}])", times, self.name, self.field_type),
      None => write!(f, "{}[{}]", self.name, self.field_type)
    }
  }
}

impl std::fmt::Display for FieldType {
  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    let s = match self {
      &FieldType::U8 => "1",
      &FieldType::U16 => "2",
      &FieldType::U32 => "4",
      &FieldType::U64 => "8",
      &FieldType::QID => "13",
      &FieldType::Stat => "n",
      &FieldType::Data => "count",
      &FieldType::String => "s"
    };
    write!(f, "{}", s)
  }
}

struct Context {
  messages: Vec<Message>
}

impl rustc_serialize::json::ToJson for Context {
  fn to_json(&self) -> rustc_serialize::json::Json {
    let mut o = std::collections::BTreeMap::<String, rustc_serialize::json::Json>::new();
    o.insert("messages".to_string(), self.messages.to_json());
    o.to_json()
  }
}

impl rustc_serialize::json::ToJson for Message {
  fn to_json(&self) -> rustc_serialize::json::Json {
    let mut o = std::collections::BTreeMap::<String, rustc_serialize::json::Json>::new();
    o.insert("name".to_string(), self.name.to_json());
    o.to_json()
  }
}

macro_rules! re {
  ($name:ident $pattern:expr) => (
    lazy_static! {
      static ref $name: regex::Regex = regex::Regex::new($pattern).unwrap();
    }
  )
}

fn field(input: &str) -> (&str, Option<Field>) {
  re!{ SCALAR r"^\s*([a-z]+)\s*[[](1|2|4|8|13|count|n|s)]\s*" }
  re!{ ARRAY r"^\s*([a-z]+)\s*[*]\s*[(]([^)]*)[)]\s*" }
  if let Some(captures) = SCALAR.captures(input) {
    let name = captures.at(1).unwrap();
    let field_type = match captures.at(2).unwrap() {
      "1"     => FieldType::U8,
      "2"     => FieldType::U16,
      "4"     => FieldType::U32,
      "8"     => FieldType::U64,
      "13"    => FieldType::QID,
      "count" => FieldType::Data,
      "n"     => FieldType::Stat,
      "s"     => FieldType::String,
      _       => return (input, None),
    };
    let rest = &input[captures.at(0).unwrap().len()..];
    (rest, Some(Field{name: name.to_owned(), times: None, field_type: field_type}))
  } else if let Some(captures) = ARRAY.captures(input) {
    let times = captures.at(1).unwrap();
    let field_text = captures.at(2).unwrap();
    let rest = &input[captures.at(0).unwrap().len()..];
    if let (_, Some(mut field)) = field(field_text) {
      field.times = Some(times.to_owned());
      (rest, Some(field))
    } else {
      (input, None)
    }
  } else {
    (input, None)
  }
}

fn name(input: &str) -> (&str, Option<(MessageType, &str, u8)>) {
  re!(NAME r"\s*(T|R)([a-z]+)\s*:\s*([1-9][0-9]*)\s*");
  if let Some(captures) = NAME.captures(input) {
    let message_type = match captures.at(1).unwrap() {
      "T" => MessageType::Request,
      "R" => MessageType::Response,
      _ => return (input, None),
    };
    let name = captures.at(2).unwrap();
    let number = captures.at(3).unwrap().parse().unwrap();
    let rest = &input[captures.at(0).unwrap().len()..];
    (rest, Some((message_type, name, number)))
  } else {
    (input, None)
  }
}

fn error(line_number: usize, line: &str, message: &str) -> Error {
  Error {
    line_number: line_number,
    line: line.to_owned(),
    message: message.to_owned(),
  }
}

struct Error {
  line_number: usize,
  line: String,
  message: String,
}

impl std::fmt::Display for Error {
  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    write!(f, "line {}: {}\nerror: {}", self.line_number, self.line, self.message)
  }
}

fn parse(definition: &str) -> Result<Vec<Message>, Error> {
  let mut messages = vec![];
  let mut names = std::collections::HashSet::<String>::new();
  let mut stripped = String::new();

  let mut n = 0;
  for line in definition.lines() {
    // advance line number
    n += 1;

    let mut s = line;

    // strip comments
    if let Some(i) = s.find('#') {
      s = &s[0..i];
    }

    // strip leading and trailing whitespace
    s = s.trim();

    // skip blank lines
    if s.len() == 0 {
      continue;
    }

    stripped = stripped + s + "\n";

    let (s, size) = field(s);
    match size {
      Some(field) => if field.name != "size" || field.field_type != FieldType::U32 {
        return Err(error(n, line, "each message must begin with size[4]"));
      },
      None => return Err(error(n, line, "each message must begin with size[4]")),
    }

    let (s, name) = name(s);
    let (message_type, message_name, number) = match name {
      Some(result) => result,
      None => return Err(error(n, line, "size tag must be followed by a message name")),
    };

    let (s, tag) = field(s);
    match tag {
      Some(field) => if field.name != "tag" || field.field_type != FieldType::U16 {
        return Err(error(n, line, "each message must have tag[2] as its first field"));
      },
      None => return Err(error(n, line, "each message must have tag[2] as its first field")),
    }

    let mut fields: Vec<Field> = vec![];
    let mut s = s;
    while s.len() > 0 {
      let (rest, option) = field(s);
      let field = match option {
        Some(field) => field,
        _ => return Err(error(n, line, &("bad field in message: ".to_owned() + rest))),
      };
      if let Some(ref times) = field.times {
        if let Some(previous) = fields.last() {
          if &previous.name != times || !previous.field_type.numeric() {
            return Err(error(n, line, "array field must be preceded by numeric times field"));
          }
        } else {
          return Err(error(n, line, "array field must be preceded by times field"));
        }
      };
      fields.push(field);
      s = rest;
    }

    let message = Message {
      name: message_name.to_owned(),
      message_type: message_type,
      number: number,
      fields: fields
    };
    let full_name = message.full_name();
    if names.contains(&full_name) {
      return Err(error(n, line, &format!("duplicate message: {}", full_name)));
    };
    names.insert(full_name);
    messages.push(message);
  }

  {
    let mut requests = std::collections::HashMap::<String, &Message>::new();
    let mut responses = std::collections::HashMap::<String, &Message>::new();

    for message in &messages {
      match message.message_type {
        MessageType::Request => requests.insert(message.name.clone(), message),
        MessageType::Response => responses.insert(message.name.clone(), message),
      };
    };

    for name in requests.keys() {
      if !responses.contains_key(name) {
        die!("request without corresponding response: {}", name)
      }
    }

    for (name, response) in &responses {
      match requests.get(name) {
        Some(request) => if response.number != request.number + 1 {
          warn!("Response number not equal to request number + 1: {}", name);
          warn!("{}: {}", request.full_name(), request.number);
          die!("{}: {}", response.full_name(), response.number);
        },
        None => if name != "error" { die!("response without corresponding request: {}", name) }
      }
    }

    let mut round_tripped = String::new();
    for message in &messages {
      round_tripped.push_str(&format!("{}\n", message));
    }

    if round_tripped != stripped {
      warn!("round tripped string does not match input:");
      warn!("input:\n{}", stripped);
      warn!("round_tripped:\n{}", round_tripped);
    }
  }

  Ok(messages)
}

fn main() {
  let text = brev::slurp("9p2000.txt");

  let messages = match parse(&text) {
    Ok(messages) => messages,
    Err(error) => brev::die(error),
  };

  for message in &messages {
    println!("{}", message);
  }

  let mut handlebars = handlebars::Handlebars::new();
  let path = std::path::Path::new("templates/protocol.rs");
  if let Err(error) = handlebars.register_template_file("protocol", path) {
    die!("error parsing template: {}", error);
  }
  let rendered = match handlebars.render("protocol", &Context{messages: messages}) {
    Err(error) => die!("error rendering template: {:?}", error),
    Ok(s) => s
  };
  brev::say(rendered);

  //brev::die("done.");
}