// Copyright 2017 The CC 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 cc // import "modernc.org/cc/v2"

import (
	"bytes"
	"fmt"
	"go/scanner"
	"io"
	"os"
	"runtime/debug"
	"strings"

	"modernc.org/strutil"
	"modernc.org/xc"
)

var (
	bNL    = []byte{'\n'}
	bPanic = []byte("panic")
)

// PrettyString returns pretty strings for things produced by this package.
func PrettyString(v interface{}) string {
	return strutil.PrettyString(v, "", "", printHooks)
}

func debugStack() []byte {
	b := debug.Stack()
	b = b[bytes.Index(b, bPanic)+1:]
	b = b[bytes.Index(b, bPanic):]
	b = b[bytes.Index(b, bNL)+1:]
	return b
}

func cppTrimSpace(toks []cppToken) []cppToken {
	for len(toks) != 0 && toks[0].Rune == ' ' {
		toks = toks[1:]
	}
	for len(toks) != 0 && toks[len(toks)-1].Rune == ' ' {
		toks = toks[:len(toks)-1]
	}
	return toks
}

func trimSpace(toks []xc.Token) []xc.Token {
	for len(toks) != 0 && toks[0].Rune == ' ' {
		toks = toks[1:]
	}
	for len(toks) != 0 && toks[len(toks)-1].Rune == ' ' {
		toks = toks[:len(toks)-1]
	}
	return toks
}

func cppTrimAllSpace(toks []cppToken) []cppToken {
	w := 0
	for _, v := range toks {
		switch v.Rune {
		case ' ', '\n':
			// nop
		default:
			toks[w] = v
			w++
		}
	}
	return toks[:w]
}

func trimAllSpace(toks []xc.Token) []xc.Token {
	w := 0
	for _, v := range toks {
		switch v.Rune {
		case ' ', '\n':
			// nop
		default:
			toks[w] = v
			w++
		}
	}
	return toks[:w]
}

func isVaList(t Type) bool { //TODO export and use
	x, ok := t.(*NamedType)
	return ok && (x.Name == idVaList || x.Name == idBuiltinVaList)
}

func cppToksDump(toks []cppToken, sep string) string {
	var a []string
	for _, t := range toks {
		if t.Rune == '\n' {
			continue
		}

		a = append(a, TokSrc(t.Token))
	}
	return strings.Join(a, sep)
}

func toksDump(toks []xc.Token, sep string) string {
	var a []string
	for _, t := range toks {
		if t.Rune == '\n' {
			continue
		}

		a = append(a, TokSrc(t))
	}
	return strings.Join(a, sep)
}

func prefer(d *Declarator) bool {
	if d.DeclarationSpecifier.IsExtern() {
		return false
	}

	t := d.Type
	for {
		switch x := UnderlyingType(t).(type) {
		case *ArrayType:
			return x.Size.Type != nil
		case *FunctionType:
			return d.FunctionDefinition != nil
		case
			*EnumType,
			*StructType,
			*UnionType:

			return true
		case *PointerType:
			t = x.Item
		case *TaggedStructType:
			return x.Type != nil
		case TypeKind:
			if x.IsScalarType() || x == Void {
				return true
			}

			panic(x)
		default:
			panic(x)
		}
	}
}

func env(key, val string) string {
	if s := os.Getenv(key); s != "" {
		return s
	}

	return val
}

