// Copyright 2012-2017 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 super::*;
use syntax_pos::Span;
use rustc::ty::maps::QueryMsg;
use std::fs::File;
use std::time::{Duration, Instant};
use std::collections::hash_map::HashMap;
use rustc::dep_graph::{DepNode};

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Query {
    pub span: Span,
    pub msg: QueryMsg,
}
pub enum Effect {
    QueryBegin(Query, CacheCase),
    TimeBegin(String),
    TaskBegin(DepNode),
}
pub enum CacheCase {
    Hit, Miss
}
/// Recursive trace structure
pub struct Rec {
    pub effect: Effect,
    pub start: Instant,
    pub dur_self: Duration,
    pub dur_total: Duration,
    pub extent: Box<Vec<Rec>>,
}
pub struct QueryMetric {
    pub count: usize,
    pub dur_self: Duration,
    pub dur_total: Duration,
}

pub fn cons_of_query_msg(q: &trace::Query) -> String {
    let s = format!("{:?}", q.msg);
    let cons: Vec<&str> = s.split(|d| d == '(' || d == '{').collect();
    assert!(cons.len() > 0 && cons[0] != "");
    cons[0].to_string()
}

pub fn cons_of_key(k: &DepNode) -> String {
    let s = format!("{:?}", k);
    let cons: Vec<&str> = s.split(|d| d == '(' || d == '{').collect();
    assert!(cons.len() > 0 && cons[0] != "");
    cons[0].to_string()
}

// First return value is text; second return value is a CSS class
pub fn html_of_effect(eff: &Effect) -> (String, String) {
    match *eff {
        Effect::TimeBegin(ref msg) => {
            (msg.clone(),
             format!("time-begin"))
        },
        Effect::TaskBegin(ref key) => {
            let cons = cons_of_key(key);
            (cons.clone(), format!("{} task-begin", cons))
        },
        Effect::QueryBegin(ref qmsg, ref cc) => {
            let cons = cons_of_query_msg(qmsg);
            (cons.clone(),
             format!("{} {}",
                     cons,
                     match *cc {
                         CacheCase::Hit => "hit",
                         CacheCase::Miss => "miss",
                     }))
        }
    }
}

// First return value is text; second return value is a CSS class
fn html_of_duration(_start: &Instant, dur: &Duration) -> (String, String) {
    use rustc::util::common::duration_to_secs_str;
    (duration_to_secs_str(dur.clone()),
     "".to_string()
    )
}

fn html_of_fraction(frac: f64) -> (String, String) {
    let css = {
        if       frac > 0.50  { format!("frac-50") }
        else if  frac > 0.40  { format!("frac-40") }
        else if  frac > 0.30  { format!("frac-30") }
        else if  frac > 0.20  { format!("frac-20") }
        else if  frac > 0.10  { format!("frac-10") }
        else if  frac > 0.05  { format!("frac-05") }
        else if  frac > 0.02  { format!("frac-02") }
        else if  frac > 0.01  { format!("frac-01") }
        else if  frac > 0.001 { format!("frac-001") }
        else                  { format!("frac-0") }
    };
    let percent = frac * 100.0;
    if percent > 0.1 { (format!("{:.1}%", percent), css) }
    else { (format!("< 0.1%", ), css) }
}

fn total_duration(traces: &Vec<Rec>) -> Duration {
    let mut sum : Duration = Duration::new(0,0);
    for t in traces.iter() {
        sum += t.dur_total;
    }
    return sum
}

fn duration_div(nom: Duration, den: Duration) -> f64 {
    fn to_nanos(d: Duration) -> u64 {
        d.as_secs() * 1_000_000_000 + d.subsec_nanos() as u64
    }

    to_nanos(nom) as f64 / to_nanos(den) as f64
}

fn write_traces_rec(file: &mut File, traces: &Vec<Rec>, total: Duration, depth: usize) {
    for t in traces {
        let (eff_text, eff_css_classes) = html_of_effect(&t.effect);
        let (dur_text, dur_css_classes) = html_of_duration(&t.start, &t.dur_total);
        let fraction = duration_div(t.dur_total, total);
        let percent = fraction * 100.0;
        let (frc_text, frc_css_classes) = html_of_fraction(fraction);
        write!(file, "<div class=\"trace depth-{} extent-{}{} {} {} {}\">\n",
               depth,
               t.extent.len(),
               /* Heuristic for 'important' CSS class: */
               if t.extent.len() > 5 || percent >= 1.0 {
                   " important" }
               else { "" },
               eff_css_classes,
               dur_css_classes,
               frc_css_classes,
        ).unwrap();
        write!(file, "<div class=\"eff\">{}</div>\n", eff_text).unwrap();
        write!(file, "<div class=\"dur\">{}</div>\n", dur_text).unwrap();
        write!(file, "<div class=\"frc\">{}</div>\n", frc_text).unwrap();
        write_traces_rec(file, &t.extent, total, depth + 1);
        write!(file, "</div>\n").unwrap();
    }
}

