# --==--==--==--==--==--==--==--==--==--==--==--==--==--==--==-- #
# Bruno Charest 
# 2022-08-09 
# 
# __ DESCRIPTIONS __
# main : stating point of the program
#
# Project that create a web interface for Joplin base on 
# Joplin Terminal running in background
#
# Inspiration of : https://ttj.dk/blog/2019/01/20/setup-a-website-with-nim
# Copyright 2019 - Thomas T. Jarløv 
# [LINK] debug url : http://127.0.0.1:7000/
# --==--==--==--==--==--==--==--==--==--==--==--==--==--==--==-- #

# --==--==--==--==--==--==--==--==--==--==-- #
# Import section 
# --==--==--==--==--==--==--==--==--==--==-- #
import os # Used to get arguments
import uri # We need to encode urls: encodeUrl()

import times # Time and date
import jester # Our webserver
import logging # Logging utils
import strutils # Basic functions
import parsecfg # Parse CFG (config) files
import std/json # json manipulation
import db_sqlite # SQLite

import nim-lib/outils/db/database_utils # Utils used in the database
import nim-lib/outils/commun/password_utils # Our file with password utils
import nim-lib/outils/communication/joplin/joplin_utils # Joplin utils procedures and types
import nim-lib/outils/web/web_utils # Web utils procedures and types

# --==--==--==--==--==--==--==--==--==--==-- #
# First we'll load config files
# --==--==--==--==--==--==--==--==--==--==-- #
let dict = loadConfig("config/config.cfg")

# --==--==--==--==--==--==--==--==--==--==-- #
# Get parameters from config.cfg
# --==--==--==--==--==--==--==--==--==--==-- #

# Database
let db_user = dict.getSectionValue("Database", "user")
let db_pass = dict.getSectionValue("Database", "pass")
let db_name = dict.getSectionValue("Database", "name")
let db_host = dict.getSectionValue("Database", "host")

# Website
let mainURL = dict.getSectionValue("Server", "url")
let mainPort = parseInt dict.getSectionValue("Server", "port")
  #let mainWebsite = dict.getSectionValue("Server", "website")

  # Joplin
let joplin_token = dict.getSectionValue("Joplin", "token")

# --==--==--==--==--==--==--==--==--==--==-- #
# Database var
# --==--==--==--==--==--==--==--==--==--==-- #
var db: DbConn

# --==--==--==--==--==--==--==--==--==--==-- #
# Jester setting server settings
# --==--==--==--==--==--==--==--==--==--==-- #
settings:
  port = Port(mainPort)
  bindAddr = mainURL

# --==--==--==--==--==--==--==--==--==--==-- #
# Setup user data
# --==--==--==--==--==--==--==--==--==--==-- #
type
  TData* = ref object of RootObj
    loggedIn*: bool
    userid, username*, userpass*, email*: string
    notification*: int
    req*: Request

# --==--==--==--==--==--==--==--==--==--==-- #
# proc init : initialisation variables
# --==--==--==--==--==--==--==--==--==--==-- #
proc init(c: var TData, cld: var ColomnLeftData, crd: var ColomnRightData) =
  ## Empty out user session data
  c.userpass = ""
  c.username = ""
  c.userid = ""
  c.loggedIn = false
  c.notification = 0

# --==--==--==--==--==--==--==--==--==--==-- #
# function : loggedIn
# --==--==--==--==--==--==--==--==--==--==-- #
func loggedIn(c: TData): bool =
  ## Check if user is logged in by verifying that c.username exists
  c.username.len > 0

