// Copyright 2021-2022 Ian Jackson and contributors to Hippotat
// SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception
// There is NO WARRANTY.

use crate::prelude::*;

use std::io::BufRead;
use std::rc::Rc;

#[derive(Debug,Clone)]
#[derive(Hash,Eq,PartialEq,Ord,PartialOrd)]
pub struct Loc {
  pub file: Arc<PathBuf>,
  pub lno: usize,
  pub section: Option<Arc<str>>,
}

#[derive(Debug,Clone)]
pub struct Val {
  pub val: String,
  pub loc: Loc,
}

pub type Parsed = HashMap<Arc<str>, Section>;

#[derive(Debug)]
pub struct Section {
  /// Location of first encounter
  pub loc: Loc,
  pub values: HashMap<String, Val>,
}

impl Display for Loc {
  #[throws(fmt::Error)]
  fn fmt(&self, f: &mut fmt::Formatter) {
    write!(f, "{:?}:{}", &self.file, self.lno)?;
    if let Some(s) = &self.section {
      write!(f, " ")?;
      let dbg = format!("{:?}", &s);
      if let Some(mid) = (||{
        let mid = dbg.strip_prefix(r#"""#)?;
        let mid = mid.strip_suffix(r#"""#)?;
        Some(mid)
      })() {
        write!(f, "[{}]", mid)?;
      } else {
        write!(f, "{}", dbg)?;
      }
    }
  }
}


#[throws(AE)]
pub fn read(parsed: &mut Parsed, file: &mut dyn BufRead, path_for_loc: &Path)
//->Result<(), AE>
{
  let parsed = Rc::new(RefCell::new(parsed));
  let path: Arc<PathBuf> = path_for_loc.to_owned().into();
  let mut section: Option<RefMut<Section>> = None;
  for (lno, line) in file.lines().enumerate() {
    let line = line.context("read")?;
    let line = line.trim();

    if line.is_empty() { continue }
    if regex_is_match!(r#"^ [;\#] "#x, line) { continue }

    let loc = Loc {
      lno,
      file: path.clone(),
      section: section.as_ref().map(|s| s.loc.section.as_ref().unwrap().clone()),
    };
    (|| Ok::<(),AE>({

      if let Some((_,new,)) =
        regex_captures!(r#"^ \[ \s* (.+?) \s* \] $"#x, line)
      {
        let new: Arc<str> = new.to_owned().into();

        section.take(); // drops previous RefCell borrow of parsed

        let new_section = RefMut::map(parsed.borrow_mut(), |p| {

          p.entry(new.clone())
            .or_insert_with(|| {
              Section {
                loc: Loc { section: Some(new), file: path.clone(), lno },
                values: default(),
                }
            })

        });

        section = Some(new_section);

      } else if let Some((_, key, val)) =
        regex_captures!(r#"^ ( [^\[] .*? ) \s* = \s* (.*) $"#x, line)
      {
        let val = Val { loc: loc.clone(), val: val.into() };

        section
          .as_mut()
          .ok_or_else(|| anyhow!("value outside section"))?
          .values
          .insert(key.into(), val);

      } else {
        throw!(if line.starts_with("[") {
          anyhow!(r#"syntax error (section missing final "]"?)"#)
        } else {
          anyhow!(r#"syntax error (setting missing "="?)"#)
        })
      }

    }))().with_context(|| loc.to_string())?
  }
}
