// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::io::Write;

use rustc_serialize::json::as_json;
use syntax::codemap::CodeMap;

use syntax::ast::CrateNum;

use super::data::{self, SpanData};
use super::dump::Dump;

pub struct JsonDumper<'a, 'b, W: Write + 'b> {
    output: &'b mut W,
    codemap: &'a CodeMap,
    first: bool,
}

impl<'a, 'b, W: Write> JsonDumper<'a, 'b, W> {
    pub fn new(writer: &'b mut W, codemap: &'a CodeMap) -> JsonDumper<'a, 'b, W> {
        if let Err(_) = write!(writer, "[") {
            error!("Error writing output");
        }
        JsonDumper { output: writer, codemap:codemap, first: true }
    }
}

impl<'a, 'b, W: Write> Drop for JsonDumper<'a, 'b, W> {
    fn drop(&mut self) {
        if let Err(_) = write!(self.output, "]") {
            error!("Error writing output");
        }
    }
}

macro_rules! impl_fn {
    ($fn_name: ident, $data_type: ident) => {
        fn $fn_name(&mut self, data: data::$data_type) {
            if self.first {
                self.first = false;
            } else {
                if let Err(_) = write!(self.output, ",") {
                    error!("Error writing output");
                }
            }
            let data = data.lower(self.codemap);
            if let Err(_) = write!(self.output, "{}", as_json(&data)) {
                error!("Error writing output '{}'", as_json(&data));
            }
        }
    }
}

impl<'a, 'b, W: Write + 'b> Dump for JsonDumper<'a, 'b, W> {
    impl_fn!(crate_prelude, CratePreludeData);
    impl_fn!(enum_data, EnumData);
    impl_fn!(extern_crate, ExternCrateData);
    impl_fn!(impl_data, ImplData);
    impl_fn!(inheritance, InheritanceData);
    impl_fn!(function, FunctionData);
    impl_fn!(function_ref, FunctionRefData);
    impl_fn!(function_call, FunctionCallData);
    impl_fn!(method, MethodData);
    impl_fn!(method_call, MethodCallData);
    impl_fn!(macro_data, MacroData);
    impl_fn!(macro_use, MacroUseData);
    impl_fn!(mod_data, ModData);
    impl_fn!(mod_ref, ModRefData);
    impl_fn!(struct_data, StructData);
    impl_fn!(struct_variant, StructVariantData);
    impl_fn!(trait_data, TraitData);
    impl_fn!(tuple_variant, TupleVariantData);
    impl_fn!(type_ref, TypeRefData);
    impl_fn!(typedef, TypedefData);
    impl_fn!(use_data, UseData);
    impl_fn!(use_glob, UseGlobData);
    impl_fn!(variable, VariableData);
    impl_fn!(variable_ref, VariableRefData);
}

trait Lower {
    type Target;
    fn lower(self, cm: &CodeMap) -> Self::Target;
}

pub type Id = u32;

#[derive(Debug, RustcEncodable)]
pub struct CratePreludeData {
    pub crate_name: String,
    pub crate_root: String,
    pub external_crates: Vec<data::ExternalCrateData>,
    pub span: SpanData,
}

impl Lower for data::CratePreludeData {
    type Target = CratePreludeData;

    fn lower(self, cm: &CodeMap) -> CratePreludeData {
        CratePreludeData {
            crate_name: self.crate_name,
            crate_root: self.crate_root,
            external_crates: self.external_crates,
            span: SpanData::from_span(self.span, cm),
        }
    }
}

/// Data for enum declarations.
#[derive(Clone, Debug, RustcEncodable)]
pub struct EnumData {
    pub id: Id,
    pub value: String,
    pub qualname: String,
    pub span: SpanData,
    pub scope: Id,
}

impl Lower for data::EnumData {
    type Target = EnumData;

    fn lower(self, cm: &CodeMap) -> EnumData {
        EnumData {
            id: self.id,
            value: self.value,
            qualname: self.qualname,
            span: SpanData::from_span(self.span, cm),
            scope: self.scope,
        }
    }
}

/// Data for extern crates.
#[derive(Debug, RustcEncodable)]
pub struct ExternCrateData {
    pub id: Id,
    pub name: String,
    pub crate_num: CrateNum,
    pub location: String,
    pub span: SpanData,
    pub scope: Id,
}

