// Copyright 2020 CUE Authors
//
// 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 export

import (
	"crypto/md5"
	"fmt"
	"io"
	"strconv"

	"cuelang.org/go/cue/ast"
	"cuelang.org/go/cue/token"
	"cuelang.org/go/internal/core/adt"
)

func (e *exporter) stringLabel(f adt.Feature) ast.Label {
	x := f.Index()
	switch f.Typ() {
	case adt.IntLabel:
		return ast.NewLit(token.INT, strconv.Itoa(x))

	case adt.DefinitionLabel, adt.HiddenLabel, adt.HiddenDefinitionLabel:
		s := e.identString(f)
		return ast.NewIdent(s)

	default:
		return ast.NewStringLabel(e.ctx.IndexToString(int64(x)))
	}
}

// identString converts the given Feature f to an identifier string.
//
// Hidden field mangling:
//
// If f is a hidden field that comes from an expanded package, it will mangle
// the name by expending it with the MD5 hash of the package name. This is to
// avoid collisions of the hidden identifiers when namespaces are merged.
// It uses the MD5 hash to allow hidden fields from the same package to
// still match across inlining and unifications.
//
// TODO: Alternatives approaches to consider:
//  1. Rewrite to unique hidden field names. This means, though, that hidden
//     fields may not match across unifications. That may be okay.
//  2. Force inline hidden fields from such packages, or translate them to let
//     expressions when necessary. This should generally preserve semantics
//     quite well.
//  3. Allow addressing hidden fields across packages, for instance by allowing
//     `_(hiddenField, pkg: "import/path")`. This kind of defeats the purpose
//     of hidden fields, though.
//
// Option 2 seems the best long-term solution. It should be possible to
// piggyback on the self containment algorithm for this: basically, whenever
// we see a hidden reference of an inlined package, we treat it as an
// external reference itself.
//
// For now, as this only can occur when the InlineImports feature is used,
// we use this simpler approach.
func (e *exporter) identString(f adt.Feature) string {
	s := f.IdentString(e.ctx)

	if !f.IsHidden() || !e.cfg.InlineImports {
		return s
	}

	pkg := f.PkgID(e.ctx)
	if pkg == "" || pkg == "_" || pkg == e.pkgID {
		return s
	}

	if e.pkgHash == nil {
		e.pkgHash = map[string]string{}
	}
	id, ok := e.pkgHash[pkg]
	if !ok {
		h := md5.New()
		io.WriteString(h, pkg)
		b := h.Sum(nil)
		id = fmt.Sprintf("_%8X", b[:4])
		e.pkgHash[pkg] = id
	}
	s += id

	return s
}