# --==--==--==--==--==--==--==--==--==--==-- #
# proc checkLoggedIn
# --==--==--==--==--==--==--==--==--==--==-- #
proc checkLoggedIn(c: var TData) =
  ## Check if user is logged in

  # Get the users cookie named `sid`. If it does not exist, return
  if not c.req.cookies.hasKey("sid"): return

  # Assign cookie to `let sid`
  let sid = c.req.cookies["sid"]

  # Update the value lastModified for the user in the
  # table session where the sid and IP match. If there's
  # any results (above 0) assign values
  if execAffectedRows(db, sql("UPDATE session SET lastModified = " & $toInt(
      epochTime()) & " " & "WHERE ip = ? AND key = ?"), c.req.ip, sid) > 0:

    # Get user data based on userID from session table
    # Assign values to user details - `c`
    c.userid = getValue(db, sql"SELECT userid FROM session WHERE ip = ? AND key = ?",
        c.req.ip, sid)

    # Get user data based on userID from person table
    let row = getRow(db, sql"SELECT name, email, status FROM person WHERE id = ?", c.userid)

    # Assign user data
    c.username = row[0]
    c.email = toLowerAscii(row[1])

    # Update our session table with info about activity
    discard tryExec(db, sql"UPDATE person SET lastOnline = ? WHERE id = ?",
        toInt(epochTime()), c.userid)

  else:
    # If the user is not found in the session table
    c.loggedIn = false


# --==--==--==--==--==--==--==--==--==--==-- #
# proc login user
# --==--==--==--==--==--==--==--==--==--==-- #
proc login(c: var TData, email, pass: string): tuple[b: bool, s: string] =
  ## User login

  # We have predefined query
  const query = sql"SELECT id, name, password, email, salt, status FROM person WHERE email = ?"

  # If the email or pass passed in the proc's parameters is empty, fail
  if email.len == 0 or pass.len == 0:
    return (false, "Missing password or username")

  # We'll use fastRows for a quick query.
  # Notice that the email is set to lower ascii
  # to avoid problems if the user has any
  # capitalized letters.
  for row in fastRows(db, query, toLowerAscii(email)):

    # Now our password library is going to work. It'll
    # check the password against the hashed password
    # and salt.
    if row[2] == makePassword(pass, row[4], row[2]):
      # Assign the values
      c.userid = row[0]
      c.username = row[1]
      c.userpass = row[2]
      c.email = toLowerAscii(row[3])

      # Generate session key and save it
      let key = makeSessionKey()
      exec(db, sql"INSERT INTO session (ip, key, userid) VALUES (?, ?, ?)",
          c.req.ip, key, row[0])

      info("Login successful")
      return (true, key)

  info("Login failed")
  return (false, "Login failed")


# --==--==--==--==--==--==--==--==--==--==-- #
# proc logout user
# --==--==--==--==--==--==--==--==--==--==-- #
proc logout(c: var TData) =
  ## Logout

  c.username = ""
  c.userpass = ""
  const query = sql"DELETE FROM session WHERE ip = ? AND key = ?"
  exec(db, query, c.req.ip, c.req.cookies["sid"])

# --==--==--==--==--==--==--==--==--==--==-- #
# Do the check inside our routes
# --==--==--==--==--==--==--==--==--==--==-- #
template createTFD() =
  ## Check if logged in and assign data to user

  # Assign the c to TDATA
  var c {.inject.}: TData

  # Assign the cld et crd to ColomnData
  var cld {.inject.}: ColomnLeftData
  var crd {.inject.}: ColomnRightData

  # Assign the dashData to DashbordData
  # var dashData {.inject.}: DashbordData

  # New instance of c
  new(c)
  new(cld)
  new(crd)
  # new(dashData)

  # Set standard values
  init(c, cld, crd)

  # Get users request
  c.req = request
  cld.req = request
  crd.req = request
  # dashData.req = request

  # Check for cookies (we need the cookie named sid)
  if cookies(request).len > 0:
    # Check if user is logged in
    checkLoggedIn(c)
  # Use the func()
  c.loggedIn = loggedIn(c)

  # Read Dashbord file
  # dashData = getDashbordData()


