#![allow(deprecated)]
use std::collections::{BTreeSet, HashMap, HashSet};
use std::ffi::OsStr;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use filetime::FileTime;
use jobserver::Client;
use crate::core::compiler::compilation;
use crate::core::compiler::Unit;
use crate::core::PackageId;
use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::{internal, profile, Config};
use super::build_plan::BuildPlan;
use super::custom_build::{self, BuildDeps, BuildScriptOutputs, BuildScripts};
use super::fingerprint::Fingerprint;
use super::job_queue::JobQueue;
use super::layout::Layout;
use super::standard_lib;
use super::unit_dependencies::{UnitDep, UnitGraph};
use super::{BuildContext, Compilation, CompileKind, CompileMode, Executor, FileFlavor};
mod compilation_files;
use self::compilation_files::CompilationFiles;
pub use self::compilation_files::{Metadata, OutputFile};
pub struct Context<'a, 'cfg> {
    
    pub bcx: &'a BuildContext<'a, 'cfg>,
    
    pub compilation: Compilation<'cfg>,
    
    pub build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
    
    
    
    pub build_explicit_deps: HashMap<Unit<'a>, BuildDeps>,
    
    pub fingerprints: HashMap<Unit<'a>, Arc<Fingerprint>>,
    
    pub mtime_cache: HashMap<PathBuf, FileTime>,
    
    
    
    pub compiled: HashSet<Unit<'a>>,
    
    
    pub build_scripts: HashMap<Unit<'a>, Arc<BuildScripts>>,
    
    pub jobserver: Client,
    
    
    
    primary_packages: HashSet<PackageId>,
    
    unit_dependencies: UnitGraph<'a>,
    
    
    
    files: Option<CompilationFiles<'a, 'cfg>>,
    
    
    
    
    pipelining: bool,
    
    
    
    rmeta_required: HashSet<Unit<'a>>,
}
impl<'a, 'cfg> Context<'a, 'cfg> {
    pub fn new(
        config: &'cfg Config,
        bcx: &'a BuildContext<'a, 'cfg>,
        unit_dependencies: UnitGraph<'a>,
        default_kind: CompileKind,
    ) -> CargoResult<Self> {
        
        
        
        
        
        
        
        
        let jobserver = match config.jobserver_from_env() {
            Some(c) => c.clone(),
            None => {
                let client = Client::new(bcx.build_config.jobs as usize)
                    .chain_err(|| "failed to create jobserver")?;
                client.acquire_raw()?;
                client
            }
        };
        let pipelining = bcx.config.build_config()?.pipelining.unwrap_or(true);
        Ok(Self {
            bcx,
            compilation: Compilation::new(bcx, default_kind)?,
            build_script_outputs: Arc::new(Mutex::new(BuildScriptOutputs::default())),
            fingerprints: HashMap::new(),
            mtime_cache: HashMap::new(),
            compiled: HashSet::new(),
            build_scripts: HashMap::new(),
            build_explicit_deps: HashMap::new(),
            jobserver,
            primary_packages: HashSet::new(),
            unit_dependencies,
            files: None,
            rmeta_required: HashSet::new(),
            pipelining,
        })
    }
    
    
    pub fn compile(
        mut self,
        units: &[Unit<'a>],
        export_dir: Option<PathBuf>,
        exec: &Arc<dyn Executor>,
    ) -> CargoResult<Compilation<'cfg>> {
        let mut queue = JobQueue::new(self.bcx, units);
        let mut plan = BuildPlan::new();
        let build_plan = self.bcx.build_config.build_plan;
        self.prepare_units(export_dir, units)?;
        self.prepare()?;
        custom_build::build_map(&mut self, units)?;
        self.check_collistions()?;
        for unit in units.iter() {
            
            
            
            
            
            let force_rebuild = self.bcx.build_config.force_rebuild;
            super::compile(&mut self, &mut queue, &mut plan, unit, exec, force_rebuild)?;
        }
        
        
        
        
        
        
        for fingerprint in self.fingerprints.values() {
            fingerprint.clear_memoized();
        }
        
        queue.execute(&mut self, &mut plan)?;
        if build_plan {
            plan.set_inputs(self.build_plan_inputs()?);
            plan.output_plan();
        }
        
        for unit in units.iter() {
            
            for output in self.outputs(unit)?.iter() {
                if output.flavor == FileFlavor::DebugInfo || output.flavor == FileFlavor::Auxiliary
                {
                    continue;
                }
                let bindst = output.bin_dst();
                if unit.mode == CompileMode::Test {
                    self.compilation.tests.push((
                        unit.pkg.clone(),
                        unit.target.clone(),
                        output.path.clone(),
                    ));
                } else if unit.target.is_executable() {
                    self.compilation.binaries.push(bindst.clone());
                }
            }
            
            
            if unit.target.is_lib() {
                for dep in &self.unit_dependencies[unit] {
                    if dep.unit.mode.is_run_custom_build() {
                        let out_dir = self
                            .files()
                            .build_script_out_dir(&dep.unit)
                            .display()
                            .to_string();
                        self.compilation
                            .extra_env
                            .entry(dep.unit.pkg.package_id())
                            .or_insert_with(Vec::new)
                            .push(("OUT_DIR".to_string(), out_dir));
                    }
                }
            }
            
            if unit.mode.is_doc_test() {
                
                
                
                
                
                
                
                
                
                
                let mut doctest_deps = Vec::new();
                for dep in self.unit_deps(unit) {
                    if dep.unit.target.is_lib() && dep.unit.mode == CompileMode::Build {
                        let outputs = self.outputs(&dep.unit)?;
                        let outputs = outputs.iter().filter(|output| {
                            output.path.extension() == Some(OsStr::new("rlib"))
                                || dep.unit.target.for_host()
                        });
                        for output in outputs {
                            doctest_deps.push((dep.extern_crate_name, output.path.clone()));
                        }
                    }
                }
                
                doctest_deps.sort();
                self.compilation.to_doc_test.push(compilation::Doctest {
                    package: unit.pkg.clone(),
                    target: unit.target.clone(),
                    deps: doctest_deps,
                });
            }
            
            let feats = &unit.features;
            if !feats.is_empty() {
                self.compilation
                    .cfgs
                    .entry(unit.pkg.package_id())
                    .or_insert_with(|| {
                        feats
                            .iter()
                            .map(|feat| format!("feature=\"{}\"", feat))
                            .collect()
                    });
            }
            
            let rustdocflags = self.bcx.rustdocflags_args(unit);
            if !rustdocflags.is_empty() {
                self.compilation
                    .rustdocflags
                    .entry(unit.pkg.package_id())
                    .or_insert_with(|| rustdocflags.to_vec());
            }
            super::output_depinfo(&mut self, unit)?;
        }
        for (&(ref pkg, _), output) in self.build_script_outputs.lock().unwrap().iter() {
            self.compilation
                .cfgs
                .entry(pkg.clone())
                .or_insert_with(HashSet::new)
                .extend(output.cfgs.iter().cloned());
            self.compilation
                .extra_env
                .entry(pkg.clone())
                .or_insert_with(Vec::new)
                .extend(output.env.iter().cloned());
            for dir in output.library_paths.iter() {
                self.compilation.native_dirs.insert(dir.clone());
            }
        }
        Ok(self.compilation)
    }
    
