(use sql-de-lite crypt)

(define *db* "/home/tjhintz/db")

(define-syntax with-db
  (syntax-rules ()
    ((_ (var) body ...)
     (call-with-database *db*
       (lambda (var)
         body ...)))))

(define (create-tables)
  (with-db (db)
    (exec (sql db "create table users(id INTEGER PRIMARY KEY, username TEXT, email TEXT, password TEXT, salt TEXT);"))
    (exec (sql db "create table sessions(bindings TEXT, session_id TEXT PRIMARY KEY);"))
    (exec (sql db "create table games(id INTEGER PRIMARY KEY, status TEXT, object TEXT, updated INTEGER);"))
    (exec (sql db "create table players(id INTEGER PRIMARY KEY, object TEXT);"))
    (exec (sql db "create table user_games(user_id INTEGER, game_id INTEGER);"))))

(define (db-session-set! sid bindings)
  (with-db (db)
    (exec (sql db "insert or replace into sessions(bindings, session_id) values (?, ?);")
          (with-output-to-string (lambda () (write bindings)))
          sid)))

(define (db-session-ref sid)
  (with-input-from-string
      (or (alist-ref
           'bindings
           (with-db (db)
             (query fetch-alist
                    (sql db "select * from sessions where session_id=?;")
                    sid)))
          "#f")
    read))

(define (add-user username email password)
  (let ((salt (crypt-gensalt)))
    (with-db (db)
      (exec (sql db "insert into users(username, password, salt, email) values(?, ?, ?, ?);")
            username (crypt password salt) salt email)
      (last-insert-rowid db))))

(define (fetch-user username)
  (with-db (db)
    (query fetch-alist
           (sql db "select * from users where username=?;")
           username)))

(define (fetch-user-by-id id)
  (with-db (db)
    (query fetch-alist
           (sql db "select * from users where id=?;")
           id)))

(define (valid-password? username password)
  (and-let* ((user (fetch-user username))
             (_ (if (null? user)
                    (begin (crypt password "$2a$12$OW1wyLclJvq.PIxgoHCjdu")
                           #f)
                    #t)))
    (string=? (crypt password (alist-ref 'salt user))
              (alist-ref 'password user))))

(define (unsecure-set-password username password)
  (let ((salt (crypt-gensalt)))
    (with-db (db)
      (exec (sql db "update users set password=?, salt=? where username=?;")
            (crypt password salt) salt username))))

(define (alist->string alist)
  (with-output-to-string (lambda () (write alist))))

(define (string->alist s)
  (with-input-from-string s read))

(define (db-add-game status object)
  (with-db (db)
    (exec (sql db "insert into games(status, object, updated) values (?, ?, ?);")
          status (alist->string object) (current-seconds))
    (last-insert-rowid db)))

(define (db-update-game id status object)
  (with-db (db)
    (exec (sql db "replace into games(id, status, object, updated) values (?, ?, ?, ?);")
          id status (alist->string object) (current-seconds))))

(define (db-fetch-game id)
  (string->alist
   (with-db (db)
     (query fetch-value
            (sql db "select object from games where id=?;")
            id))))

(define (db-fetch-open-games)
  (map
   string->alist
   (with-db (db)
     (query fetch-column
            (sql db "select object from games where status=? order by updated desc;")
            "pre-game"))))

(define (db-fetch-game-row id)
  (let ((res
         (with-db (db)
           (query fetch-alist
                  (sql db "select * from games where id=?;")
                  id))))
    `((id . ,(alist-ref 'id res))
      (status . ,(alist-ref 'status res))
      (object . ,(string->alist (alist-ref 'object res))))))

(define (db-add-player object)
  (with-db (db)
    (exec (sql db "insert into players(object) values (?);")
          (alist->string object))
    (last-insert-rowid db)))

(define (db-update-player id object)
  (with-db (db)
    (exec (sql db "replace into players(id, object) values (?, ?);")
          id (alist->string object))))

(define (db-fetch-player id)
  (string->alist
   (with-db (db)
     (query fetch-value
            (sql db "select object from players where id=?;")
            id))))

(define (db-add-user-game user-id game-id)
  (with-db (db)
    (exec (sql db "insert into user_games(user_id, game_id) values (?, ?);")
          user-id game-id)))

(define (db-remove-user-game user-id game-id)
  (with-db (db)
    (exec (sql db "delete from user_games where user_id=? and game_id=?;")
          user-id game-id)))

(define (db-fetch-user-games user-id)
  (with-db (db)
    (query fetch-column
           (sql db "select game_id from user_games join games on user_games.game_id=games.id where user_games.user_id=? and not games.status=? order by updated desc;")
           user-id "finished")))
