use std::ops::Range;
use std::sync::Arc;

use gccjit::{Function, Location, RValue};
use rustc_abi::Size;
use rustc_codegen_ssa::mir::debuginfo::VariableKind;
use rustc_codegen_ssa::traits::{DebugInfoBuilderMethods, DebugInfoCodegenMethods};
use rustc_middle::ty::{ExistentialTraitRef, Instance, Ty};
use rustc_span::{BytePos, Pos, SourceFile, SourceFileAndLine, Span, Symbol};
use rustc_target::callconv::FnAbi;

use crate::builder::Builder;
use crate::context::CodegenCx;

pub(super) const UNKNOWN_LINE_NUMBER: u32 = 0;
pub(super) const UNKNOWN_COLUMN_NUMBER: u32 = 0;

impl<'a, 'gcc, 'tcx> DebugInfoBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> {
    fn dbg_scope_fn(
        &mut self,
        _instance: Instance<'tcx>,
        _fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
        _maybe_definition_llfn: Option<Function<'gcc>>,
    ) -> Self::DIScope {
        // FIXME(antoyo): implement.
    }

    fn dbg_create_lexical_block(
        &mut self,
        _pos: BytePos,
        _parent_scope: Self::DIScope,
    ) -> Self::DIScope {
    }

    fn dbg_location_clone_with_discriminator(
        &mut self,
        loc: Self::DILocation,
        _discriminator: u32,
    ) -> Option<Self::DILocation> {
        Some(loc)
    }

    fn extend_scope_to_file(
        &mut self,
        _scope_metadata: Self::DIScope,
        _file: &SourceFile,
    ) -> Self::DIScope {
        // FIXME(antoyo): implement.
    }

    fn dbg_loc(
        &mut self,
        _scope: Self::DIScope,
        _inlined_at: Option<Self::DILocation>,
        span: Span,
    ) -> Self::DILocation {
        let pos = span.lo();
        let DebugLoc { file, line, col } = self.lookup_debug_loc(pos);
        match file.name {
            rustc_span::FileName::Real(ref name) => self.context.new_location(
                name.path(rustc_span::RemapPathScopeComponents::DEBUGINFO).to_string_lossy(),
                line as i32,
                col as i32,
            ),
            _ => Location::null(),
        }
    }

    fn create_dbg_var(
        &mut self,
        _variable_name: Symbol,
        _variable_type: Ty<'tcx>,
        _scope_metadata: Self::DIScope,
        _variable_kind: VariableKind,
        _span: Span,
    ) -> Self::DIVariable {
    }

    // FIXME(eddyb) find a common convention for all of the debuginfo-related
    // names (choose between `dbg`, `debug`, `debuginfo`, `debug_info` etc.).
    fn dbg_var_addr(
        &mut self,
        _dbg_var: Self::DIVariable,
        _dbg_loc: Self::DILocation,
        _variable_alloca: Self::Value,
        _direct_offset: Size,
        _indirect_offsets: &[Size],
        _fragment: &Option<Range<Size>>,
    ) {
        // FIXME(tempdragon): Not sure if this is correct, probably wrong but still keep it here.
        #[cfg(feature = "master")]
        _variable_alloca.set_location(_dbg_loc);
    }

    fn dbg_var_value(
        &mut self,
        _dbg_var: Self::DIVariable,
        _dbg_loc: Self::DILocation,
        _value: Self::Value,
        _direct_offset: Size,
        _indirect_offsets: &[Size],
        _fragment: &Option<Range<Size>>,
    ) {
    }

    fn insert_reference_to_gdb_debug_scripts_section_global(&mut self) {
        // FIXME(antoyo): insert reference to gdb debug scripts section global.
    }

    /// FIXME(tempdragon): Currently, this function is not yet implemented. It seems that the
    /// debug name and the mangled name should both be included in the LValues.
    /// Besides, a function to get the rvalue type(m_is_lvalue) should also be included.
    fn set_var_name(&mut self, _value: RValue<'gcc>, _name: &str) {}

    fn set_dbg_loc(&mut self, dbg_loc: Self::DILocation) {
        self.location = Some(dbg_loc);
    }

    fn clear_dbg_loc(&mut self) {
        self.location = None;
    }
}

/// A source code location used to generate debug information.
// FIXME(eddyb) rename this to better indicate it's a duplicate of
// `rustc_span::Loc` rather than `DILocation`, perhaps by making
// `lookup_char_pos` return the right information instead.
pub struct DebugLoc {
    /// Information about the original source file.
    pub file: Arc<SourceFile>,
    /// The (1-based) line number.
    pub line: u32,
    /// The (1-based) column number.
    pub col: u32,
}

impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> {
    /// Looks up debug source information about a `BytePos`.
    // FIXME(eddyb) rename this to better indicate it's a duplicate of
    // `lookup_char_pos` rather than `dbg_loc`, perhaps by making
    // `lookup_char_pos` return the right information instead.
    // Source of Origin: cg_llvm
    pub fn lookup_debug_loc(&self, pos: BytePos) -> DebugLoc {
        let (file, line, col) = match self.sess().source_map().lookup_line(pos) {
            Ok(SourceFileAndLine { sf: file, line }) => {
                let line_pos = file.lines()[line];

                // Use 1-based indexing.
                let line = (line + 1) as u32;
                let col = (file.relative_position(pos) - line_pos).to_u32() + 1;

                (file, line, col)
            }
            Err(file) => (file, UNKNOWN_LINE_NUMBER, UNKNOWN_COLUMN_NUMBER),
        };

        // For MSVC, omit the column number.
        // Otherwise, emit it. This mimics clang behaviour.
        // See discussion in https://github.com/rust-lang/rust/issues/42921
        if self.sess().target.is_like_msvc {
            DebugLoc { file, line, col: UNKNOWN_COLUMN_NUMBER }
        } else {
            DebugLoc { file, line, col }
        }
    }

    pub(crate) fn debuginfo_finalize(&self) {
        self.context.set_debug_info(true)
    }
}

impl<'gcc, 'tcx> DebugInfoCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> {
    fn create_vtable_debuginfo(
        &self,
        _ty: Ty<'tcx>,
        _trait_ref: Option<ExistentialTraitRef<'tcx>>,
        _vtable: Self::Value,
    ) {
        // FIXME(antoyo)
    }
}
