// Copyright 2014 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.

#![allow(bad_style)]

use std::mem;
use winapi::ctypes::*;
use winapi::shared::minwindef::*;
use winapi::um::processthreadsapi;
use winapi::um::winnt::{self, CONTEXT};
use winapi::um::dbghelp;
use winapi::um::dbghelp::*;

pub struct Frame {
    inner: STACKFRAME64,
}

impl Frame {
    pub fn ip(&self) -> *mut c_void {
        self.inner.AddrPC.Offset as *mut _
    }

    pub fn symbol_address(&self) -> *mut c_void {
        self.ip()
    }
}

#[inline(always)]
pub fn trace(cb: &mut FnMut(&super::Frame) -> bool) {
    // According to windows documentation, all dbghelp functions are
    // single-threaded.
    let _g = ::lock::lock();

    unsafe {
        // Allocate necessary structures for doing the stack walk
        let process = processthreadsapi::GetCurrentProcess();
        let thread = processthreadsapi::GetCurrentThread();

        // The CONTEXT structure needs to be aligned on a 16-byte boundary for
        // 64-bit Windows, but currently we don't have a way to express that in
        // Rust. Allocations are generally aligned to 16-bytes, though, so we
        // box this up.
        let mut context = Box::new(mem::zeroed::<CONTEXT>());
        winnt::RtlCaptureContext(&mut *context);
        let mut frame = super::Frame {
            inner: Frame { inner: mem::zeroed() },
        };
        let image = init_frame(&mut frame.inner.inner, &context);

        // Initialize this process's symbols
        let _c = ::dbghelp_init();

        // And now that we're done with all the setup, do the stack walking!
        while dbghelp::StackWalk64(image as DWORD,
                                   process,
                                   thread,
                                   &mut frame.inner.inner,
                                   &mut *context as *mut _ as *mut _,
                                   None,
                                   Some(dbghelp::SymFunctionTableAccess64),
                                   Some(dbghelp::SymGetModuleBase64),
                                   None) == TRUE {
            if frame.inner.inner.AddrPC.Offset == frame.inner.inner.AddrReturn.Offset ||
               frame.inner.inner.AddrPC.Offset == 0 ||
               frame.inner.inner.AddrReturn.Offset == 0 {
                break
            }

            if !cb(&frame) {
                break
            }
        }
    }
}

#[cfg(target_arch = "x86_64")]
fn init_frame(frame: &mut STACKFRAME64, ctx: &CONTEXT) -> WORD {
    frame.AddrPC.Offset = ctx.Rip as u64;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrStack.Offset = ctx.Rsp as u64;
    frame.AddrStack.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = ctx.Rbp as u64;
    frame.AddrFrame.Mode = AddrModeFlat;
    winnt::IMAGE_FILE_MACHINE_AMD64
}

#[cfg(target_arch = "x86")]
fn init_frame(frame: &mut STACKFRAME64, ctx: &CONTEXT) -> WORD {
    frame.AddrPC.Offset = ctx.Eip as u64;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrStack.Offset = ctx.Esp as u64;
    frame.AddrStack.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = ctx.Ebp as u64;
    frame.AddrFrame.Mode = AddrModeFlat;
    winnt::IMAGE_FILE_MACHINE_I386
}