// IncompatibleTypeDiff is a debug helper.
func IncompatibleTypeDiff(a, b Type) {
	if a == nil || b == nil {
		panic(fmt.Errorf("TODO\n\t%v\n\t%v", a, b))
	}

	ok0 := a.IsCompatible(b)
	a0 := a
	b0 := b
	a = UnderlyingType(a)
	b = UnderlyingType(b)
	if ok := a.IsCompatible(b); ok != ok0 {
		fmt.Printf(`UnderlyingType changed compatibility from %v to %v (%T %T)\n
	%v
	%v
	----
	%v
	%v
	====
	%v
	%v
	----
	%v
	%v
	----
`, ok0, ok, a0, b0, a0, b0, a, b, PrettyString(a0), PrettyString(b0), PrettyString(a), PrettyString(b))
	}
	switch x := a.(type) {
	case *FunctionType:
		y := b.(*FunctionType)
		if g, e := x.Variadic, y.Variadic; g != e {
			fmt.Printf("different .Variadic %v %v\n", g, e)
		}
		if g, e := x.Result, y.Result; g != e && !g.IsCompatible(e) {
			fmt.Printf("Incompatible result types in %v, %T(%v) and %T(%v)\n", x, g, g, e, e)
			IncompatibleTypeDiff(g, e)
		}
		if g, e := len(x.Params), len(y.Params); g != e {
			fmt.Printf("len(Params) %v %v\n", g, e)
			return
		}

		for i, v := range x.Params {
			w := y.Params[i]
			if g, e := v, w; g != e && !g.IsCompatible(e) {
				fmt.Printf("Parameter %v.Type: types not compatible %T %T\n\t%v\n\t%v\n", i, g, e, g, e)
				IncompatibleTypeDiff(g, e)
			}
		}
	case *PointerType:
		y := b.(*PointerType)
		IncompatibleTypeDiff(x.Item, y.Item)
	case *StructType:
		switch y := b.(type) {
		case *StructType:
			if g, e := x.Tag, y.Tag; g != e {
				fmt.Printf("Tags %q %q\n", dict.S(g), dict.S(e))
				return
			}

			if g, e := len(x.Fields), len(y.Fields); g != e {
				fmt.Printf("len(Fields) %v %v\n", g, e)
				return
			}

			for i, v := range x.Fields {
				w := y.Fields[i]
				if g, e := v.Name, w.Name; g != e {
					fmt.Printf("Field %v.Name: %q %q\n", i, dict.S(g), dict.S(e))
				}
				if g, e := v.Bits, w.Bits; g != e {
					fmt.Printf("Field %v.Bits: %v %v\n", i, g, e)
				}
				if g, e := v.PackedType, w.PackedType; g != e && !g.IsCompatible(e) {
					fmt.Printf("Field %v.PackedType: incompatible types\n\t%v\n\t%v\n", i, g, e)
					IncompatibleTypeDiff(g, e)
				}
				if g, e := v.Type, w.Type; g != e && !g.IsCompatible(e) {
					fmt.Printf("Field %v.Type: incompatible types\n\t%v\n\t%v\n", i, g, e)
					IncompatibleTypeDiff(g, e)
				}
			}
		case *TaggedStructType:
			b = y.Type
			if b == y {
				panic("TODO")
			}

			if b == nil {
				panic(fmt.Errorf("%T(%v).Type: %v", y, y, y.Type))
			}

			IncompatibleTypeDiff(x, b)
		default:
			panic(fmt.Errorf("%T", y))
		}
	case TypeKind:
		y := b.(TypeKind)
		if g, e := x, y; g != e && !g.IsCompatible(e) {
			fmt.Printf("TypeKinds differ: %v %v\n", g, e)
		}
	default:
		panic(fmt.Errorf("%T", x))
	}
}

// ErrString is like error.Error() but expands scanner.ErrorList.
func ErrString(err error) string {
	var b bytes.Buffer
	printError(&b, "", err)
	return b.String()
}

func printError(w io.Writer, pref string, err error) {
	switch x := err.(type) {
	case scanner.ErrorList:
		for i, v := range x {
			fmt.Fprintf(w, "%s%v\n", pref, v)
			if i == 50 {
				fmt.Fprintln(w, "too many errors")
				break
			}
		}
	default:
		fmt.Fprintf(w, "%s%v\n", pref, err)
	}
}

func cppToks(toks []xc.Token) []cppToken {
	r := make([]cppToken, len(toks))
	for i, v := range toks {
		r[i].Token = v
	}
	return r
}

func xcToks(toks []cppToken) []xc.Token {
	r := make([]xc.Token, len(toks))
	for i, v := range toks {
		r[i] = v.Token
	}
	return r
}
