package gocode

import (
	"go/scanner"
	"go/token"
)

// All the code in this file serves single purpose:
// It separates a function with the cursor inside and the rest of the code. I'm
// doing that, because sometimes parser is not able to recover itself from an
// error and the autocompletion results become less complete.

type tok_pos_pair struct {
	tok token.Token
	pos token.Pos
}

type tok_collection struct {
	tokens []tok_pos_pair
	fset   *token.FileSet
}

func (this *tok_collection) next(s *scanner.Scanner) bool {
	pos, tok, _ := s.Scan()
	if tok == token.EOF {
		return false
	}

	this.tokens = append(this.tokens, tok_pos_pair{tok, pos})
	return true
}

func (this *tok_collection) find_decl_beg(pos int) int {
	lowest := 0
	lowpos := -1
	lowi := -1
	cur := 0
	for i := pos; i >= 0; i-- {
		t := this.tokens[i]
		switch t.tok {
		case token.RBRACE:
			cur++
		case token.LBRACE:
			cur--
		}

		if cur < lowest {
			lowest = cur
			lowpos = this.fset.Position(t.pos).Offset
			lowi = i
		}
	}

	cur = lowest
	for i := lowi - 1; i >= 0; i-- {
		t := this.tokens[i]
		switch t.tok {
		case token.RBRACE:
			cur++
		case token.LBRACE:
			cur--
		}
		if t.tok == token.SEMICOLON && cur == lowest {
			lowpos = this.fset.Position(t.pos).Offset
			break
		}
	}

	return lowpos
}

func (this *tok_collection) find_decl_end(pos int) int {
	highest := 0
	highpos := -1
	cur := 0

	if this.tokens[pos].tok == token.LBRACE {
		pos++
	}

	for i := pos; i < len(this.tokens); i++ {
		t := this.tokens[i]
		switch t.tok {
		case token.RBRACE:
			cur++
		case token.LBRACE:
			cur--
		}

		if cur > highest {
			highest = cur
			highpos = this.fset.Position(t.pos).Offset
		}
	}

	return highpos
}

func (this *tok_collection) find_outermost_scope(cursor int) (int, int) {
	pos := 0

	for i, t := range this.tokens {
		if cursor <= this.fset.Position(t.pos).Offset {
			break
		}
		pos = i
	}

	return this.find_decl_beg(pos), this.find_decl_end(pos)
}

// return new cursor position, file without ripped part and the ripped part itself
// variants:
//   new-cursor, file-without-ripped-part, ripped-part
//   old-cursor, file, nil
func (this *tok_collection) rip_off_decl(file []byte, cursor int) (int, []byte, []byte) {
	this.fset = token.NewFileSet()
	var s scanner.Scanner
	s.Init(this.fset.AddFile("", this.fset.Base(), len(file)), file, nil, scanner.ScanComments)
	for this.next(&s) {
	}

	beg, end := this.find_outermost_scope(cursor)
	if beg == -1 || end == -1 {
		return cursor, file, nil
	}

	ripped := make([]byte, end+1-beg)
	copy(ripped, file[beg:end+1])

	newfile := make([]byte, len(file)-len(ripped))
	copy(newfile, file[:beg])
	copy(newfile[beg:], file[end+1:])

	return cursor - beg, newfile, ripped
}

func rip_off_decl(file []byte, cursor int) (int, []byte, []byte) {
	var tc tok_collection
	return tc.rip_off_decl(file, cursor)
}
