// Metasyntactic variables represented on the ASG
//
//  Copyright (C) 2014-2023 Ryan Specialty, LLC.
//
//  This file is part of TAME.
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

//! Metasyntactic variables on the ASG.
//!
//! Metasyntactic variables
//!   (sometimes called "metavariables" herein for short)
//!   have historically been a feature of the template system.
//! The canonical metavariable is the template parameter.

use super::{prelude::*, Ident};
use crate::{
    diagnose::Annotate,
    diagnostic_todo,
    f::Functor,
    fmt::{DisplayWrapper, TtQuote},
    parse::util::SPair,
    span::Span,
};
use std::fmt::Display;

/// Metasyntactic variable (metavariable).
///
/// A metavariable is a lexical construct.
/// Its value is a lexeme that represents an [`Ident`],
///   whose meaning depends on the context in which the metavariable is
///   referenced.
/// Its lexeme may be composed of multiple [`Self::Lexeme`]s,
///   and may even be constructed dynamically based on the values of other
///   [`Meta`]s.
///
/// Metavariables are identified by being bound by an [`Ident`];
///   the symbol representing that identifier then acts as a metavariable.
#[derive(Debug, PartialEq, Eq)]
pub enum Meta {
    Required(Span),
    ConcatList(Span),
    Lexeme(Span, SPair),
}

impl Meta {
    /// Create a new metavariable without a value.
    ///
    /// Metavariables with no value cannot be used in an expansion context.
    /// Intuitively,
    ///   they act as required parameters.
    pub fn new_required(span: Span) -> Self {
        Self::Required(span)
    }

    pub fn span(&self) -> Span {
        match self {
            Self::Required(span)
            | Self::ConcatList(span)
            | Self::Lexeme(span, _) => *span,
        }
    }

    /// Assign a lexeme to a metavariable.
    ///
    /// In a template definition context,
    ///   this acts as a default value for this metavariable.
    /// In an application context,
    ///   this has the effect of binding a value to this metavariable.
    pub fn assign_lexeme(self, lexeme: SPair) -> Self {
        match self {
            Self::Required(span) => Self::Lexeme(span, lexeme),

            Self::ConcatList(_) => diagnostic_todo!(
                vec![lexeme.note("while parsing this lexeme")],
                "append to ConcatList",
            ),

            Self::Lexeme(_, _) => diagnostic_todo!(
                vec![lexeme.note("while parsing this lexeme")],
                "Lexeme => ConcatList",
            ),
        }
    }
}

impl From<&Meta> for Span {
    fn from(meta: &Meta) -> Self {
        meta.span()
    }
}

impl Display for Meta {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Self::Required(_) => {
                write!(f, "metasyntactic parameter with required value")
            }
            Self::ConcatList(_) => {
                write!(f, "metasyntactic concatenation list")
            }
            Self::Lexeme(_, spair) => {
                write!(f, "lexeme {}", TtQuote::wrap(spair))
            }
        }
    }
}

impl Functor<Span> for Meta {
    fn map(self, f: impl FnOnce(Span) -> Span) -> Self::Target {
        match self {
            Self::Required(span) => Self::Required(f(span)),
            Self::ConcatList(span) => Self::ConcatList(f(span)),
            Self::Lexeme(span, spair) => Self::Lexeme(f(span), spair),
        }
    }
}

object_rel! {
    /// Metavariables contain lexical data and references to other
    ///   metavariables.
    Meta -> {
        tree  Meta,   // TODO: do we need tree?
        cross Ident,
    }
}

impl ObjectIndex<Meta> {
    pub fn assign_lexeme(self, asg: &mut Asg, lexeme: SPair) -> Self {
        self.map_obj(asg, |meta| meta.assign_lexeme(lexeme))
    }

    pub fn close(self, asg: &mut Asg, close_span: Span) -> Self {
        self.map_obj(asg, |meta| {
            meta.map(|open_span| {
                open_span.merge(close_span).unwrap_or(open_span)
            })
        })
    }
}
