--
-- LLRNet - network part of LLR
--
-- (C) 2004-2005 Vincent Penne
--
-- Released under GNU LIBRARY GENERAL PUBLIC LICENSE
-- (See file LICENSE that must be included with this software)
--
--

version = "0.9b7"

dofile(EXEDIR.."init.lua")

if llrInit() then
   print("Failed to initialize LLR")
   return
end

if not dolib("basic") then
   return
end

if not dolib("client") then
   return
end

if not dolib("client_server") then
   return
end

print(format("LLR network client %s started", version))
print(format("Based on LLR version %s", LLRversion))
--print(EXEDIR)
--print(WIN32)

server = "localhost"
port = 7000
--username = "zig"
password = "llrnet" -- for now, same password for everybody

workFile = "workfile.txt"
tosendFile = "tosend.txt"

WUCacheMaxSize = 100

-- WIN32 specific
serviceName = "LLRnet"
serviceDisplayName = "LLRnet, k*2^n-1 / k*2^n+1 networked prime tester"

tosend = { }


asynchronous = 1


-- asynchronous mode :
-- this function dialogs with the server to send latest results and 
-- grabs new work units
-- this function is called regularly in a separate thread
function DialogWithServer()

   if not username or username == "nobody" then
      return
   end

   --print("dialog with server")

   local t, k, n, more
   local changed
   
   -- first check if we have some job to work on
   t, k, n, more = ReadWorkfile()
   more = more or { }

   -- if no unfinished job, then ask a new job to the server
   if not t or not k or not n then
      --print("Requesting new job from the server ...")
      t, k, n = GetPair()
      changed = t and k and n
   end

   if t and k and n and WUCacheSize then

      local i, t, k, n
      local s = getn(more) + 2
      local num = WUCacheSize - s + 1
      if num > 0 then
	 print(format("Fetching %d additional Work Unit(s) ...", num))
      end
      for i = s, WUCacheSize, 1 do
	 print(format("  Fetching WU #%d", i))
	 t, k, n = GetPair()
	 if t and k and n then
	    tinsert(more, { k = k, n = n })
	    changed = 1
	 else
	    break
	 end
      end


   end

   if changed then
      WriteWorkfile(t, k, n, more)
      SendStructureToAllGUIs("WUs", more, 1)
   end

   SendAllResults()

   Logout()
end