# isMainModule
# ---------------------------- #
when isMainModule:
  echo "Nim Web is now running: " & $now()

  # Generate DB if newdb is in the arguments
  # or if the database does not exists
  if "newdb" in commandLineParams() or not fileExists(db_host):
    generateDB()
    quit()

  # Connect to DB
  try:
    # We are using the values which we assigned earlier
    db = open(connection = db_host, user = db_user, password = db_pass,
        database = db_name)
    info("Connection to DB is established.")
  except:
    fatal("Connection to DB could not be established.")
    sleep(5_000)
    quit()

  # Add an admin user if newuser is in the args
  if "newuser" in commandLineParams():
    createAdminUser(db, commandLineParams())
    quit()


# Include template files
# ---------------------------- #
#include "tmpl/main.tmpl"
include "tmpl/user.nim"
include "tmpl/dashboard.nim"
include "tmpl/website.nim"


# Tests pages include
# ---------------------------- #
include "tmpl/tests/test_homepage.nim"
include "tmpl/tests/test_ping.nim"
include "tmpl/tests/test_notebooks.nim"
include "tmpl/tests/test_notes.nim"
include "tmpl/tests/test_tags.nim"
include "tmpl/tests/test_viewtree.nim"
include "tmpl/tests/test_bouton.nim"

# --==--==--==--==--==--==--==--==--==--==-- #
# Setup routes (URL's)
# --==--==--==--==--==--==--==--==--==--==-- #
routes:

  # default route
  # --==--==--==--==--==--==--==--==--==--==-- #
  get "/":
    createTFD()
    resp genMain(c)

  # master site once login
  # --==--==--==--==--==--==--==--==--==--==-- #
  get "/secret":
    createTFD()
    echo c.loggedIn
    var isStart: int = 0


    # if joplin_cli_status() == false:
    #   isStart = joplin_cli_start()


    echo "MESSAGE :", @"msg"
    echo "NOTE_ID :", @"noteid"
    let selectedNoteId = @"noteid"
    var url_note = "/secret?msg=notes"


    # URL msg and note id if exist noteid
    if selectedNoteId != "":
      url_note = "/secret?msg=notes&noteid=" & selectedNoteId

    if c.loggedIn:
    # Start joplin terminal cli if stropped
      if @"msg" == "startStopJoplin":
        var isStart = joplin_cli_start_stop()
        redirect(url_note)

      # if Joplin application work
      var checkJoplin = waitFor ping_joplin(joplin_token)
      if checkJoplin.ping_status:
        cld.j_status = true
      else:
        cld.j_status = false

      # determine the section to uptade
      if @"msg" == "newNote":
        cld.option = newNote
        echo "=> Section newNote"

      elif @"msg" == "search":
        cld.option = search
        echo "=> Section search"

      elif @"msg" == "shortcuts":
        cld.option = shortcuts
        echo "=> Section shortcuts"

      elif @"msg" == "notebooks":
        echo "=> Section notebooks"
        cld.option = notebooks
        cld.j_notebooks = waitFor get_joplin_notebooks(joplin_token)

      elif @"msg" == "notes":
        echo "=> Section notes"
        cld.option = notes
        cld.j_notes = waitFor get_joplin_notes(joplin_token)
        cld.j_notes_nb = cld.j_notes.id.len()

        if selectedNoteId != "":
          crd.j_SelectedNote = waitFor get_joplin_note(joplin_token, selectedNoteId)

      elif @"msg" == "tags":
        echo "=> Section tags"
        cld.option = tags
        cld.j_tags = waitFor get_joplin_tags(joplin_token)

      elif @"msg" == "dashbord":
        echo "=> Section dashbord"
        #url_note = "/secret?msg=dashbord"
        cld.option = dashbord
        cld.dashbord = waitFor getDashbordData()
        # resp test_bouton(cld)
        #resp dashbord(cld)



      elif @"msg" == "sendFeedBack":
        echo "Todo"

      resp Http200, {"Access-Control-Allow-Origin": "http://127.0.0.1:7000"},
          genSecret(c, cld, crd)


  # Login route
  # --==--==--==--==--==--==--==--==--==--==-- #
  get "/login":
    createTFD()
    resp Http200, {"Access-Control-Allow-Origin": "http://127.0.0.1:7000"},
        genLogin(c, @"msg")

  # action route during login
  # --==--==--==--==--==--==--==--==--==--==-- #
  post "/dologin":
    createTFD()

    let (loginB, loginS) = login(c, replace(toLowerAscii(@"email"), " ", ""),
        replace(@"password", " ", ""))
    if loginB:
      when defined(dev):
        jester.setCookie("sid", loginS, daysForward(7))
      else:
        jester.setCookie("sid", loginS, daysForward(7), samesite = Lax,
            secure = true, httpOnly = true)
        #jester.setCookie("sid", loginS, daysForward(7), samesite = Lax, secure = true, httpOnly = true)

      redirect("/secret")
    else:
      redirect("/login?msg=" & encodeUrl(loginS))

  # Logout route
  # --==--==--==--==--==--==--==--==--==--==-- #
  get "/logout":
    createTFD()
    logout(c)
    redirect("/")

  # start_joplin route
  # --==--==--==--==--==--==--==--==--==--==-- #
  # post "/start_joplin":
  #   if joplin_cli_status() == false:
  #     joplin_cli_start()




