// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::fmt::Debug;

use super::ast::*;
use super::dialect::*;
use super::parser::{Parser, ParserError};
use super::tokenizer::{Token, TokenKind, Tokenizer};

/// Tests use the methods on this struct to invoke the parser on one or
/// multiple dialects.
pub struct TestedDialects {
    pub dialects: Vec<Box<dyn Dialect>>,
}

impl TestedDialects {
    /// Run the given function for all of `self.dialects`, assert that they
    /// return the same result, and return that result.
    pub fn one_of_identical_results<F, T: Debug + PartialEq>(&self, f: F) -> T
    where
        F: Fn(&dyn Dialect) -> T,
    {
        let parse_results = self.dialects.iter().map(|dialect| (dialect, f(&**dialect)));
        parse_results
            .fold(None, |s, (dialect, parsed)| {
                if let Some((prev_dialect, prev_parsed)) = s {
                    assert_eq!(
                        prev_parsed, parsed,
                        "Parse results with {:?} are different from {:?}",
                        prev_dialect, dialect
                    );
                }
                Some((dialect, parsed))
            })
            .unwrap()
            .1
    }

    pub fn run_parser_method<F, T: Debug + PartialEq>(&self, sql: &str, f: F) -> T
    where
        F: Fn(&mut Parser) -> T,
    {
        self.one_of_identical_results(|dialect| {
            let mut tokenizer = Tokenizer::new(dialect, sql);
            let tokens = tokenizer.tokenize().unwrap();
            f(&mut Parser::new(tokens))
        })
    }

    pub fn parse_sql_statements(&self, sql: &str) -> Result<Vec<Statement>, ParserError> {
        self.one_of_identical_results(|dialect| Parser::parse_sql(dialect, sql.to_string()))
        // To fail the `ensure_multiple_dialects_are_tested` test:
        // Parser::parse_sql(&**self.dialects.first().unwrap(), sql.to_string())
    }

    /// Ensures that `sql` parses as a single statement, optionally checking
    /// that converting AST back to string equals to `canonical` (unless an
    /// empty canonical string is provided).
    pub fn one_statement_parses_to(&self, sql: &str, canonical: &str) -> Statement {
        let mut statements = self.parse_sql_statements(&sql).unwrap();
        assert_eq!(statements.len(), 1);

        let only_statement = statements.pop().unwrap();
        if !canonical.is_empty() {
            assert_eq!(canonical, only_statement.to_string())
        }
        only_statement
    }

    /// Ensures that `sql` parses as a single SQLStatement, and is not modified
    /// after a serialization round-trip.
    pub fn verified_stmt(&self, query: &str) -> Statement {
        self.one_statement_parses_to(query, query)
    }

    /// Ensures that `sql` parses as a single SQLQuery, and is not modified
    /// after a serialization round-trip.
    pub fn verified_query(&self, sql: &str) -> Query {
        match self.verified_stmt(sql) {
            Statement::Query(query) => *query,
            _ => panic!("Expected SQLQuery"),
        }
    }

    /// Ensures that `sql` parses as a single SQLSelect, and is not modified
    /// after a serialization round-trip.
    pub fn verified_only_select(&self, query: &str) -> Select {
        match self.verified_query(query).body {
            SetExpr::Select(s) => *s,
            _ => panic!("Expected SQLSetExpr::Select"),
        }
    }

    /// Ensures that `sql` parses as an expression, and is not modified
    /// after a serialization round-trip.
    pub fn verified_expr(&self, sql: &str) -> Expr {
        let ast = self.run_parser_method(sql, Parser::parse_expr).unwrap();
        assert_eq!(sql, &ast.to_string(), "round-tripping without changes");
        ast
    }
}

pub fn all_dialects() -> TestedDialects {
    TestedDialects {
        dialects: vec![
            Box::new(GenericDialect {}),
            Box::new(PostgreSqlDialect {}),
            Box::new(MsSqlDialect {}),
            Box::new(AnsiDialect {}),
        ],
    }
}

pub fn only<T>(v: impl IntoIterator<Item = T>) -> T {
    let mut iter = v.into_iter();
    if let (Some(item), None) = (iter.next(), iter.next()) {
        item
    } else {
        panic!("only called on collection without exactly one item")
    }
}

pub fn expr_from_projection(item: &SelectItem) -> &Expr {
    match item {
        SelectItem::UnnamedExpr(expr) => expr,
        _ => panic!("Expected UnnamedExpr"),
    }
}

pub trait StrIdentExt {
    fn into_ident(&self) -> Ident;
}
impl StrIdentExt for str {
    fn into_ident(&self) -> Ident {
        Ident {
            token: Token {
                kind: TokenKind::make_word(self, None),
                span: 0..0,
            },
        }
    }
}
