package hclwrite

import (
    "Havoc/pkg/profile/yaotl/hclsyntax"
    "github.com/zclconf/go-cty/cty"
)

type Block struct {
    inTree

    leadComments *node
    typeName     *node
    labels       *node
    open         *node
    body         *node
    close        *node
}

func newBlock() *Block {
    return &Block{
        inTree: newInTree(),
    }
}

// NewBlock constructs a new, empty block with the given type name and labels.
func NewBlock(typeName string, labels []string) *Block {
    block := newBlock()
    block.init(typeName, labels)
    return block
}

func (b *Block) init(typeName string, labels []string) {
    nameTok := newIdentToken(typeName)
    nameObj := newIdentifier(nameTok)
    b.leadComments = b.children.Append(newComments(nil))
    b.typeName = b.children.Append(nameObj)
    labelsObj := newBlockLabels(labels)
    b.labels = b.children.Append(labelsObj)
    b.open = b.children.AppendUnstructuredTokens(Tokens{
        {
            Type:  hclsyntax.TokenOBrace,
            Bytes: []byte{'{'},
        },
        {
            Type:  hclsyntax.TokenNewline,
            Bytes: []byte{'\n'},
        },
    })
    body := newBody() // initially totally empty; caller can append to it subsequently
    b.body = b.children.Append(body)
    b.close = b.children.AppendUnstructuredTokens(Tokens{
        {
            Type:  hclsyntax.TokenCBrace,
            Bytes: []byte{'}'},
        },
        {
            Type:  hclsyntax.TokenNewline,
            Bytes: []byte{'\n'},
        },
    })
}

// Body returns the body that represents the content of the receiving block.
//
// Appending to or otherwise modifying this body will make changes to the
// tokens that are generated between the blocks open and close braces.
func (b *Block) Body() *Body {
    return b.body.content.(*Body)
}

// Type returns the type name of the block.
func (b *Block) Type() string {
    typeNameObj := b.typeName.content.(*identifier)
    return string(typeNameObj.token.Bytes)
}

// SetType updates the type name of the block to a given name.
func (b *Block) SetType(typeName string) {
    nameTok := newIdentToken(typeName)
    nameObj := newIdentifier(nameTok)
    b.typeName.ReplaceWith(nameObj)
}

// Labels returns the labels of the block.
func (b *Block) Labels() []string {
    return b.labelsObj().Current()
}

// SetLabels updates the labels of the block to given labels.
// Since we cannot assume that old and new labels are equal in length,
// remove old labels and insert new ones before TokenOBrace.
func (b *Block) SetLabels(labels []string) {
    b.labelsObj().Replace(labels)
}

// labelsObj returns the internal node content representation of the block
// labels. This is not part of the public API because we're intentionally
// exposing only a limited API to get/set labels on the block itself in a
// manner similar to the main hcl.Block type, but our block accessors all
// use this to get the underlying node content to work with.
func (b *Block) labelsObj() *blockLabels {
    return b.labels.content.(*blockLabels)
}

type blockLabels struct {
    inTree

    items nodeSet
}

func newBlockLabels(labels []string) *blockLabels {
    ret := &blockLabels{
        inTree: newInTree(),
        items:  newNodeSet(),
    }

    ret.Replace(labels)
    return ret
}

func (bl *blockLabels) Replace(newLabels []string) {
    bl.inTree.children.Clear()
    bl.items.Clear()

    for _, label := range newLabels {
        labelToks := TokensForValue(cty.StringVal(label))
        // Force a new label to use the quoted form, which is the idiomatic
        // form. The unquoted form is supported in HCL 2 only for compatibility
        // with historical use in HCL 1.
        labelObj := newQuoted(labelToks)
        labelNode := bl.children.Append(labelObj)
        bl.items.Add(labelNode)
    }
}

func (bl *blockLabels) Current() []string {
    labelNames := make([]string, 0, len(bl.items))
    list := bl.items.List()

    for _, label := range list {
        switch labelObj := label.content.(type) {
        case *identifier:
            if labelObj.token.Type == hclsyntax.TokenIdent {
                labelString := string(labelObj.token.Bytes)
                labelNames = append(labelNames, labelString)
            }

        case *quoted:
            tokens := labelObj.tokens
            if len(tokens) == 3 &&
                tokens[0].Type == hclsyntax.TokenOQuote &&
                tokens[1].Type == hclsyntax.TokenQuotedLit &&
                tokens[2].Type == hclsyntax.TokenCQuote {
                // Note that TokenQuotedLit may contain escape sequences.
                labelString, diags := hclsyntax.ParseStringLiteralToken(tokens[1].asHCLSyntax())

                // If parsing the string literal returns error diagnostics
                // then we can just assume the label doesn't match, because it's invalid in some way.
                if !diags.HasErrors() {
                    labelNames = append(labelNames, labelString)
                }
            } else if len(tokens) == 2 &&
                tokens[0].Type == hclsyntax.TokenOQuote &&
                tokens[1].Type == hclsyntax.TokenCQuote {
                // An open quote followed immediately by a closing quote is a
                // valid but unusual blank string label.
                labelNames = append(labelNames, "")
            }

        default:
            // If neither of the previous cases are true (should be impossible)
            // then we can just ignore it, because it's invalid too.
        }
    }

    return labelNames
}
