--
-- 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 = "nobody"
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

   SendAllResults()


--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()
      if t and k and n then
	 print(format("  Fetching WU #1/%d: %s %s",WUCacheSize,k,n))
         changed = 1
      else
         return
      end
   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
      for i = s, WUCacheSize, 1 do
	 t, k, n = GetPair()
	 if t and k and n then
            print(format("  Fetching WU #%d/%d: %s %s",i,WUCacheSize,k,n))
  	    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
--   if getn(tosend) > 0 then

--      print(format("Sending %d result(s) to the server", getn(tosend)))

--      SendAllResults()

--   end
   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)

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

   -- 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
	    t, k, n = GetPair()
	    if t and k and n then
	       print(format("  Fetching WU #%d/%d: %s %s",i,WUCacheSize,k,n))
	       tinsert(more, { k = k, n = n })
	    else
	       break
	    end
	 end
      end

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

--      print(format("Current test : %s/%s (%s)", k, n, t))
      WriteWorkfile(t, k, n, more)
      SendStructureToAllGUIs("WUs", more, 1)
      --sleep(1)

      SemaSignal(semaphore)

      if cancelJob  then
--        local i
--        for i=1, getn(more), 1 do
--          print(format("getn1 #%s: %s %s\n", i, more[i].k, more[i].n))
--        end

	print(format("Cancelling : %s/%s (%s)", k, n, t))
 	result, residue = -2, "CANCEL"
--        print(format("cancel1: %s/%s", k, n))
        local tbl = { t = t, k = k, n = n, result = result, residue = residue, date = date("%c") }
        tinsert(tosend,tbl)
        WriteTosendfile()
        if more[1] then
          local p = more[1]
	  local k, n = p.k, p.n
--	  print(format("cancel1: %s/%s", k, n))
          local tbl = { t = t, k = k, n = n, result = result, residue = residue, date = date("%c") }
--          tinsert(tosend,tbl)
--          WriteTosendfile()

	  tremove(more, 1)
	  if WriteWorkfile(t, k, n, more) then
            print(format("Remove : (%s) %s/%s", k, n, t))
	    print("Could not write to workfile.txt file !")
	    SemaSignal(semaphore)
	    return -1
	  end
        else
 	  ClearWorkfile()
        end
      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

--      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

---------------------------------------------------------------------
-- Help function
---------------------------------------------------------------------
function Help()
   print([[
usage : llrnet [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

---------------------------------------------------------------------
-- LLR Main program loop
---------------------------------------------------------------------
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.]]
      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
