#!/usr/bin/env python
#
#   Version 1.1
#
#   Copyright 2016-2018 Lee Djavaherian
#
#   This file is part of TrillSat.
#
#   TrillSat is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   TrillSat is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with TrillSat.  If not, see <http://www.gnu.org/licenses/>.
###################################################################################

import sys, os, time
import multiprocessing    #to get input in parallel and avoid blocks
import argparse           #to get ax25d variables and set delimiter mode
import termios            #for LF mode, using Getch()
import select             #for CR mode, using sys.stdio.read()
import posix_ipc          #for PacketBBS and XMPPBot communication

sys.path.append('/trillsat/')
import trillsat_config    #configuration settings

#################################################
def Getch(lock):
  fd = sys.stdin.fileno()
  try:
    old_settings = termios.tcgetattr(fd)
    new_settings = old_settings[:]
    new_settings[3] &= ~termios.ICANON
    termios.tcsetattr(fd, termios.TCSANOW, new_settings)
    c = sys.stdin.read(1)
  except:
    Safeprint(lock,"Error in Getch")
  finally:
    termios.tcsetattr(fd, termios.TCSANOW, old_settings)
  return c
########################################
def GetLineOfText(lock, var_safe_call):
  try:
     line = ''
     mykey = ''
     delimiter = int(args.delimiter)

     #LF mode uses Getch() termios routine, since Linux line buffering
     #occurs on terminals
     
     #CR mode uses sys.stdin.read() and select, since read() in non-terminal
     #mode doesn't need to worry about line buffering, but it blocks STDOUT,
     #so I had to also use select to avoid this block.  But I put select
     #in blocking mode (to save CPU), but select doesn't block STDOUT like read()

     if delimiter == 10:
        while mykey != chr(delimiter):
           line += mykey
           mykey = Getch(lock)

     elif delimiter == 13:
        
        #replacing the 3rd [] with 0 will make it non-blocking       
        #this gets all text up to \r in one go, so it expects
        #that anything coming from axcall (call) ends in \r
        
        if sys.stdin in select.select([sys.stdin], [], [])[0]: 
          while mykey != chr(delimiter):
            line += mykey 
            mykey = sys.stdin.read(1)
            
     if line == 'b':
       return 'bye'
     exec ('bbs_queue_' + var_safe_call + '.send("' + line + '")')
  except Exception as e:
     Safeprint(lock, "Error in getlineoftext" + str(e))
                  
  return 'stay'
###########################################
def Safeprint(lock, message):
  dl = chr(int(args.delimiter))
  #lock.acquire()

  #make sure you break it into small enough chunks for ax.25 packets (256 bytes)
  #or you will get an error 90 "message too long"
  #I get the error if I have only 254 characters in my file
  #the dl delimiter character adds 1 character, so 255 bytes seems to be max limit 
  #this may be due to ax.25 SOCK_SEQPACKET size limitation of 256 bytes - not sure
  try:
    message += dl #add delimiter to end of message
    message_packet = ''
    count = 0
    for character in message:
      count += 1
      message_packet += character
      if count % 255 == 0:
        sys.stdout.write(message_packet)
        sys.stdout.flush()
        message_packet = ''
    sys.stdout.write(message_packet)
    sys.stdout.flush() #do a final flush for remaining chars
  except Exception as e:
    #can't use Safeprint for itself or it will recurse and overflow stack
    sys.stdout.write('Safeprint error: ' + str(e))
    sys.stdout.flush() 
  #lock.release()
###########################################
def Textprint(message):
  try:
    message += chr(13) + chr(10) #add cr/lf to end of message
    message_packet = ''
    count = 0
    for character in message:
      count += 1
      message_packet += character
      if count % 255 == 0:
        sys.stdout.write(message_packet)
        sys.stdout.flush()
        message_packet = ''
    sys.stdout.write(message_packet)
    sys.stdout.flush() #do a final flush for remaining chars
  except Exception as e:
    Safeprint(lock, 'Textprint error: ' + str(e))