-- perform some work
-- returns nil if success, -1 if some error occures
function Work()
   local t, k, n, more
   local result, residue

   SemaWait(semaphore)

   -- first check if we have some job to work on
   t, k, n, more = ReadWorkfile()
   more = more or { }

   -- if no unfinished job, then ask a new job to the server
   if not asynchronous and (not t or not k or not n) then
      if cancelJob then
	 print("No more job to cancel !")
	 SemaSignal(semaphore)
	 return
      end

      print("Requesting new job from the server ...")
      t, k, n = GetPair()
   else
      --print("Starting job from local list ...")
   end

   if k and n and t then

      if not asynchronous and not cancelJob and WUCacheSize then
	 local i, t, k, n
	 local s = getn(more) + 2
	 local num = WUCacheSize - s + 1
	 if num > 0 then
	    print(format("Fetching %d additional Work Unit(s) ...", num))
	 end
	 for i = s, WUCacheSize, 1 do
	    print(format("Fetching WU #%d", i))
	    t, k, n = GetPair()
	    if t and k and n then
	       tinsert(more, { k = k, n = n })
	    else
	       break
	    end
	 end
      end

      --print("Test type:", t)

      WriteWorkfile(t, k, n, more)
      SendStructureToAllGUIs("WUs", more, 1)
      --sleep(1)

      SemaSignal(semaphore)

      if cancelJob  then
	 print(format("Cancelling : %s/%s (%s)", k, n, t))
	 result, residue = -2, "CANCEL"
      else
	 -- perform prime test !
	 if not asynchronous then
	    Logout() -- logout before performing computation
	 end
	 UpdateStatus(format("Working on : %s/%s (%s)", k, n, t))
	 print(format("Working on : %s/%s (%s)", k, n, t))

	 result, residue = primeTest(t, format("%s %s", k, n))
	 --result, residue = 0, "0"

	 -- check user interruption
	 if stopCheck() then
	    return -- return with no error
	 end
      end

      SemaWait(semaphore)

      -- add the result in tosend table and file
      local tbl = { t = t, k = k, n = n, 
	 result = result, residue = residue, date = date("%c") }
      tinsert(tosend, tbl)
      if WriteTosendfile() then
	 print("Could not write to tosend.txt file ! Sorry but your result will be lost :(")
	 SemaSignal(semaphore)
	 return -1
      end

      -- send results to all GUI clients
      tinsert(results, tbl)
      SendStructureToAllGUIs("results", results, 1)

      if asynchronous then
	 local t2, k2, n2
	 t2, k2, n2, more = ReadWorkfile()
	 if t~=t2 or k~=k2 or n~=n2 then
	    print("FATAL : first entry of workfile modified in middle of computation !")
	    SemaSignal(semaphore)
	    return -1
	 end
	 more = more or { }
      end

      -- remove the working unit from the work file
      if more[1] then
	 local p = more[1]
	 local k, n = p.k, p.n
	 tremove(more, 1)
	 if WriteWorkfile(t, k, n, more) then
	    print("Could not write to workfile.txt file !")
	    SemaSignal(semaphore)
	    return -1
	 end
      else
	 ClearWorkfile()
      end
      
   local file = openfile("lresults.txt", "a")
         write(file, format("[%s] ", date("%c")))
         closefile(file)

      if not asynchronous then
	 SendAllResults((sendRetries or 0) + 1)
      end

      SendStructureToAllGUIs("tosend", tosend, 1)

      SemaSignal(semaphore)
      return nil

   else
      SemaSignal(semaphore)
      if not asynchronous then
	 print("Nothing to do ! Will ask more work to the server a bit later ...")
      end
      return -1
   end
end

function Help()
   print([[
usage : llrserver.sh [options]
options :

 -h :
   print this message

 -d :
   detach client and run in background

 -v :
   set verbose mode on

 -c :
  cancel current work unit

 -no-sse2 :
  disable SSE2 instructions, may improve performance on athlon 64 CPUs

 -1 :
  perform just one test  

 -no-gui :
  do not launch the gui

]])
end

-- read configuration file
dofile("llr-clientconfig.txt")

-- parse command line options
local i, n
n = getn(arg)
for i=1,n,1 do
   if arg[i] == "-d" then
      detach()
   elseif arg[i] == "-v" then
      llrVerbose(1)
   elseif arg[i] == "-c" then
      noGui = 1
      cancelJob = 1
   elseif arg[i] == "-1" then
      once = 1
   elseif arg[i] == "-no-sse2" then
      disableSSE2()
   elseif arg[i] == "-h" then
      Help()
      return
   elseif arg[i] == "-no-gui" then
      noGui = 1
   elseif i<n and arg[i] == "-username" then
      i = i+1
      username = arg[i]
   elseif i<n and arg[i] == "-id" then
      i = i+1
      s = arg[i]
      GUIport = GUIport - s
      workFile = workFile..s
      tosendFile = tosendFile..s
   else
      print(format("Unknown option '%s'", arg[i]))
      Help()
      return
   end
end

-- install quitting signal
LlrSignalsInstalled = 1
InstallLlrSignal()

dolib("win32")

-- put a hard limit to the WUCacheSize
WUCacheSize = min(WUCacheSize or 1, WUCacheMaxSize)

-- create the global file access semaphore
semaphore = SemaCreate(1, 1)
   
-- install client GUI server
if not cancelJob and GUIremote then
   --net_Server(maxConnections, GUIport, 1, GUImask, GUIallow)
   local GUIserverError
   local func = 
      function()
	 GUIserverError = net_Server(maxConnections, GUIport, nil, GUImask, GUIallow)
      end

   create_thread(func)
   sleep(1)
   if GUIserverError then
      print("Could not run the GUI server, make sure another LLRnet client is not running")

