// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package symbolz symbolizes a profile using the output from the symbolz
// service.
package symbolz

import (
	"bytes"
	"fmt"
	"io"
	"net/url"
	"regexp"
	"strconv"
	"strings"

	"cmd/pprof/internal/profile"
)

var (
	symbolzRE = regexp.MustCompile(`(0x[[:xdigit:]]+)\s+(.*)`)
)

// Symbolize symbolizes profile p by parsing data returned by a
// symbolz handler. syms receives the symbolz query (hex addresses
// separated by '+') and returns the symbolz output in a string.  It
// symbolizes all locations based on their addresses, regardless of
// mapping.
func Symbolize(source string, syms func(string, string) ([]byte, error), p *profile.Profile) error {
	if source = symbolz(source, p); source == "" {
		// If the source is not a recognizable URL, do nothing.
		return nil
	}

	// Construct query of addresses to symbolize.
	var a []string
	for _, l := range p.Location {
		if l.Address != 0 && len(l.Line) == 0 {
			a = append(a, fmt.Sprintf("%#x", l.Address))
		}
	}

	if len(a) == 0 {
		// No addresses to symbolize.
		return nil
	}
	lines := make(map[uint64]profile.Line)
	functions := make(map[string]*profile.Function)
	if b, err := syms(source, strings.Join(a, "+")); err == nil {
		buf := bytes.NewBuffer(b)
		for {
			l, err := buf.ReadString('\n')

			if err != nil {
				if err == io.EOF {
					break
				}
				return err
			}

			if symbol := symbolzRE.FindStringSubmatch(l); len(symbol) == 3 {
				addr, err := strconv.ParseUint(symbol[1], 0, 64)
				if err != nil {
					return fmt.Errorf("unexpected parse failure %s: %v", symbol[1], err)
				}

				name := symbol[2]
				fn := functions[name]
				if fn == nil {
					fn = &profile.Function{
						ID:         uint64(len(p.Function) + 1),
						Name:       name,
						SystemName: name,
					}
					functions[name] = fn
					p.Function = append(p.Function, fn)
				}

				lines[addr] = profile.Line{Function: fn}
			}
		}
	}

	for _, l := range p.Location {
		if line, ok := lines[l.Address]; ok {
			l.Line = []profile.Line{line}
			if l.Mapping != nil {
				l.Mapping.HasFunctions = true
			}
		}
	}

	return nil
}

// symbolz returns the corresponding symbolz source for a profile URL.
func symbolz(source string, p *profile.Profile) string {
	if url, err := url.Parse(source); err == nil && url.Host != "" {
		if last := strings.LastIndex(url.Path, "/"); last != -1 {
			if strings.HasSuffix(url.Path[:last], "pprof") {
				url.Path = url.Path[:last] + "/symbol"
			} else {
				url.Path = url.Path[:last] + "/symbolz"
			}
			return url.String()
		}
	}

	return ""
}