##############################################################
def ShowMenu():

  global var_safe_call
  dl = chr(int(args.delimiter))
  Safeprint(lock, "i - information about the PBBS" + dl + \
                  "m - send message to station operator" + dl + \
                  "c - request live chat with station operator" + dl + \
                  "? - help - show this menu" + dl + \
                  "b - bye (disconnect)")

  if os.path.exists(trillsat_config.maildir + var_safe_call):
    Safeprint(lock, dl + 'You have mail from ' + trillsat_config.stationcall + '.  Type r to read.')

###########################################################
def ShowKeys():
  Safeprint (lock, "(i)nfo (m)essage, (c)chat, (?)help, (b)ye: ?") 
############################################# 
def PacketBBS(lock, var_safe_call):

 global bot_queue

 chatmode = ''
 dl = chr(int(args.delimiter))
 try:
  chatrequest = 'canceled'
  
  Safeprint(lock, dl + dl + dl + "Welcome to the PBBS.")
  ShowMenu()

  while 1:
   line, _ = eval('bbs_queue_' + var_safe_call + '.receive()')
   
   #add line below for Python 3
   if sys.version_info[0] >= 3:
     line = line.decode("utf-8")
   mykey = ''
   command = line.split(' ')

   #note - these chat botpassword commands are bad if xabber user sends them while the other user is in message mode
   #add better code for message mode later

   if chatmode == 'chat':
     ####### chatmode #############
     if command[0] == trillsat_config.botpassword:
         if command[1] == var_safe_call:
           if command[2] == 'MESSAGE':  #MESSAGES must be in both chat and no chat
             Safeprint(lock, " ".join(command[3:]))
           elif command[2] == 'CHAT_ENDED':
             Safeprint(lock, "Station operator has ended the chat.")
             ShowKeys()
             chatmode = '' #weird and buggy, can drop to normal mode below using same command - fix later
           elif command[2] == 'CHAT_LINE':
               Safeprint (lock, " ".join(command[3:])) #xmpp to bbs (skipping control code), rejoin is not ideal, fix later
     else:
             Safeprint (lock, 'You sent: ' + line)
             bot_queue.send(var_safe_call + ': ' + line)
             #note that bbs user is trapped in chat, at discretion of station operator, but can always type "b"-- maybe accidentally!
   else:
    ######### normal mode ###########
    if command[0] == trillsat_config.botpassword:
       if command[1] == var_safe_call:
          if command[2] == 'MESSAGE':  #MESSAGES must be in both chat and no chat
            Safeprint(lock, " ".join(command[3:]))
          elif command[2] == 'CHAT_ACCEPTED':
            Safeprint(lock, "Station operator has enabled chat.  Entering chat mode.")
            chatmode = 'chat'
          elif command[2] == 'CHAT_DENIED_AWAY':
            Safeprint(lock, "Station operator is not available for chat.  Enter m to leave a message.")
            ShowKeys()
          elif command[2] == 'CHAT_DENIED_IN_CHAT':
            Safeprint(lock, "Station operator is currently chatting with another person and has been notified of your request.  He/she will respond afterwards if they have time, or you can enter m to leave a message.")
            ShowKeys()

    elif command[0] == 'r':
       try:
         msgfile = open(trillsat_config.maildir + var_safe_call, 'r')
         Safeprint(lock, msgfile.read())          
         msgfile.close()
         os.remove(trillsat_config.maildir + var_safe_call)
       except FileNotFoundError:
         Safeprint(lock, 'You have no mail.')
         ShowKeys()
    elif command[0] == 'i':
      msgfile = open(trillsat_config.info_file, "r")
      Safeprint(lock, msgfile.read())
      msgfile.close()
      ShowKeys()

    elif command[0] == 'm':
      paragraph = ''
      line = ''
      leaveflag = 0
      Safeprint(lock, "Enter message (/EX) to send:")
      while leaveflag != 1:
        paragraph += line
        line, _ = eval('bbs_queue_' + var_safe_call + '.receive()')
        if sys.version_info[0] >= 3:
           line = line.decode("utf-8")
        command = line.split(' ')
        if command[0] == trillsat_config.botpassword:
          if command[1] == var_safe_call:
            if command[2] == 'MESSAGE':  #allow messages to work here too
              Safeprint(lock, " ".join(command[3:]))
            elif command[2] == 'CHAT_ACCEPTED':
              bot_queue.send(var_safe_call + ' is leaving you a message right now.  Try again later.')
        elif line[-3:] == '/EX':
          paragraph += line[:-3]
          leaveflag = 1
          timestamp = time.strftime("%a, %d %b %Y %H:%M:%S CST", time.localtime())
          msgfile = open(trillsat_config.message_file, "a")          
          msgfile.write('Date: ' + timestamp + "\nFrom: " + var_safe_call + paragraph + "\n--------------------\n")
          msgfile.close()
          bot_queue.send(var_safe_call + ' has left a message.')
          Safeprint (lock, "Message has been sent.")
          ShowKeys()
        else:
          paragraph = paragraph + dl
      
    elif command[0] == 'c':
          if chatrequest == 'canceled':
            chatrequest = 'requested'            	
            bot_queue.send('CHAT_REQUEST: ' + var_safe_call)
            Safeprint(lock, "Chat with " + trillsat_config.stationcall + " has been requested and the Station Operator has been paged.  Please allow a few minutes for him to respond or press c again to cancel chat request.")
            ShowKeys()
          else:
            chatrequest = 'canceled'
            Safeprint(lock, "Chat with " + trillsat_config.stationcall + " has been canceled.")
            ShowKeys()
    elif command[0] == '?':
         ShowMenu()  

 except Exception as e:
    Safeprint(lock, "Error in bbs: " + str(e) )

