Documentation
#[macro_use]
extern crate lazy_static;

extern crate regex;

mod tests;

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

#[derive(Copy, Clone)]
pub enum MessageType {
  Request,
  Response
}

impl MessageType {
  fn char(self) -> char {
    match self {
      MessageType::Request => 'T',
      MessageType::Response => 'R',
    }
  }
}

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

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

impl FieldType {
  fn integer(self) -> bool {
    match self {
      FieldType::U8 | FieldType::U16 | FieldType::U32 | FieldType::U64 => true,
      _ => false,
    }
  }
}

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::Bytes => "count",
      &FieldType::String => "s"
    };
    write!(f, "{}", s)
  }
}

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::Bytes,
      "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) -> Result<Vec<Message>, String> {
  Err(if line_number > 0 {
    format!("line {}: {}\nerror: {}", line_number, line, message)
  } else {
    format!("error: {}", message)
  })
}

pub fn strip(definition: &str) -> Vec<(usize, &str)> {
  definition.lines().enumerate().flat_map(|(i, mut line)| {
    if let Some(i) = line.find('#') {
      line = &line[0..i]
    }

    line = line.trim();

    if line.len() == 0 {
      None
    } else {
      Some((i, line))
    }
  }).collect()
}

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

  for (i, line) in strip(definition) {
    let n = i + 1;
    let (s, size) = field(line);
    match size {
      Some(field) => if field.name != "size" || field.field_type != FieldType::U32 {
        return error(n, line, "each message must begin with size[4]");
      },
      None => return 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 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 error(n, line, "each message must have tag[2] as its first field");
      },
      None => return 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 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.integer() {
            return error(n, line, "array field must be preceded by integer times field");
          }
        } else {
          return error(n, line, "array field must be preceded by times field");
        }
      };
      fields.push(field);
      s = rest;
    }

    let message = Message {
      name: message_name.to_owned(),
      full_name: message_type.char().to_string() + message_name,
      message_type: message_type,
      number: number,
      fields: fields
    };
    if names.contains(&message.full_name) {
      return error(n, line, &format!("duplicate message name: {}", message.full_name));
    };
    names.insert(message.full_name.clone());
    if numbers.contains(&message.number) {
      let msg = &format!("duplicate message number: {}:{}", message.full_name, message.number);
      return error(n, line, msg);
    }
    numbers.insert(message.number);
    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) {
        return error(0, "", &format!("request without corresponding response: {}", name));
      }
    }

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

  Ok(messages)
}