// Copyright 2015 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 rustc::hir::def_id::{DefId, DefIndex};
use rbml;
use std::io::{Cursor, Write};
use std::slice;
use std::u32;

/// As part of the metadata, we generate an index that stores, for
/// each DefIndex, the position of the corresponding RBML document (if
/// any).  This is just a big `[u32]` slice, where an entry of
/// `u32::MAX` indicates that there is no RBML document. This little
/// struct just stores the offsets within the metadata of the start
/// and end of this slice. These are actually part of an RBML
/// document, but for looking things up in the metadata, we just
/// discard the RBML positioning and jump directly to the data.
pub struct Index {
    data_start: usize,
    data_end: usize,
}

impl Index {
    /// Given the RBML doc representing the index, save the offests
    /// for later.
    pub fn from_rbml(index: rbml::Doc) -> Index {
        Index { data_start: index.start, data_end: index.end }
    }

    /// Given the metadata, extract out the offset of a particular
    /// DefIndex (if any).
    #[inline(never)]
    pub fn lookup_item(&self, bytes: &[u8], def_index: DefIndex) -> Option<u32> {
        let words = bytes_to_words(&bytes[self.data_start..self.data_end]);
        let index = def_index.as_usize();

        debug!("lookup_item: index={:?} words.len={:?}",
               index, words.len());

        let position = u32::from_be(words[index]);
        if position == u32::MAX {
            debug!("lookup_item: position=u32::MAX");
            None
        } else {
            debug!("lookup_item: position={:?}", position);
            Some(position)
        }
    }
}

/// While we are generating the metadata, we also track the position
/// of each DefIndex. It is not required that all definitions appear
/// in the metadata, nor that they are serialized in order, and
/// therefore we first allocate the vector here and fill it with
/// `u32::MAX`. Whenever an index is visited, we fill in the
/// appropriate spot by calling `record_position`. We should never
/// visit the same index twice.
pub struct IndexData {
    positions: Vec<u32>,
}

impl IndexData {
    pub fn new(max_index: usize) -> IndexData {
        IndexData {
            positions: vec![u32::MAX; max_index]
        }
    }

    pub fn record(&mut self, def_id: DefId, position: u64) {
        assert!(def_id.is_local());
        self.record_index(def_id.index, position)
    }

    pub fn record_index(&mut self, item: DefIndex, position: u64) {
        let item = item.as_usize();

        assert!(position < (u32::MAX as u64));
        let position = position as u32;

        assert!(self.positions[item] == u32::MAX,
                "recorded position for item {:?} twice, first at {:?} and now at {:?}",
                item, self.positions[item], position);

        self.positions[item] = position;
    }

    pub fn write_index(&self, buf: &mut Cursor<Vec<u8>>) {
        for &position in &self.positions {
            write_be_u32(buf, position);
        }
    }
}

/// A dense index with integer keys. Different API from IndexData (should
/// these be merged?)
pub struct DenseIndex {
    start: usize,
    end: usize
}

impl DenseIndex {
    pub fn lookup(&self, buf: &[u8], ix: u32) -> Option<u32> {
        let data = bytes_to_words(&buf[self.start..self.end]);
        data.get(ix as usize).map(|d| u32::from_be(*d))
    }
    pub fn from_buf(buf: &[u8], start: usize, end: usize) -> Self {
        assert!((end-start)%4 == 0 && start <= end && end <= buf.len());
        DenseIndex {
            start: start,
            end: end
        }
    }
}

pub fn write_dense_index(entries: Vec<u32>, buf: &mut Cursor<Vec<u8>>) {
    let elen = entries.len();
    assert!(elen < u32::MAX as usize);

    for entry in entries {
        write_be_u32(buf, entry);
    }

    info!("write_dense_index: {} entries", elen);
}

fn write_be_u32<W: Write>(w: &mut W, u: u32) {
    let _ = w.write_all(&[
        (u >> 24) as u8,
        (u >> 16) as u8,
        (u >>  8) as u8,
        (u >>  0) as u8,
    ]);
}

fn bytes_to_words(b: &[u8]) -> &[u32] {
    assert!(b.len() % 4 == 0);
    unsafe { slice::from_raw_parts(b.as_ptr() as *const u32, b.len()/4) }
}
