// This package contains the database interface and an in memory implementation.
//
// For the in-memory implementation see the "NewInMemoryStore()" function.
package db

import (
	"errors"
	"fmt"
	"sync"

	"git.phc.dm.unipi.it/aziis98/posti-dm/server/util"
)

// ErrAlreadyExists is the error thrown from Store functions when an entity already exists.
var ErrAlreadyExists = errors.New(`object already exists in database`)

// ErrDoesntExist is the error thrown from Store functions when an entity doesn't exist.
var ErrDoesntExist = errors.New(`object doesn't exist in database`)

const (
	PermissionAdmin     = "admin"
	PermissionModerator = "moderator"
)

// Entities

// type FrontendUser struct {
//  Anonymous   bool     `json:"anonymous,omitempty"`
// 	ID          string   `json:"id,omitempty"`
// 	Permissions []string `json:"permissions,omitempty"`
// }

// User represents a user in the database.
type User struct {
	ID          string         `json:"id"`
	Permissions util.StringSet `json:"permissions"`

	// passwordSaltHash string
}

// Room represents a room in the database, a room has an id, a name and a collection of seatIDs of this room.
type Room struct {
	ID      string   `json:"id"`
	SeatIDs []string `json:"seatIds"`

	// gridRows, gridCols int
}

// Version 1:
//
// type SeatState struct {
// 	Occupied  bool
// 	Anonymous bool
// 	UserID    string
// }

// Version 2:
//
// type SeatState interface{ isSeatState() }
//
// type SeatNotOccupied struct{}
// type SeatOccupiedByUser struct{ UserID string }
// type SeatOccupiedByAnonymous struct{}
//
// func (s SeatNotOccupied) isSeatState()
// func (s SeatOccupiedByUser) isSeatState()
// func (s SeatOccupiedByAnonymous) isSeatState()

// Seat represents a seat in the database, it belongs to a single room and can be free or occupied by some user (referenced by userID).
type Seat struct {
	ID     string `json:"id"`
	RoomID string `json:"roomId"`

	// OccupiedBy is an empty list or a singleton of a userID
	OccupiedBy []string `json:"occupiedBy"`

	// x, y, w, h int
}

// type FrontendSeat struct {
// 	ID     string `json:"id"`
// 	RoomID string `json:"roomId"`

// 	// OccupiedBy is an empty list or a singleton of a userID
// 	OccupiedBy *FrontendUser `json:"occupiedBy"`

// 	DiagramRect struct {
// 		X      int `json:"x"`
// 		Y      int `json:"y"`
// 		Width  int `json:"width"`
// 		Height int `json:"height"`
// 	} `json:"diagramRect"`
// }

//
// Database Interfaces
//

// Database is the main interface for interacting with database implementations, for now the only implementation is "memDB".
type Database interface {
	BeginTransaction()
	EndTransaction()

	CreateUser(user *User) error
	CreateRoom(room *Room) error
	CreateSeat(seat *Seat) error

	DeleteUser(user *User) error
	DeleteRoom(room *Room) error
	DeleteSeat(seat *Seat) error

	GetUser(userID string) (*User, error)
	GetRoom(roomID string) (*Room, error)
	GetSeat(seatID string) (*Seat, error)

	GetRooms() ([]string, error)

	GetRoomOccupiedSeats(roomID string) ([]string, error)
	GetRoomFreeSeats(roomID string) ([]string, error)

	GetUserSeats(userID string) (util.StringSet, error)

	OccupySeat(seatID, userID string) error
	FreeSeat(seatID string) error
}

// TODO: Create an SQLite implementation

// NewInMemoryStore creates an instance of memDB hidden behind the Store interface.
func NewInMemoryStore() Database {
	db := &memDB{
		&sync.Mutex{},

		make(map[string]*User),
		make(map[string]*Room),
		make(map[string]*Seat),
	}

	db.rooms["aula-stud"] = &Room{
		ID:      "aula-stud",
		SeatIDs: []string{},
	}

	for i := 1; i <= 11; i++ {
		seatID := fmt.Sprintf(`aula-stud/posto-%d`, i)

		db.rooms["aula-stud"].SeatIDs = append(db.rooms["aula-stud"].SeatIDs, seatID)
		db.seats[seatID] = &Seat{
			ID:         seatID,
			RoomID:     "aula-stud",
			OccupiedBy: []string{},
		}
	}

	db.seats["aula-stud/posto-7"].OccupiedBy = []string{"aziis98"}
	db.seats["aula-stud/posto-1"].OccupiedBy = []string{"bachoseven"}

	db.users["aziis98"] = &User{
		ID:          "aziis98",
		Permissions: util.NewStringSet(PermissionAdmin),
	}

	db.users["bachoseven"] = &User{
		ID:          "bachoseven",
		Permissions: util.NewStringSet(PermissionAdmin),
	}

	return db
}