fn compute_counts_rec(counts: &mut HashMap<String,QueryMetric>, traces: &Vec<Rec>) {
    for t in traces.iter() {
        match t.effect {
            Effect::TimeBegin(ref msg) => {
                let qm = match counts.get(msg) {
                    Some(_qm) => { panic!("TimeBegin with non-unique, repeat message") }
                    None => QueryMetric{
                        count: 1,
                        dur_self: t.dur_self,
                        dur_total: t.dur_total,
                    }};
                counts.insert(msg.clone(), qm);
            },
            Effect::TaskBegin(ref key) => {
                let cons = cons_of_key(key);
                let qm = match counts.get(&cons) {
                    Some(qm) =>
                        QueryMetric{
                            count: qm.count + 1,
                            dur_self: qm.dur_self + t.dur_self,
                            dur_total: qm.dur_total + t.dur_total,
                        },
                    None => QueryMetric{
                        count: 1,
                        dur_self: t.dur_self,
                        dur_total: t.dur_total,
                    }};
                counts.insert(cons, qm);
            },
            Effect::QueryBegin(ref qmsg, ref _cc) => {
                let qcons = cons_of_query_msg(qmsg);
                let qm = match counts.get(&qcons) {
                    Some(qm) =>
                        QueryMetric{
                            count: qm.count + 1,
                            dur_total: qm.dur_total + t.dur_total,
                            dur_self: qm.dur_self + t.dur_self
                        },
                    None => QueryMetric{
                        count: 1,
                        dur_total: t.dur_total,
                        dur_self: t.dur_self,
                    }
                };
                counts.insert(qcons, qm);
            }
        }
        compute_counts_rec(counts, &t.extent)
    }
}

pub fn write_counts(count_file: &mut File, counts: &mut HashMap<String,QueryMetric>) {
    use rustc::util::common::duration_to_secs_str;
    use std::cmp::Ordering;

    let mut data = vec![];
    for (ref cons, ref qm) in counts.iter() {
        data.push((cons.clone(), qm.count.clone(), qm.dur_total.clone(), qm.dur_self.clone()));
    };
    data.sort_by(|&(_,_,_,self1),&(_,_,_,self2)|
                 if self1 > self2 { Ordering::Less } else { Ordering::Greater } );
    for (cons, count, dur_total, dur_self) in data {
        write!(count_file, "{}, {}, {}, {}\n",
               cons, count,
               duration_to_secs_str(dur_total),
               duration_to_secs_str(dur_self)
        ).unwrap();
    }
}

pub fn write_traces(html_file: &mut File, counts_file: &mut File, traces: &Vec<Rec>) {
    let mut counts : HashMap<String,QueryMetric> = HashMap::new();
    compute_counts_rec(&mut counts, traces);
    write_counts(counts_file, &mut counts);

    let total : Duration = total_duration(traces);
    write_traces_rec(html_file, traces, total, 0)
}

pub fn write_style(html_file: &mut File) {
    write!(html_file,"{}", "
body {
    font-family: sans-serif;
    background: black;
}
.trace {
    color: black;
    display: inline-block;
    border-style: solid;
    border-color: red;
    border-width: 1px;
    border-radius: 5px;
    padding: 0px;
    margin: 1px;
    font-size: 0px;
}
.task-begin {
    border-width: 1px;
    color: white;
    border-color: #ff8;
    font-size: 0px;
}
.miss {
    border-color: red;
    border-width: 1px;
}
.extent-0 {
    padding: 2px;
}
.time-begin {
    border-width: 4px;
    font-size: 12px;
    color: white;
    border-color: #afa;
}
.important {
    border-width: 3px;
    font-size: 12px;
    color: white;
    border-color: #f77;
}
.hit {
    padding: 0px;
    border-color: blue;
    border-width: 3px;
}
.eff {
  color: #fff;
  display: inline-block;
}
.frc {
  color: #7f7;
  display: inline-block;
}
.dur {
  display: none
}
.frac-50 {
  padding: 10px;
  border-width: 10px;
  font-size: 32px;
}
.frac-40 {
  padding: 8px;
  border-width: 8px;
  font-size: 24px;
}
.frac-30 {
  padding: 6px;
  border-width: 6px;
  font-size: 18px;
}
.frac-20 {
  padding: 4px;
  border-width: 6px;
  font-size: 16px;
}
.frac-10 {
  padding: 2px;
  border-width: 6px;
  font-size: 14px;
}
").unwrap();
}
