<commit_before>use crate::cpp_code_generator;
use crate::database::CppCheckerAddResult;
use crate::database::CppCheckerEnv;
use crate::database::CppFfiItem;
use crate::database::CppFfiItemKind;
use crate::processor::ProcessingStep;
use crate::processor::ProcessorData;
use indicatif::ProgressBar;
use indicatif::ProgressStyle;
use log::debug;
use ritual_common::cpp_lib_builder::{
    c2r_cmake_vars, BuildType, CppLibBuilder, CppLibBuilderOutput,
};
use ritual_common::errors::{bail, Result};
use ritual_common::file_utils::{create_dir_all, create_file, path_to_str, remove_dir_all};
use ritual_common::target::current_target;
use ritual_common::utils::MapIfOk;
use std::path::Path;
use std::path::PathBuf;

fn check_snippet(
    main_cpp_path: &Path,
    builder: &CppLibBuilder,
    snippet: &Snippet,
) -> Result<CppLibBuilderOutput> {
    {
        let mut file = create_file(main_cpp_path)?;
        file.write("#include \"utils.h\"\n\n")?;
        match snippet.context {
            SnippetContext::Main => {
                file.write(format!("int main() {{\n{}\n}}\n", snippet.code))?;
            }
            SnippetContext::Global => {
                file.write(format!("{}\n\nint main() {{}}\n", snippet.code))?;
            }
        }
    }
    builder.run()
}

fn snippet_for_item(item: &CppFfiItem) -> Result<Snippet> {
    match item.kind {
        CppFfiItemKind::Function(ref cpp_ffi_function) => Ok(Snippet::new_global(
            cpp_code_generator::function_implementation(cpp_ffi_function)?,
        )),
        CppFfiItemKind::QtSlotWrapper(ref _qt_slot_wrapper) => {
            bail!("qt slot wrappers are not supported yet");
        }
    }
}

struct CppChecker<'b, 'a: 'b> {
    data: &'b mut ProcessorData<'a>,
    env: CppCheckerEnv,
    main_cpp_path: PathBuf,
    builder: CppLibBuilder,
}

enum SnippetContext {
    Main,
    Global,
}
struct Snippet {
    code: String,
    context: SnippetContext,
}

impl Snippet {
    fn new_in_main<S: Into<String>>(code: S) -> Snippet {
        Snippet {
            code: code.into(),
            context: SnippetContext::Main,
        }
    }
    fn new_global<S: Into<String>>(code: S) -> Snippet {
        Snippet {
            code: code.into(),
            context: SnippetContext::Global,
        }
    }
}

fn new_progress_bar(count: u64, message: &str) -> ProgressBar {
    let progress_bar = ProgressBar::new(count);
    progress_bar.set_style(ProgressStyle::default_bar().template("{pos}/{len} {msg} {wide_bar}"));
    progress_bar.set_message(message);
    progress_bar
}

struct PreliminaryTest {
    name: String,
    snippet: Snippet,
    expected: bool,
}

impl PreliminaryTest {
    fn new(name: &str, expected: bool, snippet: Snippet) -> Self {
        Self {
            name: name.into(),
            expected,
            snippet,
        }
    }
}

impl CppChecker<'_, '_> {
    fn run(&mut self) -> Result<()> {
        if !self
            .data
            .current_database
            .environments
            .iter()
            .any(|e| e == &self.env)
        {
            self.data
                .current_database
                .environments
                .push(self.env.clone());
        }

        self.run_tests()?;

        let total_count = self.data.current_database.cpp_items.len();
        let progress_bar = new_progress_bar(total_count as u64, "Checking items");

        let env = self.env.clone();
        for item in &mut self.data.current_database.cpp_items {
            progress_bar.inc(1);
            for ffi_item in &mut item.ffi_items {
                if ffi_item.checks.items.iter().any(|check| check.env == env) {
                    continue;
                }

                if let Ok(snippet) = snippet_for_item(ffi_item) {
                    let error_data =
                        match check_snippet(&self.main_cpp_path, &self.builder, &snippet)? {
                            CppLibBuilderOutput::Success => None, // no error
                            CppLibBuilderOutput::Fail(output) => {
                                Some(format!("build failed: {}", output.stderr))
                            }
                        };
                    let r = ffi_item.checks.add(&self.env, error_data.clone());
                    let change_text = match r {
                        CppCheckerAddResult::Added => "Added".to_string(),
                        CppCheckerAddResult::Unchanged => "Unchanged".to_string(),
                        CppCheckerAddResult::Changed { ref old } => {
                            format!("Changed! Old data for the same env: {:?}", old)
                        }
                    };

                    debug!(
                        "[cpp_checker_update] ffi_item = {:?}; snippet = {:?}; error = {:?}; {}",
                        ffi_item, snippet.code, error_data, change_text
                    );
                }
            }
        }

        Ok(())
    }