impl Lower for data::ExternCrateData {
    type Target = ExternCrateData;

    fn lower(self, cm: &CodeMap) -> ExternCrateData {
        ExternCrateData {
            id: self.id,
            name: self.name,
            crate_num: self.crate_num,
            location: self.location,
            span: SpanData::from_span(self.span, cm),
            scope: self.scope,
        }
    }
}

/// Data about a function call.
#[derive(Debug, RustcEncodable)]
pub struct FunctionCallData {
    pub span: SpanData,
    pub scope: Id,
    pub ref_id: Id,
}

impl Lower for data::FunctionCallData {
    type Target = FunctionCallData;

    fn lower(self, cm: &CodeMap) -> FunctionCallData {
        FunctionCallData {
            span: SpanData::from_span(self.span, cm),
            scope: self.scope,
            ref_id: self.ref_id.index.as_u32(),
        }
    }
}

/// Data for all kinds of functions and methods.
#[derive(Clone, Debug, RustcEncodable)]
pub struct FunctionData {
    pub id: Id,
    pub name: String,
    pub qualname: String,
    pub declaration: Option<Id>,
    pub span: SpanData,
    pub scope: Id,
}

impl Lower for data::FunctionData {
    type Target = FunctionData;

    fn lower(self, cm: &CodeMap) -> FunctionData {
        FunctionData {
            id: self.id,
            name: self.name,
            qualname: self.qualname,
            declaration: self.declaration.map(|id| id.index.as_u32()),
            span: SpanData::from_span(self.span, cm),
            scope: self.scope,
        }
    }
}

/// Data about a function call.
#[derive(Debug, RustcEncodable)]
pub struct FunctionRefData {
    pub span: SpanData,
    pub scope: Id,
    pub ref_id: Id,
}

impl Lower for data::FunctionRefData {
    type Target = FunctionRefData;

    fn lower(self, cm: &CodeMap) -> FunctionRefData {
        FunctionRefData {
            span: SpanData::from_span(self.span, cm),
            scope: self.scope,
            ref_id: self.ref_id.index.as_u32(),
        }
    }
}
#[derive(Debug, RustcEncodable)]
pub struct ImplData {
    pub id: Id,
    pub span: SpanData,
    pub scope: Id,
    pub trait_ref: Option<Id>,
    pub self_ref: Option<Id>,
}

impl Lower for data::ImplData {
    type Target = ImplData;

    fn lower(self, cm: &CodeMap) -> ImplData {
        ImplData {
            id: self.id,
            span: SpanData::from_span(self.span, cm),
            scope: self.scope,
            trait_ref: self.trait_ref.map(|id| id.index.as_u32()),
            self_ref: self.self_ref.map(|id| id.index.as_u32()),
        }
    }
}

#[derive(Debug, RustcEncodable)]
pub struct InheritanceData {
    pub span: SpanData,
    pub base_id: Id,
    pub deriv_id: Id
}

impl Lower for data::InheritanceData {
    type Target = InheritanceData;

    fn lower(self, cm: &CodeMap) -> InheritanceData {
        InheritanceData {
            span: SpanData::from_span(self.span, cm),
            base_id: self.base_id.index.as_u32(),
            deriv_id: self.deriv_id
        }
    }
}

/// Data about a macro declaration.
#[derive(Debug, RustcEncodable)]
pub struct MacroData {
    pub span: SpanData,
    pub name: String,
    pub qualname: String,
}

impl Lower for data::MacroData {
    type Target = MacroData;

    fn lower(self, cm: &CodeMap) -> MacroData {
        MacroData {
            span: SpanData::from_span(self.span, cm),
            name: self.name,
            qualname: self.qualname,
        }
    }
}

/// Data about a macro use.
#[derive(Debug, RustcEncodable)]
pub struct MacroUseData {
    pub span: SpanData,
    pub name: String,
    pub qualname: String,
    // Because macro expansion happens before ref-ids are determined,
    // we use the callee span to reference the associated macro definition.
    pub callee_span: SpanData,
    pub scope: Id,
    pub imported: bool,
}

impl Lower for data::MacroUseData {
    type Target = MacroUseData;

