// Copyright 2015 Google Inc. All rights reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kati

import (
	"fmt"
	"io"
	"os"
	"sort"
	"sync"
	"time"
)

type traceEventT struct {
	mu  sync.Mutex
	f   io.WriteCloser
	t0  time.Time
	pid int
}

const (
	traceEventMain = iota + 1
	// add new ones to use new goroutine.
)

var traceEvent traceEventT

// TraceEventStart starts trace event.
func TraceEventStart(f io.WriteCloser) {
	traceEvent.start(f)
}

// TraceEventStop stops trace event.
func TraceEventStop() {
	traceEvent.stop()
}

func (t *traceEventT) start(f io.WriteCloser) {
	t.f = f
	t.t0 = time.Now()
	fmt.Fprint(t.f, "[ ")
}

func (t *traceEventT) enabled() bool {
	return t.f != nil
}

func (t *traceEventT) stop() {
	fmt.Fprint(t.f, "\n]\n")
	t.f.Close()
}

type event struct {
	name, v string
	tid     int
	t       time.Time
	emit    bool
}

func (t *traceEventT) begin(name string, v Value, tid int) event {
	var e event
	e.tid = tid
	e.t = time.Now()
	if t.f != nil || EvalStatsFlag {
		e.name = name
		e.v = v.String()
	}
	if t.f != nil {
		e.emit = name == "include" || name == "shell"
		if e.emit {
			t.emit("B", e, e.t.Sub(t.t0))
		}
	}
	return e
}

func (t *traceEventT) emit(ph string, e event, ts time.Duration) {
	t.mu.Lock()
	defer t.mu.Unlock()

	if t.pid == 0 {
		t.pid = os.Getpid()
	} else {
		fmt.Fprintf(t.f, ",\n")
	}
	fmt.Fprintf(t.f, `{"pid":%d,"tid":%d,"ts":%d,"ph":%q,"cat":%q,"name":%q,"args":{}}`,
		t.pid,
		e.tid,
		ts.Nanoseconds()/1e3,
		ph,
		e.name,
		e.v,
	)
}

func (t *traceEventT) end(e event) {
	if t.f != nil {
		if e.emit {
			t.emit("E", e, time.Since(t.t0))
		}
	}
	stats.add(e.name, e.v, e.t)
}

type statsData struct {
	Name    string
	Count   int
	Longest time.Duration
	Total   time.Duration
}

type statsT struct {
	mu   sync.Mutex
	data map[string]statsData
}

var stats = &statsT{
	data: make(map[string]statsData),
}

func (s *statsT) add(name, v string, t time.Time) {
	if !EvalStatsFlag {
		return
	}
	d := time.Since(t)
	key := fmt.Sprintf("%s:%s", name, v)
	s.mu.Lock()
	sd := s.data[key]
	if d > sd.Longest {
		sd.Longest = d
	}
	sd.Total += d
	sd.Count++
	s.data[key] = sd
	s.mu.Unlock()
}

// DumpStats dumps statistics collected if EvalStatsFlag is set.
func DumpStats() {
	if !EvalStatsFlag {
		return
	}
	var sv byTotalTime
	for k, v := range stats.data {
		v.Name = k
		sv = append(sv, v)
	}
	sort.Sort(sv)
	fmt.Println("count,longest(ns),total(ns),longest,total,name")
	for _, s := range sv {
		fmt.Printf("%d,%d,%d,%v,%v,%s\n", s.Count, s.Longest, s.Total, s.Longest, s.Total, s.Name)
	}
}

type byTotalTime []statsData

func (b byTotalTime) Len() int      { return len(b) }
func (b byTotalTime) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b byTotalTime) Less(i, j int) bool {
	return b[i].Total > b[j].Total
}

type shellStatsT struct {
	mu       sync.Mutex
	duration time.Duration
	count    int
}

var shellStats = &shellStatsT{}

func (s *shellStatsT) add(d time.Duration) {
	s.mu.Lock()
	s.duration += d
	s.count++
	s.mu.Unlock()
}

func (s *shellStatsT) Duration() time.Duration {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.duration
}

func (s *shellStatsT) Count() int {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.count
}