#####################################################################
try:
     
  try: 
   parser = argparse.ArgumentParser(description='This is a packet bot.')
   parser.add_argument('-d','--port', help='AX.25 port connection is on',required=True)
   parser.add_argument('-U','--remotecallsign',help='Remote callsign', required=True)
   parser.add_argument('-S','--remotecallandssid',help='Remote callsign and SSID', required=True)
   parser.add_argument('-n','--delimiter',help='Use ascii value as delimiter for Linux testing', required=False)
   args = parser.parse_args()
   var_safe_call = args.remotecallandssid.replace('-', '_')
  except:
     Safeprint (lock, "error with parsing")

  lock = multiprocessing.Lock()
  
  try: 
    bot_queue = posix_ipc.MessageQueue('/bot_queue')
    #dynamically create unique posix message queue based on name of remote caller
    #this allows multiple bbs instances to receive data from bot
    exec('bbs_queue_' + var_safe_call + ' = posix_ipc.MessageQueue("/bbs_queue_' + var_safe_call + '" , posix_ipc.O_CREAT)')
  except Exception as e: Safeprint(lock, str(e))

  ####### JUST RECEIVE REMOTE TEXT ON PORT 3 AND EXIT #####
  if args.port == '3':
     Safeprint(lock, 'What is your text? ')
     line, _ = eval('bbs_queue_' + var_safe_call + '.receive()')
     #add line below for Python 3
     #if sys.version_info[0] >= 3:
       #line = line.decode("utf-8")
     bot_queue.send(line)
     Safeprint(lock, 'Your text has been received.')
  else:  
   
    ###### ELSE, LAUNCH NORMAL PBBS #######
    #spawn separate process to get key input for Packet BBS  
    PacketBBS_process = multiprocessing.Process(target=PacketBBS, args=(lock, var_safe_call,))
    PacketBBS_process.start()

    futuretime = 0
     
    #bot_queue.send('New contact from: ' + var_safe_call + "\n")
    bot_queue.send('CALL_START: ' + var_safe_call)
    while GetLineOfText(lock, var_safe_call) != 'bye':
      pass

except Exception as e:
  Safeprint (lock, str(e))

finally:
  bot_queue.send('CALL_END: ' + var_safe_call)  
  bot_queue.close()
  exec('bbs_queue_' + var_safe_call + '.close()')
  exec('posix_ipc.unlink_message_queue("/bbs_queue_' + var_safe_call + '")')

  if args.port != '3':
    PacketBBS_process.terminate()

  #if launched by ax25d (using carriage return delimiter, shut down open socket)
  if args.delimiter == '13':
    time.sleep(2)
    try:
      os.system("axctl " + args.port + " " + args.remotecallandssid + " " + trillsat_config.stationcall + " kill")
    except:
      pass
  sys.exit()

 