    fn lower(self, cm: &CodeMap) -> MacroUseData {
        MacroUseData {
            span: SpanData::from_span(self.span, cm),
            name: self.name,
            qualname: self.qualname,
            callee_span: SpanData::from_span(self.callee_span, cm),
            scope: self.scope,
            imported: self.imported,
        }
    }
}

/// Data about a method call.
#[derive(Debug, RustcEncodable)]
pub struct MethodCallData {
    pub span: SpanData,
    pub scope: Id,
    pub ref_id: Option<Id>,
    pub decl_id: Option<Id>,
}

impl Lower for data::MethodCallData {
    type Target = MethodCallData;

    fn lower(self, cm: &CodeMap) -> MethodCallData {
        MethodCallData {
            span: SpanData::from_span(self.span, cm),
            scope: self.scope,
            ref_id: self.ref_id.map(|id| id.index.as_u32()),
            decl_id: self.decl_id.map(|id| id.index.as_u32()),
        }
    }
}

/// Data for method declarations (methods with a body are treated as functions).
#[derive(Clone, Debug, RustcEncodable)]
pub struct MethodData {
    pub id: Id,
    pub qualname: String,
    pub span: SpanData,
    pub scope: Id,
}

impl Lower for data::MethodData {
    type Target = MethodData;

    fn lower(self, cm: &CodeMap) -> MethodData {
        MethodData {
            span: SpanData::from_span(self.span, cm),
            scope: self.scope,
            id: self.id,
            qualname: self.qualname,
        }
    }
}

/// Data for modules.
#[derive(Debug, RustcEncodable)]
pub struct ModData {
    pub id: Id,
    pub name: String,
    pub qualname: String,
    pub span: SpanData,
    pub scope: Id,
    pub filename: String,
}

impl Lower for data::ModData {
    type Target = ModData;

    fn lower(self, cm: &CodeMap) -> ModData {
        ModData {
            id: self.id,
            name: self.name,
            qualname: self.qualname,
            span: SpanData::from_span(self.span, cm),
            scope: self.scope,
            filename: self.filename,
        }
    }
}

/// Data for a reference to a module.
#[derive(Debug, RustcEncodable)]
pub struct ModRefData {
    pub span: SpanData,
    pub scope: Id,
    pub ref_id: Option<Id>,
    pub qualname: String
}

impl Lower for data::ModRefData {
    type Target = ModRefData;

    fn lower(self, cm: &CodeMap) -> ModRefData {
        ModRefData {
            span: SpanData::from_span(self.span, cm),
            scope: self.scope,
            ref_id: self.ref_id.map(|id| id.index.as_u32()),
            qualname: self.qualname,
        }
    }
}

#[derive(Debug, RustcEncodable)]
pub struct StructData {
    pub span: SpanData,
    pub id: Id,
    pub ctor_id: Id,
    pub qualname: String,
    pub scope: Id,
    pub value: String
}

impl Lower for data::StructData {
    type Target = StructData;

    fn lower(self, cm: &CodeMap) -> StructData {
        StructData {
            span: SpanData::from_span(self.span, cm),
            id: self.id,
            ctor_id: self.ctor_id,
            qualname: self.qualname,
            scope: self.scope,
            value: self.value
        }
    }
}

#[derive(Debug, RustcEncodable)]
pub struct StructVariantData {
    pub span: SpanData,
    pub id: Id,
    pub qualname: String,
    pub type_value: String,
    pub value: String,
    pub scope: Id
}

impl Lower for data::StructVariantData {
    type Target = StructVariantData;

    fn lower(self, cm: &CodeMap) -> StructVariantData {
        StructVariantData {
            span: SpanData::from_span(self.span, cm),
            id: self.id,
            qualname: self.qualname,
            type_value: self.type_value,
            value: self.value,
            scope: self.scope,
        }
    }
}

#[derive(Debug, RustcEncodable)]
pub struct TraitData {
    pub span: SpanData,
    pub id: Id,
    pub qualname: String,
    pub scope: Id,
    pub value: String
}

impl Lower for data::TraitData {
    type Target = TraitData;

    fn lower(self, cm: &CodeMap) -> TraitData {
        TraitData {
            span: SpanData::from_span(self.span, cm),
            id: self.id,
            qualname: self.qualname,
            scope: self.scope,
            value: self.value,
        }
    }
}

