///|
priv enum Value {
  Empty
  Number(Double)
  Text(String)
  NumericString(String, Double)
}

///|
fn finite(n : Double) -> Value {
  Number(n)
}

///|
fn input_value(text : String) -> Value {
  let trimmed = text.trim().to_owned()
  let (n, end, overflow) = numeric_prefix(trimmed)
  if end > 0 && end == trimmed.to_array().length() && !overflow {
    return NumericString(text, n)
  }
  Text(text)
}

///|
fn Value::text(self : Value) -> String {
  match self {
    Empty => ""
    Number(n) => n.to_string()
    Text(s) | NumericString(s, _) => s
  }
}

///|
fn Value::number(self : Value) -> Double {
  match self {
    Empty => 0.0
    Number(n) | NumericString(_, n) => n
    Text(s) => {
      let (n, _, _) = numeric_prefix(s.trim().to_owned())
      n
    }
  }
}

///|
fn Value::truth(self : Value) -> Bool {
  match self {
    Empty => false
    Number(n) | NumericString(_, n) => n != 0.0
    Text(s) => !s.is_empty()
  }
}

///|
fn boolean(b : Bool) -> Value {
  Number(if b { 1.0 } else { 0.0 })
}

///|
priv struct State {
  vars : Map[String, Value]
  arrays : Map[String, Map[String, Value]]
  fields : Array[Value]
  mut fields_ready : Bool
  mut reuse_csv_fields : Bool
  mut saved_fs : String
  mut input_mode : CSVMode?
  mut output_mode : CSVMode?
  mut field_names : Array[String]?
  mut record : String
  mut record_true_string : Bool
  output : Array[String]
  mut output_size : Int
  mut steps : Int
  step_limit : Int
  mut entries : Int
  mut eval_depth : Int
  regexes : Map[String, Regex]
  functions : Map[String, FunctionDef]
  kinds : Map[String, Int]
  frames : Array[Frame]
  mut random_state : Int64
  mut random_seed : Double
  clock_seconds : Double
  input : MainInput?
  io : ((IORequest) -> IOReply)?
  readers : Map[String, MainInput]
  writers : Map[String, String]
  mut protected_stdin : Bool
  output_sink : ((String) -> String?)?
}

///|
fn State::tick(self : State) -> Unit raise ParseError {
  if self.steps >= self.step_limit {
    raise Invalid("execution step budget")
  }
  self.steps += 1
}

///|
fn State::variable(self : State, name : String) -> Value raise ParseError {
  if name == "NF" {
    self.ensure_fields()
  }
  let vars = match self.local_frame(name) {
    Some(frame) => frame.vars
    None => self.vars
  }
  if self.has_array(name) {
    raise Invalid("array used as scalar: " + name)
  }
  match vars.get(name) {
    Some(value) => value
    None => {
      vars[name] = Empty
      Empty
    }
  }
}

///|
fn State::array(
  self : State,
  name : String,
) -> Map[String, Value] raise ParseError {
  let frame = self.local_frame(name)
  let vars = match frame {
    Some(f) => f.vars
    None => self.vars
  }
  let arrays = match frame {
    Some(f) => f.arrays
    None => self.arrays
  }
  if vars.contains(name) {
    raise Invalid("scalar used as array: " + name)
  }
  match arrays.get(name) {
    Some(a) => a
    None => {
      let a : Map[String, Value] = Map([])
      arrays[name] = a
      if frame is Some(f) {
        f.owned.push(a)
      }
      a
    }
  }
}

///|
fn State::put_element(
  self : State,
  name : String,
  key : String,
  value : Value,
) -> Unit raise ParseError {
  let a = self.array(name)
  if !a.contains(key) {
    if self.entries >= 100000 {
      raise Invalid("array entry budget")
    }
    self.entries += 1
  }
  a[key] = value
}

///|
fn split_fields(
  text : String,
  separator : String,
  st : State,
  is_regex? : Bool = false,
  skip_empty? : Bool = false,
) -> Array[Value] raise ParseError {
  if text.is_empty() {
    return []
  }
  if separator.is_empty() {
    return text.to_array().map(c => input_value(c.to_string()))
  }
  if separator == " " && !is_regex {
    let chars = text.to_array()
    let result = []
    let mut start = 0
    for i in 0..<=chars.length() {
      if i == chars.length() || chars[i].is_whitespace() {
        if i > start {
          result.push(input_value(String::from_array(chars[start:i])))
        }
        start = i + 1
      }
    }
    result
  } else if separator.to_array().length() == 1 && !is_regex {
    text.split(separator).map(s => input_value(s.to_owned())).collect()
  } else {
    let regex = st.regex(separator)
    let chars = text.to_array()
    let fields = []
    let mut start = 0
    let mut from = 0
    let mut last_nonempty_end = -1
    let mut final_empty = false
    while from <= chars.length() {
      match regex_match(regex, chars, from, st) {
        None => break
        Some((a, b)) => {
          // Record fields ignore every empty separator match. The split()
          // builtin retains the pinned reference's regexp.Split behavior.
          if skip_empty && a == b {
            from = a + 1
            continue
          }
          if a == b && a == last_nonempty_end {
            from = a + 1
            continue
          }
          if b > 0 {
            fields.push(input_value(String::from_array(chars[start:a])))
          }
          start = b
          final_empty = a == b && b == chars.length()
          if a == b {
            from = a + 1
          } else {
            from = b
            last_nonempty_end = b
          }
        }
      }
      if fields.length() > 10000 {
        raise Invalid("field count budget")
      }
    }
    if !final_empty {
      fields.push(input_value(String::from_array(chars[start:])))
    }
    fields
  }
}

