package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"strconv"
	"strings"
	"time"

	svg "github.com/ajstarks/svgo"
)

func main() {
	inFile := flag.String("input", "/dev/stdin", "")
	flag.Parse()

	f, err := os.Open(*inFile)
	if err != nil {
		log.Fatalf("open: %v", err)
	}
	defer f.Close()

	points, err := readPoints(f)
	if err != nil {
		log.Fatalf("readPoints: %v", err)
	}

	const (
		wantCount     = 1200
		eachWidth     = 1
		eachHeight    = 8
		histBinHeight = 2
	)

	bounds := []int64{}
	for i := int64(1e4); i < 1e9; i *= 10 { // 10ms -- 5s
		bounds = append(bounds, 1*i)
		bounds = append(bounds, 2*i)
		bounds = append(bounds, 5*i)
	}

	// log.Printf("points: %v", points)
	r := find(points, minValue(points), maxValue(points), wantCount, bounds)
	// log.Printf("%v", start)
	// log.Printf("%v", concurrent)
	// log.Printf("%v", end)

	r.formatInt64 = func(v int64) string {
		return time.Duration(v).String()
	}

	var img bytes.Buffer
	r.render(&img, eachWidth, eachHeight, histBinHeight)
	io.Copy(os.Stdout, &img)
}

func readPoints(r io.Reader) ([][2]int64, error) {
	var v [][2]int64
	sc := bufio.NewScanner(r)
	for sc.Scan() {
		f := strings.Fields(sc.Text())
		if len(f) != 2 {
			return nil, fmt.Errorf("bad line %q", sc.Text())
		}

		var next [2]int64
		for i := range next {
			var err error
			next[i], err = strconv.ParseInt(f[i], 10, 64)
			if err != nil {
				return nil, fmt.Errorf("bad line %q", sc.Text())
			}
		}

		v = append(v, next)
	}
	return v, sc.Err()
}

func minValue(v [][2]int64) int64 {
	var min int64
	for i, v := range v {
		if i == 0 {
			min = v[0]
		}
		for _, v := range v {
			if v < min {
				min = v
			}
		}
	}
	return min
}

func maxValue(v [][2]int64) int64 {
	var max int64
	for i, v := range v {
		if i == 0 {
			max = v[0]
		}
		for _, v := range v {
			if v > max {
				max = v
			}
		}
	}
	return max
}

func getSteps(span int64, wantCount int) (step int64, count int) {
	count64 := int64(wantCount)
	if count64 <= 0 {
		count64 = 10
	}
	step = span / count64
	if step <= 1 {
		step = 1
	}
	count64 = span / step
	if count64*step <= span {
		count64++
	}

	return step, int(count64)
}

type report struct {
	count  int
	bounds []int64

	start      []int
	concurrent []int
	end        []int
	duration   []*histogram

	formatInt64 func(int64) string
}

func newReport(count int, bounds []int64) *report {
	r := &report{
		count:  count,
		bounds: bounds,

		start:      make([]int, count),
		concurrent: make([]int, count),
		end:        make([]int, count),
		duration:   make([]*histogram, count),

		formatInt64: func(v int64) string { return fmt.Sprintf("%.e", float64(v)) },
	}
	for i := range r.duration {
		r.duration[i] = newHistogram(bounds)
	}
	return r
}

func find(v [][2]int64, min, max int64, wantCount int, bounds []int64) *report {
	if min >= max {
		return nil
	}

	step, count := getSteps(max-min, wantCount)

	r := newReport(count, bounds)
	for _, v := range v {
		vs := v[0]
		if vs < min {
			vs = min
		}
		ve := v[1]
		if ve > max {
			ve = max
		}

		left, right := (vs-min)/step, (ve-min)/step
		r.start[left]++ // TODO: make these not count if vs/ve were adjusted
		r.end[right]++
		r.duration[right].add(ve-vs, 1)
		for i := left; i <= right; i++ {
			// TODO: too slow? we expect a small step count, <1000
			r.concurrent[i]++
		}
	}

	return r
}

func (r *report) render(w io.Writer, eachWidth, eachHeight, histBinHeight int) {
	canvas := svg.New(w)

	histHeight := len(r.bounds) * histBinHeight

	canvas.Start(r.count*eachWidth, 3*eachHeight+histHeight)
	defer canvas.End()

	canvas.Rect(0, 0*eachHeight+histHeight, r.count*eachWidth, eachHeight, "fill:red")
	canvas.Rect(0, 1*eachHeight+histHeight, r.count*eachWidth, eachHeight, "fill:black")
	canvas.Rect(0, 2*eachHeight+histHeight, r.count*eachWidth, eachHeight, "fill:blue")

	const minAlpha = 0.3

	name := map[int]string{
		0: "start",
		1: "concurrent",
		2: "end",
	}
	for row, pts := range [][]int{r.start, r.concurrent, r.end} {
		maxPt := maxInt(pts)
		for col, pt := range pts {
			alpha := float64(pt) / float64(maxPt)
			if alpha != 0 && alpha < minAlpha {
				alpha = minAlpha
			}
			fill := canvas.RGBA(0xff, 0xff, 0xff, 1-alpha)

			canvas.Link("", fmt.Sprintf("%s: %d", name[row], pt))
			canvas.Rect(col*eachWidth, row*eachHeight+histHeight, eachWidth, eachHeight, fill)
			canvas.LinkEnd()
		}
	}

	for col, hist := range r.duration {
		ptMax := maxInt64(hist.counts)
		if ptMax == 0 {
			continue
		}
		var sum int64
		for _, pt := range hist.counts {
			sum += pt
		}
		for row, pt := range hist.counts {
			alpha := float64(pt) / float64(ptMax)
			if alpha != 0 {
				var bound string
				switch row {
				case 0:
					bound = fmt.Sprintf("(-\\Inf, %v)", r.formatInt64(hist.bounds[row]))
				case len(hist.bounds):
					bound = fmt.Sprintf("[%v, -\\Inf)", r.formatInt64(hist.bounds[row-1]))
				default:
					bound = fmt.Sprintf("[%v, %v)",
						r.formatInt64(hist.bounds[row-1]), r.formatInt64(hist.bounds[row]))
				}

				w, h := eachWidth, histBinHeight
				fill := canvas.RGBA(0x00, 0x00, 0x00, alpha)

				canvas.Link("", fmt.Sprintf("%d of %d within %s", pt, sum, bound))
				canvas.Rect(col*eachWidth, histHeight-row*histBinHeight, w, h, fill)
				canvas.LinkEnd()
			}
		}
	}
}

func maxInt(v []int) int {
	var max int
	for i, v := range v {
		if i == 0 {
			max = v
		}
		if v > max {
			max = v
		}
	}
	return max
}

func maxInt64(v []int64) int64 {
	var max int64
	for i, v := range v {
		if i == 0 {
			max = v
		}
		if v > max {
			max = v
		}
	}
	return max
}

type histogram struct {
	bounds []int64
	counts []int64
}

func newHistogram(bounds []int64) *histogram {
	return &histogram{bounds: bounds, counts: make([]int64, len(bounds)+1)}
}

func (h *histogram) bucketFor(v int64) int {
	for i, upper := range h.bounds {
		if v < upper {
			return i
		}
	}
	return len(h.bounds)
}

func (h *histogram) add(v int64, count int64) {
	h.counts[h.bucketFor(v)] += count
}