#[derive(Debug, RustcEncodable)]
pub struct TupleVariantData {
    pub span: SpanData,
    pub id: Id,
    pub name: String,
    pub qualname: String,
    pub type_value: String,
    pub value: String,
    pub scope: Id,
}

impl Lower for data::TupleVariantData {
    type Target = TupleVariantData;

    fn lower(self, cm: &CodeMap) -> TupleVariantData {
        TupleVariantData {
            span: SpanData::from_span(self.span, cm),
            id: self.id,
            name: self.name,
            qualname: self.qualname,
            type_value: self.type_value,
            value: self.value,
            scope: self.scope,
        }
    }
}

/// Data for a typedef.
#[derive(Debug, RustcEncodable)]
pub struct TypedefData {
    pub id: Id,
    pub span: SpanData,
    pub qualname: String,
    pub value: String,
}

impl Lower for data::TypedefData {
    type Target = TypedefData;

    fn lower(self, cm: &CodeMap) -> TypedefData {
        TypedefData {
            id: self.id,
            span: SpanData::from_span(self.span, cm),
            qualname: self.qualname,
            value: self.value,
        }
    }
}

/// Data for a reference to a type or trait.
#[derive(Clone, Debug, RustcEncodable)]
pub struct TypeRefData {
    pub span: SpanData,
    pub scope: Id,
    pub ref_id: Option<Id>,
    pub qualname: String,
}

impl Lower for data::TypeRefData {
    type Target = TypeRefData;

    fn lower(self, cm: &CodeMap) -> TypeRefData {
        TypeRefData {
            span: SpanData::from_span(self.span, cm),
            scope: self.scope,
            ref_id: self.ref_id.map(|id| id.index.as_u32()),
            qualname: self.qualname,
        }
    }
}

#[derive(Debug, RustcEncodable)]
pub struct UseData {
    pub id: Id,
    pub span: SpanData,
    pub name: String,
    pub mod_id: Option<Id>,
    pub scope: Id
}

impl Lower for data::UseData {
    type Target = UseData;

    fn lower(self, cm: &CodeMap) -> UseData {
        UseData {
            id: self.id,
            span: SpanData::from_span(self.span, cm),
            name: self.name,
            mod_id: self.mod_id.map(|id| id.index.as_u32()),
            scope: self.scope,
        }
    }
}

#[derive(Debug, RustcEncodable)]
pub struct UseGlobData {
    pub id: Id,
    pub span: SpanData,
    pub names: Vec<String>,
    pub scope: Id
}

impl Lower for data::UseGlobData {
    type Target = UseGlobData;

    fn lower(self, cm: &CodeMap) -> UseGlobData {
        UseGlobData {
            id: self.id,
            span: SpanData::from_span(self.span, cm),
            names: self.names,
            scope: self.scope,
        }
    }
}

/// Data for local and global variables (consts and statics).
#[derive(Debug, RustcEncodable)]
pub struct VariableData {
    pub id: Id,
    pub name: String,
    pub qualname: String,
    pub span: SpanData,
    pub scope: Id,
    pub value: String,
    pub type_value: String,
}

impl Lower for data::VariableData {
    type Target = VariableData;

    fn lower(self, cm: &CodeMap) -> VariableData {
        VariableData {
            id: self.id,
            name: self.name,
            qualname: self.qualname,
            span: SpanData::from_span(self.span, cm),
            scope: self.scope,
            value: self.value,
            type_value: self.type_value,
        }
    }
}

/// Data for the use of some item (e.g., the use of a local variable, which
/// will refer to that variables declaration (by ref_id)).
#[derive(Debug, RustcEncodable)]
pub struct VariableRefData {
    pub name: String,
    pub span: SpanData,
    pub scope: Id,
    pub ref_id: Id,
}

impl Lower for data::VariableRefData {
    type Target = VariableRefData;

    fn lower(self, cm: &CodeMap) -> VariableRefData {
        VariableRefData {
            name: self.name,
            span: SpanData::from_span(self.span, cm),
            scope: self.scope,
            ref_id: self.ref_id.index.as_u32(),
        }
    }
}