    pub fn get_executable(&mut self, unit: &Unit<'a>) -> CargoResult<Option<PathBuf>> {
        for output in self.outputs(unit)?.iter() {
            if output.flavor == FileFlavor::DebugInfo {
                continue;
            }
            let is_binary = unit.target.is_executable();
            let is_test = unit.mode.is_any_test() && !unit.mode.is_check();
            if is_binary || is_test {
                return Ok(Option::Some(output.bin_dst().clone()));
            }
        }
        Ok(None)
    }
    pub fn prepare_units(
        &mut self,
        export_dir: Option<PathBuf>,
        units: &[Unit<'a>],
    ) -> CargoResult<()> {
        let profile_kind = &self.bcx.build_config.profile_kind;
        let dest = self.bcx.profiles.get_dir_name(profile_kind);
        let host_layout = Layout::new(self.bcx.ws, None, &dest)?;
        let mut targets = HashMap::new();
        if let CompileKind::Target(target) = self.bcx.build_config.requested_kind {
            let layout = Layout::new(self.bcx.ws, Some(target), &dest)?;
            standard_lib::prepare_sysroot(&layout)?;
            targets.insert(target, layout);
        }
        self.primary_packages
            .extend(units.iter().map(|u| u.pkg.package_id()));
        self.record_units_requiring_metadata();
        let files =
            CompilationFiles::new(units, host_layout, targets, export_dir, self.bcx.ws, self);
        self.files = Some(files);
        Ok(())
    }
    
    
    pub fn prepare(&mut self) -> CargoResult<()> {
        let _p = profile::start("preparing layout");
        self.files_mut()
            .host
            .prepare()
            .chain_err(|| internal("couldn't prepare build directories"))?;
        for target in self.files.as_mut().unwrap().target.values_mut() {
            target
                .prepare()
                .chain_err(|| internal("couldn't prepare build directories"))?;
        }
        self.compilation.host_deps_output = self.files_mut().host.deps().to_path_buf();
        let files = self.files.as_ref().unwrap();
        let layout = files.layout(self.bcx.build_config.requested_kind);
        self.compilation.root_output = layout.dest().to_path_buf();
        self.compilation.deps_output = layout.deps().to_path_buf();
        Ok(())
    }
    pub fn files(&self) -> &CompilationFiles<'a, 'cfg> {
        self.files.as_ref().unwrap()
    }
    fn files_mut(&mut self) -> &mut CompilationFiles<'a, 'cfg> {
        self.files.as_mut().unwrap()
    }
    
    pub fn outputs(&self, unit: &Unit<'a>) -> CargoResult<Arc<Vec<OutputFile>>> {
        self.files.as_ref().unwrap().outputs(unit, self.bcx)
    }
    
    pub fn unit_deps(&self, unit: &Unit<'a>) -> &[UnitDep<'a>] {
        &self.unit_dependencies[unit]
    }
    pub fn is_primary_package(&self, unit: &Unit<'a>) -> bool {
        self.primary_packages.contains(&unit.pkg.package_id())
    }
    
    
    pub fn build_plan_inputs(&self) -> CargoResult<Vec<PathBuf>> {
        
        let mut inputs = BTreeSet::new();
        
        for unit in self.unit_dependencies.keys() {
            inputs.insert(unit.pkg.manifest_path().to_path_buf());
        }
        Ok(inputs.into_iter().collect())
    }
    fn check_collistions(&self) -> CargoResult<()> {
        let mut output_collisions = HashMap::new();
        let describe_collision =
            |unit: &Unit<'_>, other_unit: &Unit<'_>, path: &PathBuf| -> String {
                format!(
                    "The {} target `{}` in package `{}` has the same output \
                     filename as the {} target `{}` in package `{}`.\n\
                     Colliding filename is: {}\n",
                    unit.target.kind().description(),
                    unit.target.name(),
                    unit.pkg.package_id(),
                    other_unit.target.kind().description(),
                    other_unit.target.name(),
                    other_unit.pkg.package_id(),
                    path.display()
                )
            };
        let suggestion =
            "Consider changing their names to be unique or compiling them separately.\n\
             This may become a hard error in the future; see \
             <https://github.com/rust-lang/cargo/issues/6313>.";
        let rustdoc_suggestion =
            "This is a known bug where multiple crates with the same name use\n\
             the same path; see <https://github.com/rust-lang/cargo/issues/6313>.";
        let report_collision = |unit: &Unit<'_>,
                                other_unit: &Unit<'_>,
                                path: &PathBuf,
                                suggestion: &str|
         -> CargoResult<()> {
            if unit.target.name() == other_unit.target.name() {
                self.bcx.config.shell().warn(format!(
                    "output filename collision.\n\
                     {}\
                     The targets should have unique names.\n\
                     {}",
                    describe_collision(unit, other_unit, path),
                    suggestion
                ))
            } else {
                self.bcx.config.shell().warn(format!(
                    "output filename collision.\n\
                    {}\
                    The output filenames should be unique.\n\
                    {}\n\
                    If this looks unexpected, it may be a bug in Cargo. Please file a bug report at\n\
                    https://github.com/rust-lang/cargo/issues/ with as much information as you\n\
                    can provide.\n\
                    {} running on `{}` target `{}`\n\
                    First unit: {:?}\n\
                    Second unit: {:?}",
                    describe_collision(unit, other_unit, path),
                    suggestion,
                    crate::version(),
                    self.bcx.host_triple(),
                    unit.kind.short_name(self.bcx),
                    unit,
                    other_unit))
            }
        };
        let mut keys = self
            .unit_dependencies
            .keys()
            .filter(|unit| !unit.mode.is_run_custom_build())
            .collect::<Vec<_>>();
        
        keys.sort_unstable();
        for unit in keys {
            for output in self.outputs(unit)?.iter() {
                if let Some(other_unit) = output_collisions.insert(output.path.clone(), unit) {
                    if unit.mode.is_doc() {
                        
                        
                        report_collision(unit, other_unit, &output.path, rustdoc_suggestion)?;
                    } else {
                        report_collision(unit, other_unit, &output.path, suggestion)?;
                    }
                }
                if let Some(hardlink) = output.hardlink.as_ref() {
                    if let Some(other_unit) = output_collisions.insert(hardlink.clone(), unit) {
                        report_collision(unit, other_unit, hardlink, suggestion)?;
                    }
                }
                if let Some(ref export_path) = output.export_path {
                    if let Some(other_unit) = output_collisions.insert(export_path.clone(), unit) {
                        self.bcx.config.shell().warn(format!(
                            "`--out-dir` filename collision.\n\
                             {}\
                             The exported filenames should be unique.\n\
                             {}",
                            describe_collision(unit, other_unit, export_path),
                            suggestion
                        ))?;
                    }
                }
            }
        }
        Ok(())
    }
    
    
    
    
    fn record_units_requiring_metadata(&mut self) {
        for (key, deps) in self.unit_dependencies.iter() {
            for dep in deps {
                if self.only_requires_rmeta(key, &dep.unit) {
                    self.rmeta_required.insert(dep.unit);
                }
            }
        }
    }
    
    
    pub fn only_requires_rmeta(&self, parent: &Unit<'a>, dep: &Unit<'a>) -> bool {
        
        self.pipelining
            
            
            && !parent.requires_upstream_objects()
            && parent.mode == CompileMode::Build
            
            
            && !dep.requires_upstream_objects()
            && dep.mode == CompileMode::Build
    }
    
    
    pub fn rmeta_required(&self, unit: &Unit<'a>) -> bool {
        self.rmeta_required.contains(unit) || self.bcx.config.cli_unstable().timings.is_some()
    }
}