package boards

import (
	"std"
	"strconv"

	"gno.land/p/avl"
)

//----------------------------------------
// Board

type BoardID uint64

func (bid BoardID) String() string {
	return strconv.Itoa(int(bid))
}

type Board struct {
	id        BoardID // only set for public boards.
	url       string
	name      string
	creator   std.Address
	threads   *avl.MutTree // Post.id -> *Post
	postsCtr  uint64       // increments Post.id
	createdAt std.Time
	deleted   *avl.MutTree // TODO reserved for fast-delete.
}

func newBoard(id BoardID, url string, name string, creator std.Address) *Board {
	if !reName.MatchString(name) {
		panic("invalid name: " + name)
	}
	exists := gBoardsByName.Has(name)
	if exists {
		panic("board already exists")
	}
	return &Board{
		id:        id,
		url:       url,
		name:      name,
		creator:   creator,
		threads:   avl.NewMutTree(),
		createdAt: std.GetTimestamp(),
	}
}

/* TODO support this once we figure out how to ensure URL correctness.
// A private board is not tracked by gBoards*,
// but must be persisted by the caller's realm.
// Private boards have 0 id and does not ping
// back the remote board on reposts.
func NewPrivateBoard(url string, name string, creator std.Address) *Board {
	return newBoard(0, url, name, creator)
}
*/

func (board *Board) IsPrivate() bool {
	return board.id == 0
}

func (board *Board) GetThread(pid PostID) *Post {
	pidkey := postIDKey(pid)
	postI, exists := board.threads.Get(pidkey)
	if !exists {
		return nil
	}
	return postI.(*Post)
}

func (board *Board) AddThread(creator std.Address, title string, body string) *Post {
	pid := board.incGetPostID()
	pidkey := postIDKey(pid)
	thread := newPost(board, pid, creator, title, body, pid, 0, 0)
	board.threads.Set(pidkey, thread)
	return thread
}

// NOTE: this can be potentially very expensive for threads with many replies.
// TODO: implement optional fast-delete where thread is simply moved.
func (board *Board) DeleteThread(pid PostID) {
	pidkey := postIDKey(pid)
	_, removed := board.threads.Remove(pidkey)
	if !removed {
		panic("thread does not exist with id " + pid.String())
	}
}

func (board *Board) HasPermission(addr std.Address, perm Permission) bool {
	if board.creator == addr {
		switch perm {
		case EditPermission:
			return true
		case DeletePermission:
			return true
		default:
			return false
		}
	}
	return false
}

// Renders the board for display suitable as plaintext in
// console.  This is suitable for demonstration or tests,
// but not for prod.
func (board *Board) RenderBoard() string {
	str := ""
	str += "\\[[post](" + board.GetPostFormURL() + ")]\n\n"
	if board.threads.Size() > 0 {
		board.threads.Iterate("", "", func(n *avl.Tree) bool {
			if str != "" {
				str += "----------------------------------------\n"
			}
			str += n.Value().(*Post).RenderSummary() + "\n"
			return false
		})
	}
	return str
}

func (board *Board) incGetPostID() PostID {
	board.postsCtr++
	return PostID(board.postsCtr)
}

func (board *Board) GetURLFromThreadAndReplyID(threadID, replyID PostID) string {
	if replyID == 0 {
		return board.url + "/" + threadID.String()
	} else {
		return board.url + "/" + threadID.String() + "/" + replyID.String()
	}
}

func (board *Board) GetPostFormURL() string {
	return "/r/boards?help&__func=CreateThread" +
		"&bid=" + board.id.String() +
		"&body.type=textarea"
}