# --==--==--==--==--==--==--==--==--==--==-- #
# #        ROUTES TESTS SECTION             ##
# --==--==--==--==--==--==--==--==--==--==-- #

  # master tests page
  # --==--==--==--==--==--==--==--==--==--==-- #
  get "/test":
    createTFD()
    resp test_homepage(c)

  # Test ping joplin - query api validation
  # --==--==--==--==--==--==--==--==--==--==-- #
  get "/test_pingjoplin":
    createTFD()

    var pingCheck: joplin_ping
    pingCheck = waitFor ping_joplin(joplin_token)
    echo pingCheck.ping_status

    resp test_ping(c, pingCheck)

  # Test geting list of all notebooks
  # --==--==--==--==--==--==--==--==--==--==-- #
  get "/test_notebooks":
    createTFD()
    cld.j_notebooks = waitFor get_joplin_notebooks(joplin_token)
    resp test_notebooks(c, cld)

  # Test geting list of all notes
  # --==--==--==--==--==--==--==--==--==--==-- #
  get "/test_notes":
    createTFD()
    cld.j_notes = waitFor get_joplin_notes(joplin_token)
    resp test_notes(c, cld)

  # Test geting list of all tags
  # --==--==--==--==--==--==--==--==--==--==-- #
  get "/test_tags":
    createTFD()
    cld.j_tags = waitFor get_joplin_tags(joplin_token)
    resp test_tags(c, cld)

  # Test a viewtree
  # --==--==--==--==--==--==--==--==--==--==-- #
  get "/test_viewtree":
    createTFD()
    resp test_viewtree(c)

  # Test a viewtree
  # --==--==--==--==--==--==--==--==--==--==-- #
  get "/test_bouton":
    createTFD()
    cld.dashbord = waitFor getDashbordData()
    resp test_bouton(cld)

  # Test geting all tags as JSON output
  # --==--==--==--==--==--==--==--==--==--==-- #
  get "/test_tags_json":
    createTFD()
    var tags: JsonNodeObj


# http://localhost:41184/notes/77ec1bfbaccd4708a9f649f42896f437&token=e5f6644fbf6a97ddc55648dae72b11caecda6c6642d8ce0d3b20129b89b196385737eb908923542c3343649ebbf865b55bda031ab4c3a16edc7723ef2ad77d8f
# http://localhost:41184/notes/77ec1bfbaccd4708a9f649f42896f437?fields=id,parent_id,title,body,created_time,updated_time,is_conflict,latitude,longitude,altitude,author,source_url,is_todo,todo_due,todo_completed,source,source_application,application_data,order,user_created_time,user_updated_time,encryption_cipher_text,encryption_applied,markup_language,is_shared,share_id,conflict_original_id,master_key_id&token=e5f6644fbf6a97ddc55648dae72b11caecda6c6642d8ce0d3b20129b89b196385737eb908923542c3343649ebbf865b55bda031ab4c3a16edc7723ef2ad77d8f

# #                    ##
# # END TESTS SECTION  ##
# #                    ##