--      if trayIcon then
--	 TrayIconRemove(trayIcon)
--	 trayIcon = nil
--      end

      if Win32Exit then
	 Win32Exit()
      end

      --if not serviceRunning and fox_init then
      if not noGui and fox_init then
	 dofile(EXEDIR.."gui.lua")
      end
      exit(0)
   end
end

function LLRMain()

   print("LLRMain started")

   -- check options
   if not username or username == "nobody" then
      if WIN32 then
	 AllocConsole()
	 paused = 1
      end
      print[[Please enter a username into llr-clientconfig.txt file.
(under windows, use wordpad, not notepad, to edit that file)
      ]]
      return
   end
   
   -- main loop
   
   if not asynchronous then
      ReadTosendfile()
      local unsent = tosend[1]
      SendAllResults()
      
      if once and unsent then
	 Logout()
	 return
      end
   end

   repeat
      if Work() then
	 if not asynchronous then
	    Logout()
	 end
	 
	 -- sleep one minute if some error occured
	 UpdateStatus("Sleeping ...")
	 if not asynchronous then
	    print("Sleeping ...")
	 end
	 local i
	 sleeping = 1
	 if asynchronous then
	    sleep(1)
	 else
	    for i=1,60,1 do
	       if not sleeping then
		  break
	       end
	       sleep(1)
	    end
	    SendAllResults()
	 end
      end
      
      if stopCheck() then
	 if not asynchronous then
	    Logout()
	 end
	 print("INTERRUPTED")
	 print("")
	 return
      end
   until once or cancelJob
   
   Logout()
   
end

-- try to launch the GUI
if GUIremote and not noGui and fox_init then

   function LaunchGui()
      local g = getfenv()
      local i, v, ng
      LLRglobals = g
--      ng = {}
--      for i, v in g do ng[i] = v end
--      setfenv(1, ng)
      print("running gui.lua ...")
--      local f = loadfile(EXEDIR.."gui.lua")
--      --setfenv(f, ng)
--      pcall(f)

      dofile(EXEDIR.."gui.lua")

      LLRglobals.GUIrunning = nil
   end

   if nil or not WIN32 then
      GUIrunning = 1
      create_thread(LaunchGui)
   else
--      SW_NORMAL = 1
--      ShellExecute(hwnd, "open", arg[0], "gui.lua", "", SW_NORMAL)
   end

--   LaunchGui()
--   return
end

if once or cancelJob then
   asynchronous = nil
end

if asynchronous then

   ReadTosendfile()
   DialogWithServer()
   serverDialogThreadRunning = 1
   create_thread(function()
		    print("server dialog thread started ...")
		    while serverDialogThreadRunning ~= -1 do
		       -- We retry the server every minute
		       -- except when we get a new result, 
		       -- then we retry at once
		       local i, n
		       n = getn(tosend)
		       for i=1,60,1 do
			  sleep(1)
			  if serverDialogThreadRunning == -1 or 
			     n ~= getn(tosend) then

			     break
			  end
		       end

		       if serverDialogThreadRunning ~= -1 then
			  SemaWait(semaphore)
			  DialogWithServer()
			  SemaSignal(semaphore)
		       end

		    end
		    serverDialogThreadRunning = nil
		 end)
end
   
while true do
   if not paused then
      LLRMain()
   end

   if GUIexit then
      break
   end

   stopReset()

   if paused then
      sleeping = 1
      sleep(1)
      sleeping = nil
      if stopCheck() then
	 break
      end
   else
      break
   end
end

   
if asynchronous then
   serverDialogThreadRunning = -1
   while serverDialogThreadRunning do
      print("waiting for server dialog thread to exit ...")
      sleep(1)
   end
end

DisconnectAllGUIs()

if Win32Exit then
   Win32Exit()
end

if GUIrunning and application then
   tinsert(GUIcommandQueue, 
	   function()
	      GUIquietExit = 1
	      FXRemoveCb(application)
	      FXSendCommand(application, application, FXApp.ID_QUIT, SEL_COMMAND)
	   end)
   sleep(1)
end