    fn run_tests(&mut self) -> Result<()> {
        let tests = &[
            PreliminaryTest::new(
                "hello world",
                true,
                Snippet::new_in_main("std::cout << \"Hello world\\n\";"),
            ),
            PreliminaryTest::new(
                "correct assertion",<commit_msg>calculate list of snippets before checking them<commit_after>use crate::cpp_code_generator;
use crate::database::CppCheckerAddResult;
use crate::database::CppCheckerEnv;
use crate::database::CppFfiItem;
use crate::database::CppFfiItemKind;
use crate::processor::ProcessingStep;
use crate::processor::ProcessorData;
use indicatif::ProgressBar;
use indicatif::ProgressStyle;
use log::debug;
use ritual_common::cpp_lib_builder::{
    c2r_cmake_vars, BuildType, CppLibBuilder, CppLibBuilderOutput,
};
use ritual_common::errors::{bail, Result};
use ritual_common::file_utils::{create_dir_all, create_file, path_to_str, remove_dir_all};
use ritual_common::target::current_target;
use ritual_common::utils::MapIfOk;
use std::path::Path;
use std::path::PathBuf;

fn check_snippet(
    main_cpp_path: &Path,
    builder: &CppLibBuilder,
    snippet: &Snippet,
) -> Result<CppLibBuilderOutput> {
    {
        let mut file = create_file(main_cpp_path)?;
        file.write("#include \"utils.h\"\n\n")?;
        match snippet.context {
            SnippetContext::Main => {
                file.write(format!("int main() {{\n{}\n}}\n", snippet.code))?;
            }
            SnippetContext::Global => {
                file.write(format!("{}\n\nint main() {{}}\n", snippet.code))?;
            }
        }
    }
    builder.run()
}

fn snippet_for_item(item: &CppFfiItem) -> Result<Snippet> {
    match item.kind {
        CppFfiItemKind::Function(ref cpp_ffi_function) => Ok(Snippet::new_global(
            cpp_code_generator::function_implementation(cpp_ffi_function)?,
        )),
        CppFfiItemKind::QtSlotWrapper(ref _qt_slot_wrapper) => {
            bail!("qt slot wrappers are not supported yet");
        }
    }
}

struct CppChecker<'b, 'a: 'b> {
    data: &'b mut ProcessorData<'a>,
    env: CppCheckerEnv,
    main_cpp_path: PathBuf,
    builder: CppLibBuilder,
}

enum SnippetContext {
    Main,
    Global,
}
struct Snippet {
    code: String,
    context: SnippetContext,
}

struct SnippetWithIndexes {
    snippet: Snippet,
    cpp_item_index: usize,
    ffi_item_index: usize,
}

impl Snippet {
    fn new_in_main<S: Into<String>>(code: S) -> Snippet {
        Snippet {
            code: code.into(),
            context: SnippetContext::Main,
        }
    }
    fn new_global<S: Into<String>>(code: S) -> Snippet {
        Snippet {
            code: code.into(),
            context: SnippetContext::Global,
        }
    }
}

fn new_progress_bar(count: u64, message: &str) -> ProgressBar {
    let progress_bar = ProgressBar::new(count);
    progress_bar.set_style(ProgressStyle::default_bar().template("{pos}/{len} {msg} {wide_bar}"));
    progress_bar.set_message(message);
    progress_bar
}

struct PreliminaryTest {
    name: String,
    snippet: Snippet,
    expected: bool,
}

impl PreliminaryTest {
    fn new(name: &str, expected: bool, snippet: Snippet) -> Self {
        Self {
            name: name.into(),
            expected,
            snippet,
        }
    }
}

impl CppChecker<'_, '_> {
    fn run(&mut self) -> Result<()> {
        if !self
            .data
            .current_database
            .environments
            .iter()
            .any(|e| e == &self.env)
        {
            self.data
                .current_database
                .environments
                .push(self.env.clone());
        }

        let env = self.env.clone();
        let mut snippets = Vec::new();
        for (cpp_item_index, item) in self.data.current_database.cpp_items.iter().enumerate() {
            for (ffi_item_index, ffi_item) in item.ffi_items.iter().enumerate() {
                if ffi_item.checks.items.iter().any(|check| check.env == env) {
                    continue;
                }

                if let Ok(snippet) = snippet_for_item(ffi_item) {
                    snippets.push(SnippetWithIndexes {
                        cpp_item_index,
                        ffi_item_index,
                        snippet,
                    });
                }
            }
        }

        if snippets.is_empty() {
            return Ok(());
        }

        self.run_tests()?;

        let progress_bar = new_progress_bar(snippets.len() as u64, "Checking items");
        for snippet in snippets {
            progress_bar.inc(1);
            let output = check_snippet(&self.main_cpp_path, &self.builder, &snippet.snippet)?;
            let error_data = match output {
                CppLibBuilderOutput::Success => None, // no error
                CppLibBuilderOutput::Fail(output) => {
                    Some(format!("build failed: {}", output.stderr))
                }
            };

            let cpp_item = &mut self.data.current_database.cpp_items[snippet.cpp_item_index];
            let ffi_item = &mut cpp_item.ffi_items[snippet.ffi_item_index];

            let r = ffi_item.checks.add(&self.env, error_data.clone());
            let change_text = match r {
                CppCheckerAddResult::Added => "Added".to_string(),
                CppCheckerAddResult::Unchanged => "Unchanged".to_string(),
                CppCheckerAddResult::Changed { ref old } => {
                    format!("Changed! Old data for the same env: {:?}", old)
                }
            };

            debug!(
                "[cpp_checker_update] ffi_item = {:?}; snippet = {:?}; error = {:?}; {}",
                ffi_item, snippet.snippet.code, error_data, change_text
            );
        }

        Ok(())
    }

    fn run_tests(&mut self) -> Result<()> {
        let tests = &[
            PreliminaryTest::new(
                "hello world",
                true,
                Snippet::new_in_main("std::cout << \"Hello world\\n\";"),
            ),
            PreliminaryTest::new(
                "correct assertion",
{'fraction_non_alphanumeric': 0.1295444294091114, 'fraction_numerical': 0.0010825439783491205, 'mean_word_length': 1.9937888198757765, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1520471', 'n_tokens_mistral': 3394, 'n_tokens_neox': 3327, 'n_words': 613}