///|
fn State::set_record(
  self : State,
  text : String,
  true_string? : Bool = false,
) -> Unit raise ParseError {
  if text.length() > 1000000 {
    raise Invalid("record length budget")
  }
  self.record = text
  self.record_true_string = true_string
  self.saved_fs = self.text(self.variable("FS"))
  self.fields_ready = false
  self.reuse_csv_fields = false
}

///|
fn State::ensure_fields(self : State) -> Unit raise ParseError {
  if self.fields_ready {
    return
  }
  self.fields_ready = true
  if self.input_mode is Some(mode) {
    if !self.reuse_csv_fields {
      let parsed = csv_fields(self.record, mode, self)
      self.fields.clear()
      for field in parsed {
        self.fields.push(field)
      }
    }
    self.vars["NF"] = Number(self.fields.length().to_double())
    return
  }
  let fs = self.saved_fs
  let raw = split_fields(self.record, fs, self, skip_empty=true)
  let fields = if self.text(self.variable("RS")).is_empty() &&
    fs.to_array().length() == 1 {
    let result = []
    for value in raw {
      for part in value.text().split("\n") {
        result.push(input_value(drop_record_cr(part.to_owned())))
      }
    }
    result
  } else {
    raw
  }
  if fields.length() > 10000 {
    raise Invalid("field count budget")
  }
  self.fields.clear()
  for field in fields {
    self.fields.push(field)
  }
  self.vars["NF"] = Number(self.fields.length().to_double())
}

///|
fn State::rebuild(self : State) -> Unit raise ParseError {
  let ofs = self.text(self.variable("OFS"))
  let mut length = 0
  for value in self.fields {
    length += self.text(value).length() + ofs.length()
    if length > 1000000 {
      raise Invalid("record length budget")
    }
  }
  self.record_true_string = true
  let texts = self.fields.map(v => self.text(v))
  self.record = match self.output_mode {
    Some(mode) => csv_write(texts, mode.separator)
    None => texts.join(ofs)
  }
  if self.record.length() > 1000000 {
    raise Invalid("record length budget")
  }
  self.vars["NF"] = Number(self.fields.length().to_double())
}

///|
fn field_index(n : Double) -> Int raise ParseError {
  if n < -10000.0 || n > 10000.0 {
    raise Invalid("field index -10000..10000 required")
  }
  n.to_int()
}

///|
priv enum Slot {
  VariableSlot(String)
  FieldSlot(Int)
  ElementSlot(String, String)
}

///|
fn State::read(self : State, slot : Slot) -> Value raise ParseError {
  match slot {
    VariableSlot(name) => self.variable(name)
    FieldSlot(i) =>
      if i == 0 {
        if self.record_true_string {
          Text(self.record)
        } else {
          input_value(self.record)
        }
      } else {
        self.ensure_fields()
        let index = if i < 0 { self.fields.length() + 1 + i } else { i }
        self.fields.get(index - 1).unwrap_or(Text(""))
      }
    ElementSlot(name, key) => {
      let a = self.array(name)
      match a.get(key) {
        Some(v) => v
        None => {
          self.put_element(name, key, Empty)
          Empty
        }
      }
    }
  }
}

///|
fn State::write(
  self : State,
  slot : Slot,
  value : Value,
  global? : Bool = false,
) -> Unit raise ParseError {
  match slot {
    VariableSlot(name) => {
      if (if global {
          self.arrays.contains(name)
        } else {
          self.has_array(name)
        }) {
        raise Invalid("array used as scalar: " + name)
      }
      if name == "INPUTMODE" || name == "OUTPUTMODE" {
        let mode = csv_mode(self.text(value), name == "INPUTMODE")
        if name == "INPUTMODE" {
          self.input_mode = mode
        } else {
          self.output_mode = mode
        }
        self.vars[name] = Text(csv_mode_text(mode))
        return
      }
      if name == "NF" {
        self.ensure_fields()
        let size = field_index(value.number())
        if size < 0 {
          raise Invalid("negative NF")
        }
        while self.fields.length() > size {
          ignore(self.fields.pop())
        }
        while self.fields.length() < size {
          self.fields.push(Empty)
        }
        self.rebuild()
        self.vars["NF"] = value
      } else {
        if ["ARGV", "ENVIRON", "FIELDS"].contains(name) {
          raise Invalid("unsupported special variable " + name)
        }
        if (name == "RS" || name == "FS") &&
          self.text(value).to_array().length() > 1 {
          ignore(self.regex(self.text(value)))
        }
        match (if global { None } else { self.local_frame(name) }) {
          Some(frame) => frame.vars[name] = value
          None => self.vars[name] = value
        }
      }
    }
    FieldSlot(i) =>
      if i == 0 {
        self.set_record(self.text(value), true_string=true)
      } else {
        self.ensure_fields()
        let i = if i < 0 { self.fields.length() + 1 + i } else { i }
        if i < 1 {
          return
        }
        while self.fields.length() < i {
          self.fields.push(Empty)
        }
        self.fields[i - 1] = Text(self.text(value))
        self.rebuild()
      }
    ElementSlot(name, key) => self.put_element(name, key, value)
  }
}

///|
fn State::emit(self : State, text : String) -> Unit raise ParseError {
  if text.length() > 1000000 {
    raise Invalid("output length budget")
  }
  if self.output_sink is Some(sink) {
    if sink(text) is Some(message) {
      raise Invalid("output: " + message)
    }
    return
  }
  if text.length() > 1000000 - self.output_size {
    raise Invalid("output length budget")
  }
  self.output_size += text.length()
  self.output.push(text)
}