use miette::SourceOffset;

/// Multi-line string location representation.
///
/// Notes about counting conventions:
///
///  - Index counting starts at 0,
///  - Line counting starts at 1,
///  - Column counting starts at 1.
///
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Location {
    pub index: usize,
    pub line: usize,
    pub column: usize,
}

impl Location {
    /// Create a new position from a given index, line and column.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Location;
    ///
    /// let s = "Hi\nThere\n!!!";
    /// let p = Location::new(11, 3, 3);
    ///
    /// assert_eq!(p.index, 11);
    /// assert_eq!(p.line, 3);
    /// assert_eq!(p.column, 3);
    /// ```
    #[must_use]
    pub fn new(index: usize, line: usize, column: usize) -> Self {
        Location {
            index,
            line,
            column,
        }
    }

    /// Create a location from a string (pointing at the last character).
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Location;
    ///
    /// assert_eq!(Location::from_string("Hi\nThere\n!!!"), Location::new(12, 3, 3));
    /// ```
    #[must_use]
    pub fn from_string(s: impl AsRef<str>) -> Self {
        let s = s.as_ref();
        Self {
            index: s.chars().count(),
            line: s.lines().count(),
            column: s.lines().last().unwrap_or("").chars().count(),
        }
    }

    /// Move to a new line (newline + "carriage return").
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Location;
    ///
    /// let mut l = Location::new(12, 3, 3);
    ///
    /// assert_eq!(l.index, 12);
    /// assert_eq!(l.line, 3);
    /// assert_eq!(l.column, 3);
    ///
    /// l.newline();
    ///
    /// assert_eq!(l.index, 13);
    /// assert_eq!(l.line, 4);
    /// assert_eq!(l.column, 1);
    /// ```
    pub fn newline(&mut self) {
        self.index += 1;
        self.line += 1;
        self.column = 1;
    }

    /// Shift to the next column.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Location;
    ///
    /// let mut l = Location::new(12, 3, 3);
    ///
    /// assert_eq!(l.index, 12);
    /// assert_eq!(l.line, 3);
    /// assert_eq!(l.column, 3);
    ///
    /// l.next_column();
    ///
    /// assert_eq!(l.index, 13);
    /// assert_eq!(l.line, 3);
    /// assert_eq!(l.column, 4);
    /// ```
    pub fn next_column(&mut self) {
        self.index += 1;
        self.column += 1;
    }

    /// Convert location to a `miette` `SourceOffset`.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Location;
    ///
    /// let s = "Hi\nThere\n!!!";
    /// let p = Location::new(11, 3, 3);
    ///
    /// assert_eq!(p.index, p.as_source_offset(s).offset());
    /// ```
    #[must_use]
    pub fn as_source_offset(&self, source: impl AsRef<str>) -> SourceOffset {
        SourceOffset::from_location(source, self.line, self.column)
    }

    /// Create a copy of the location with a column offset.
    ///
    /// # Example
    ///
    /// ```
    /// use nexus_lang::Location;
    ///
    /// let l = Location::new(12, 3, 3);
    /// let l2 = l.with_column_offset(2);
    ///
    /// assert_eq!(l2.index, 12);
    /// assert_eq!(l2.line, 3);
    /// assert_eq!(l2.column, 5);
    /// ```
    #[must_use]
    pub fn with_column_offset(&self, offset: usize) -> Self {
        let mut l = *self;
        l.column += offset;
        l
    }
}

impl Default for Location {
    fn default() -> Self {
        Location {
            index: 0,
            line: 1,
            column: 1,
        }
    }
}
