#!/usr/bin/env python3.8

"""
EDL - Edit an External Dynamic List

Many firewalls or other security services can import EDL's, or, lists of hosts to block.
This project can be used as command line tool and as a module. It has the capability to
to search, add, delete and view the designated list.

This module produces 2 EDL's, a master list and the actual EDL. The actual EDL is one IP
address per line.

By default, this script only takes IP addresses and will reject anything that is NOT
resolvable into an IP address.

The master list contains the IP, user who submitted the IP, the timestamp, whois owner and
abuse contact and finally a comment about the block. This information is for accounting purposes
only (although you might find the abuse contact useful), but the actual EDL is derived from it.
The actual EDL is just a list of IP's with no commentary or other designators. You must save the
master EDL and consumable EDL's, although in some places you can set the module to save the
consumable EDL automatically.

The Exclude file, will take IPs or CIDR ranges.

The reason for IPs only, we discovered a bug in Palo Alto's code for consuming EDL's that *really*
messed up our PA Cluster.

This module relies on two other modules from the same author, whois-rdap and py-helper-mod.
You must have them installed for this to work properly.

The module also has a shell mode.

Lastly, the module will also look at the environment variables, EDLMASTER, EDLFILE, EDLEXCLUDES
and EDLCOMMENTS for the EDL list and any excluded addresses or ranges and the default comment.
The default comment is only used when one is not provided.
"""

import os, sys
import re
import time
import argparse
import ipaddress
import random
import uuid
import copy
import csv
import getpass
import subprocess
import shutil
import socket
import cmd
import configparser

# Ma Stoof

import py_helper as ph
from py_helper import Msg,DbgMsg,ErrMsg,DebugMode,CmdLineMode,ModuleMode
from py_helper import Taggable, AuditTrail, ValidIP, IsIPv4, IsIPv6, IsNetwork
from py_helper import Clear, NewLine, Pause, Menu
from py_helper import SwapFile, Dump, BackUp, Restore, Touch, TmpFilename
from py_helper import TimestampConverter

# My module for getting whois info about IPs being placed in the EDL.
import whois

from datetime import datetime, timedelta
import datetime as dt

#
# Classes and Support
#

# Enviroment Variables to look for
__EnvEDLMaster__ = "EDLMASTER"
__EnvEDLFile__ = "EDLFILE"
__EnvEDLExcludes__ = "EDLEXCLUDE"
__EnvComment__ = "EDLCOMMENT"

# EDL Columns
Columns = [ "ip","user","timestamp","owner","abuse","comment","status" ]

# EDL Dictionary Row Template
EDLRowTemplate = {
	"ip" : None,
	"user" : None,
	"timestamp" : None,
	"owner" : None,
	"abuse" : None,
	"comment" : None,
	"status" : None
}

Timeformat = "%m/%d/%Y %H:%M:%S %p"

# EDL Entry
class EDLEntry(Taggable):
	"""
	Wrapper around an EDL Row line in the EDL file
	"""
	entry = None

	# Init Instance
	def __init__(self,**kwargs):
		"""Init instance"""
		global EDLRowTemplate, Columns

		super().__init__()

		entry = kwargs.get("entry",None)

		ip = kwargs.get("ip",None)
		user = kwargs.get("user",None)
		timestamp = kwargs.get("timestamp",datetime.now())
		owner = kwargs.get("owner",None)
		abuse = kwargs.get("abuse",None)
		comment = kwargs.get("comment",None)
		status = kwargs.get("status",False)

		if entry:
			if type(entry) == dict:
				self.entry = entry
			elif type(entry) == list:
				self.entry = dict(zip(Columns,entry))
		elif ip != None:
			self.entry = copy.deepcopy(EDLRowTemplate)

			self.entry["ip"] = ip
			self.entry["user"] = user
			self.entry["timestamp"] = timestamp
			self.entry["owner"] = owner
			self.entry["abuse"] = abuse
			self.entry["comment"] = comment
			self.entry["status"] = status

	# Print Entry
	def Print(output=None):
		"""Print Entry"""

		if output:
			output.write(entry)
			output.write("\n")
		else:
			print(self.entry)

	# Get IP from entry
	def IP(self,value=None):
		"""Return the blocked IP from the EDL Line"""

		if value:
			self.entry["ip"] = value

		return self.entry["ip"] if self.entry else None

	# Get User from entry
	def User(self,value=None):
		"""Return the submitting user from the EDL line"""

		if value:
			self.entry["user"] = value

		return self.entry["user"] if self.entry else None

	# Get Timestamp from entry
	def Timestamp(self,value=None):
		"""Return the time stamp the EDL entry was created from the EDL line"""

		if value:
			self.entry["timestamp"] = value

		return self.entry["timestamp"] if self.entry else None

	# Get Owner from entry
	def Owner(self,value=None):
		"""Return the Owner of the IP from the EDL line"""

		if value:
			self.entry["owner"] = value

		return self.entry["owner"] if self.entry else None

	# Get Abuse Contact from entry
	def Abuse(self,value=None):
		"""Return the abuse email address from the EDL line"""

		if value:
			self.entry["abuse"] = value

		return self.entry["abuse"] if self.entry else None

	# Get Comment from entry
	def Comment(self,value=None):
		"""Return the comment from the EDL line"""

		if value:
			self.entry["comment"] = value

		return self.entry["comment"] if self.entry else None

	# Get or Set Status
	def Status(self,value=None):
		"""Return the status from the EDL Line"""

		if value:
			self.entry["status"] = value

		return self.entry["status"] if self.entry else None

	# Return Internal Dictionary
	def GetDict(self):
		"""Return the underlying Dictionary"""

		return self.entry

	def Values(self):
		"""Get Values from Entry Dict"""

		results = list()

		if self.entry is not None:
			results = self.entry.values()

		return results

	#
	# Object to CSV Row or CSV Row to Object Operations
	#

	# Get Row For Appending To EDL File
	def GetRow(self):
		"""Get Row Suitable to appending to the EDL File"""

		global EDLRowTemplate, Timeformat

		row = None

		if self.entry != None:
			row = copy.deepcopy(EDLRowTemplate)

			for key,value in self.entry.items():
				if key == "timestamp":
					value = value.strftime(Timeformat)
				elif key == "status":
					value = "1" if value else "0"

				row[key] = value

		return row

	# Read Row
	def ReadRow(self,row):
		"""Read CSV Row Into EDL Entry"""

		global EDLRowTemplate, Timeformat

		self.entry = copy.deepcopy(EDLRowTemplate)

		for key,value in row.items():
			if key == "timestamp":
				value = datetime.strptime(value,Timeformat)
			elif key == "status":
				value = True if value == "1" else False

			self.entry[key] = value

		return self

	# Write Row
	def WriteRow(self,writer):
		"""Write Entry to CSV File"""

		global EDLRowTemplate, Timeformat

		if self.entry != None:
			row = self.GetRow()

			writer.writerow(row)

		return self

	#
	# Support Functions
	#

	# Get Whois Info
	def GetWhois(self):
		"""Get Whois Information"""

		if self.entry != None:
			owner = None
			abuse = None

			response = whois.GetIPInfo(self.entry["ip"])

			if response and response[0] == 200:
				owner = response[1] if len(response) > 1 and response[1] else "unknown"
				abuse = response[7] if len(response) > 7 and response[7] else ""
			else:
				owner = "unknown" if owner == None else owner
				abuse = "" if abuse == None else abuse

			self.entry["owner"] = owner
			self.entry["abuse"] = abuse

# EDL Shell
class EDLShell(cmd.Cmd):
	# cmd.Cmd attributes
	intro = "Welcome to the EDL Shell. Type help or ? to list commands.\n"
	prompt = "edl > "
	file = None

	# Parsers
	__parser__ = None
	# Create Command Parer
	__create_parser__ = None
	# Save Parser
	__save_parser__ = None
	# Dump Parser
	__dump_parser__ = None

	# Set Parser
	def SetParser(self,parser):
		"""Set Parser"""

		self.__parser__ = parser

	# Init Internal Parsers
	def InitParsers(self):
		"""Init Parsers"""

		if self.__create_parser__ == None:
			self.__create_parser__ = parser = argparse.ArgumentParser(description="File creation sub command")
			subparser = parser.add_subparsers(help="File creation operations",dest="operation")

			all_parser = subparser.add_parser("all",help="Create all files")
			all_parser.add_argument("masterfile",nargs="?",help="New Master file")
			all_parser.add_argument("edlfile",nargs="?",help="New edl file")
			all_parser.add_argument("excludes",nargs="?",help="New exclude file")

			master_parser = subparser.add_parser("master",help="Create master file")
			master_parser.add_argument("masterfile",nargs="?",help="Path to masterfile")

			edl_parser = subparser.add_parser("edl",help="Create edl file")
			edl_parser.add_argument("edl",nargs="?",help="Path to EDL file")

			ex_parser = subparser.add_parser("excludes",help="Create excludes file")
			ex_parser.add_argument("excludes",nargs="?",help="Path to excludes file")

		if self.__save_parser__ == None:
			self.__save_parser__ = parser = argparse.ArgumentParser(description="Save EDL sub command")
			parser.add_argument("edl",help="EDL file to save to")
			parser.add_argument("master",help="Master file for EDL")

		if self.__dump_parser__ == None:
			self.__dump_parser__ = parser = argparse.ArgumentParser(description="Dump file sub command")
			parser.add_argument("file",nargs="?",help="Dump selected file (master,edl,excludes)")

	# Create All Files
	def do_create(self,arguments):
		"""
		Create Masterfile, EDLFile and exlcudes, if they do not current exist
		"""

		global EDLMaster, EDLFile, Excludes

		if self.__create_parser__ == None: self.InitParsers()

		arguments = ph.ParseDelimitedString(arguments)

		try:
			args,unknowns = self.__create_parser__.parse_known_args(arguments)

			if args.operation == "all":
				DbgMsg("Creating ALL - {args.master}, {args.edl}, {args.excludes}")

				if args.masterfile != None:
					EDLMaster = args.masterfile

				CreateMaster()

				if args.edlfile != None:
					EDLFile = args.edlfile

				ph.Touch(EDLFile)

				if args.excludes != None:
					Excludes = args.excludes

				ph.Touch(Excludes)
			elif args.operation == "master":
				DbgMsg(f"Creating Master {args.master}")

				if args.masterfile != None:
					EDLMaster = args.masterfile

				CreateMaster()
			elif args.operation == "edl":
				DbgMsg(f"Creating edl {args.edl}")

				if args.edlfile != None:
					EDLFile = args.edlfile

				ph.Touch(EDLFile)
			elif args.operation == "excludes":
				DbgMsg(f"Creating Excludes - {args.excludes}")

				if args.excludes != None:
					Excludes = args.excludes

				ph.Touch(Excludes)
			else:
				Msg("No operation, all, master, edl, excludes provided, can't do anything")

		except SytemExit:
			pass

	# Get or Set Debugmode
	def do_debugmode(self,arguments):
		"""Get or set DebugMode"""

		if arguments != None and arguments != "":
			try:
				DebugMode(ParseBool(arguments))
			except:
				Msg(f"What is {arguments}?")
		else:
			Msg(f"Debugmode = {DebugMode()}")

	# Get or Set EDL Master file
	def do_masterfile(self,arguments):
		"""Get or Set Masterfile"""

		global EDLMaster

		if arguments != None and arguments != "":
			EDLMaster = arguments

		Msg(f"EDL Master file is currently {EDLMaster}")

	# Get or Set EDL File
	def do_edlfile(self,arguments):
		"""Get or Set EDL File"""

		global EDLFile

		if arguments != None and arguments != "":
			EDLFile = arguments

		Msg(f"EDL file is currently {EDLFile}")

	# Get or Set Excludes File
	def do_excludes(self,arguments):
		"""Get or Set Excludes File"""

		global Excludes

		if arguments != None and arguments != "":
			Excludes = arguments

		Msg(f"Excludes file is currently {Excludes}")

	# Add Comment To Responses
	def do_comment(self,arguments):
		"""Add comments to responses"""

		global Responses

		arguments = arguments.replace("'","").replace('"',"")

		if not arguments in Responses:
			AddResponse(arguments)
			Msg(f"Added {arguments}")
		else:
			Msg("Already exists in list")

	# Show Comments
	def do_comments(self,arguments):
		"""Show comments"""

		global Responses

		Msg("\nComments\n===========")

		count = 1

		for response in Responses:
			Msg("{}. {}".format(count,response))
			count += 1

		NewLine()

	# Get or set audit file
	def do_auditfile(self,arguments):
		"""Set or Get Audit File"""

		global AuditFile

		if arguments != None and arguments != "":
			AuditFile = arguments

		Msg(f"Audit file is currently {AuditFile}")

	# Do DNS Query for Arguments.
	def do_dns(self,arguments):
		"""Do DNS Check For Arguments"""

		hosts = re.split("\s+",arguments)

		iplist = HostIPCheck(hosts)

		for ip in iplist:
				Msg(f"{ip}")

	# Command handlers

	# Save EDL File
	def do_save(self,arguments):
		"""Save EDL File"""

		global EDLMaster, EDLFile

		if self.__save_parser__ == None: self.InitParsers()

		arguments = arguments.split(" ")

		try:
			args,unknowns = self.__save__parser__.parse_known_args(arguments)

			edlfile = EDLFIle if args.edl == None else args.edl
			masterfile = EDLMaster if args.master == None else args.master

			Save(edlfile,masterfile)
		except SystemExit:
			pass

	# Add To EDL
	def do_add(self,arguments):
		"""Add Entry to EDL"""

		global Responses

		params = list()

		params.append("add")

		args = ph.ParseDelimitedString(arguments)

		params.extend(args)

		try:
			args, unknowns = self.__parser__.parse_known_args(params)

			comment = args.comment

			if CmdLineMode() and comment in [ None, "" ]:
				comment = GetComment()
			elif len(Responses) > 0:
				comment = Responses[0]
			else:
				comment = "No comment provided"

			entries = Add(args.host,comment=args.comment)

			for entry in entries:
				Msg(f"Added {entry}")
		except SystemExit:
			pass

	# Bulk Add
	def do_bulkadd(self,args):
		"""Bulk Add"""

		filename,comment = args.split(" ",1)

		if comment == "": comment = None

		BulkAdd(filename,comment=comment)

	# Remove From EDL
	def do_remove(self,arguments):
		"""Remove Entry From EDL"""

		params = list()

		params.append("remove")

		params.extend(re.split("\s+",arguments))

		args = __parser__.parse_args(params)

		removed = Remove(args.host)

	# Remove Alias
	def do_del(self,arguments):
		"""Remove Alias"""

		self.do_remove(arguments)

	# Bulk Remove
	def do_bulkrm(self,args):
		"""Bulk Remove"""

		BulkRemove(args)

	# Search From EDL
	def do_search(self,arguments):
		"""Search EDL"""

		params = list()

		params.append("search")

		targs = ph.ParseDelimitedString(arguments)

		params.extend(targs)

		try:
			args,unknowns = ParseArgs(arguments=params)

			results = Search(args.search_str,by_type=args.by_type)

			for result in results:
				Msg(result)
		except SystemExit:
			pass

	# Cull
	def do_cull(self,args):
		"""Cull Entries from File"""

		if args == "simulate":
			items = Cull(simulate=True)
		else:
			items = Cull()

		buffer = ""

		for item in items:
			buffer += f"{item}\n"

		ph.Page(buffer)

	# Dump File
	def do_dump(self,arguments):
		"""Dump File"""

		global EDLMaster,EDLFile,Excludes

		if self.__dump_parser__ == None: self.InitParsers()

		try:
			args,unknowns = self.__dump_parser__.parse_known_args(arguments.split(" "))

			file = None

			if args.file == "master":
				file = EDLMaster
			elif args.file == "edl":
				file = EDLFile
			elif args.file == "excludes":
				file = Excludes
			else:
				Msg(f"Don't know what '{args.file}' is")

			if file:
				if os.path.exists(file):
					ph.Page(file)
				else:
					Msg(f"File, {file} does not presently exist")
		except SystemExit:
			pass

	# Show Data Information
	def do_info(self,args):
		"""Show Information About Data"""

		global EDLMaster, EDLFile, Excludes, AuditFile, DMEDLFile
		global MaxAge, LastAdd, Responses, Version
		global __EnvEDLMaster__, __EnvEDLFile__, __EnvEDLExcludes__,__EnvComment__

		def GetInfo(filename):
			msg = "does not presently exist"

			if os.path.exists(filename):
				value = ph.BestUnits(os.path.getsize(filename),units=1)
				lines = ph.LineCount(filename=filename)

				msg = f"{value} {lines} lines"

			return msg

		Msg("{:<20} : {}".format("Version",Version))
		Msg("{:<20} : {} {}".format("EDL Master File",EDLMaster,GetInfo(EDLMaster)))
		Msg("{:<20} : {} {}".format("EDL File",EDLFile,GetInfo(EDLFile)))
		Msg("{:<20} : {} {}".format("Exclude File",Excludes,GetInfo(Excludes)))
		Msg("{:<20} : {} {}".format("Test EDL File",DMEDLFile,GetInfo(DMEDLFile)))
		Msg("{:<20} : {}".format("Audit File",AuditFile))
		Msg("{:<20} : {} days".format("MaxAge",MaxAge.days))
		Msg("{:<20} : {}".format("LastAdd",LastAdd))
		Msg("{:<20} : {}".format("EDL Master Env",os.environ.get(__EnvEDLMaster__,"Not present")))
		Msg("{:<20} : {}".format("EDL File Env",os.environ.get(__EnvEDLFile__,"Not Present")))
		Msg("{:<20} : {}".format("Excludes Env",os.environ.get(__EnvEDLExcludes__,"Not Present")))
		Msg("{:<20} : {}".format("Comment Env",os.environ.get(__EnvComment__,"Not Present")))
		Msg("{:<20} : {}".format("Responses",""))
		for comment in Responses:
			Msg(f"\t{comment}")

	# Exit
	def do_quit(self,args):
		"""Exit Shell"""
		return True

	# Exit
	def do_exit(self,arguments):
		"""Exit Shell"""
		return True

#
# Variables and Constants
#

# Ask before sumbit
Confirm=False

# Explicit No Prompt
NoPrompt=False

# Autosave EDLFIle
AutoSave=False

# Version
VERSION=(0,0,39)
Version = __version__ = ".".join([ str(x) for x in VERSION ])

# Parser
__Parser__ = None

# Config File
__Config__ = "/etc/edl/config"

# Response list
Responses = list()

# Test File
DMEDLFile="/tmp/edl.test.txt"
# EDL Master File
EDLMaster = "/tmp/edlmaster.csv"
# EDL File
EDLFile="/tmp/edl.txt"
# Exclude File
Excludes="/tmp/edl-excl.txt"
# Audit Trail File
AuditFile=None

# Acceptable IPs
NoIPv6 = False

# Cull MaxAge
# See Unix 'man date' for details on the -d option for format of this string
MaxAge=timedelta(days=90)

# Last added item
LastAdd=""

# Audit Messages
# Excluded Audit Message
AuditMsg_Excluded = "The IP {} is excluded from being blocked"

#
# Lambdas
#

# Parse Text String Into Boolean Value
ParseBool = lambda S : False if S.lower() == "false" else True

#
# Functions
#

#
# Support Functions
#

# Add To Audit Trail
def Audit(message):
	"""Wrapper for writing to an audit trail, IF the audit file is defined"""

	if AuditFile:
		AuditTrail(AuditFile,message)

# Check Host/IP Submitted
def HostIPCheck(host):
	"""
	Check Host String, if IP, pass through, if not, attempt DNS resolution.
	If resolution fails, return None

	"""

	hosts = None
	iplist = list()

	if not type(host) is list:
		hosts = [ host ]
	else:
		hosts = host

	for host in hosts:
		if not ValidIP(host):
			try:
				# host = socket.gethostbyname(host)

				records = socket.getaddrinfo(host,None)

				for record in records:
					ip = record[4][0]

					if not ip in iplist:
						iplist.append(ip)
			except:
				# Pass errors silently
				pass
		else:
			iplist.append(host)

	return iplist

# Create or Convert Timestamp
def Timestamp(timestamp=None):
	"""Create or Convert Timestamp"""

	global Timeformat

	tsc = TimestampConverter()

	reg_ex = "^\d{1,2}/\d{1,2}/\d{4}$\s+ \d{1,2}\:\d{1,2}\:\d{1,2}\s+([aA]|[pP])[mM]$"
	fmt = Timeformat

	tsc.AddTimeFormat(reg_ex,fmt)

	dt = None

	if timestamp == None:
		dt = datetime.now()
		timestamp = dt.strftime(fmt)
	elif type(timestamp) is datetime:
		dt = timestamp
		timestamp = timestamp.strftime(fmt)
	elif type(timestamp) is str:
		tsc = ph.TimestampConverter()

		dt = tsc.ConvertTimestamp(timestamp)
		timestamp = dt.strftime(fmt)

	return timestamp,dt

# Preset Comment for adds
def AddResponse(comment):
	"""
	Add a comment to the cached comment list
	"""
	global Responses

	if not comment in Responses:
		Responses.append(comment)

#
# Business Functions
#

# Save Consumable EDL
def Save(edlfile=None,masterfile=None):
	"""Save Consumable EDL"""

	global EDLMaster, EDLFile

	if masterfile == None: masterfile = EDLMaster
	if edlfile == None: edlfile = EDLFile

	with open(masterfile,"r",newline="") as csvfile:
		reader = csv.DictReader(csvfile)

		with open(edlfile,"w") as f_out:
			for row in reader:
				entry = EDLEntry().ReadRow(row)

				ip = entry.IP()

				f_out.write(f"{ip}\n")

# Create Master File
def CreateMaster(masterfile=None):
	"""
	Create Master File

	Since the master file is a CSV with a header, this function will create
	an empty CSV with header.
	"""

	global EDLMaster, Columns

	if masterfile == None: masterfile = EDLMaster

	DbgMsg(f"Masterfile = {masterfile}")

	with open(masterfile,"w",newline='') as csvfile:
		writer = csv.DictWriter(csvfile,fieldnames=Columns)

		DbgMsg("Writing header")
		writer.writeheader()

# Determine if supplied IP address is in the excludes list
def Excluded(ip,exclude_file=None):
	"""
	Determine if the supplied IP string appears inside the exclude file, if an exclude file is supplied and exists.
	"""

	global Excludes

	DbgMsg("Entering edl::Excluded",dbglabel="edl")

	excluded = False

	if exclude_file == None: exclude_file = Excludes

	if ValidIP(ip) and exclude_file and os.path.exists(exclude_file):
		with open(exclude_file,"rt") as excludes:
			for entry in excludes:
				cleaned = entry.strip().split("#")
				item = cleaned[0].strip()

				if IsNetwork(item):
					ip = ipaddress.ip_address(ip)

					net = ipaddress.ip_network(item)

					if ip in net:
						excluded = True
						Audit(AuditMsg_Excluded.format(ip))
						break
				elif item == ip:
					excluded = True
					break

	DbgMsg("Exiting edl::Excluded",dbglabel="edl")

	return excluded

# Find Single Entry
def FindEntry(ip,masterfile=None):
	"""
	Find a Block Entry in the EDL
	"""

	global EDLMaster, Columns

	if masterfile == None: masterfile = EDLMaster

	entry = None

	if ValidIP(ip):
		if os.path.exists(masterfile):
			with open(masterfile,newline='') as csvfile:
				reader = csv.DictReader(csvfile)

				for row in reader:
					if ip == row["ip"]:
						entry = EDLEntry().ReadRow(row)
						break

	return entry

# Search For Block
def Search(search_str,by_type="ip",exit_early=False,silent=False,masterfile=None):
	"""Search the EDL for an existing block"""

	global EDLMaster, Columns

	if masterfile == None: masterfile = EDLMaster

	hits = list()

	tsc = TimestampConverter()

	with open(masterfile, newline="") as csvfile:
		reader = csv.DictReader(csvfile)

		for row in reader:
			entry = EDLEntry().ReadRow(row)

			if by_type != "timestamp" and search_str == row[by_type]:
				values = list(row.values())
				hits.append(values)

				if exit_early: break
			elif by_type == "timestamp":
				sts = tsc.ConvertTimestamp(search_str)

				if sts != None:
					if sts == enty.Timestamp():
						hits.append(entry)

						if exit_early: break

	return hits

# Append TO EDL
def AppendToEDL(entry,masterfile=None,edlfile=None):
	"""
	Append new entry to the EDL
	"""
	global EDLMaster, Columns, AutoSave, NoIPv6, AuditFile

	DbgMsg("Entering edl::AppendToEDL",dbglabel="edl")

	if masterfile == None: masterfile = EDLMaster

	success = True

	if type(entry) is EDLEntry:
		pass
	elif type(entry) is list:
		entry = dict(zip(Columns,entry))

		entry = EDLEntry(entry=entry)
	elif type(entry) is dict:
		entry = EDLEntry(entry=entry)

	ip = entry.IP()

	if IsIPv4(ip) or (IsIPv6(ip) and not NoIPv6):
		if not os.path.exists(masterfile):
			CreateMaster(masterfile=masterfile)

		with open(masterfile,"a",newline='') as csvfile:
			writer = csv.DictWriter(csvfile,Columns)

			entry.WriteRow(writer)

		if AuditFile: Audit("Appended {} to edl master".format(entry.GetRow()))

		if AutoSave: Save(edlfile,masterfile)
	else:
		DbgMsg(f"Rejected {ip}, NoIPv6 = {NoIPv6}")
		success = False

	DbgMsg("Exiting edl::AppendToEDL",dbglabel="edl")

	return success

# Add EntryEntry
def AddEDLEntry(entry,masterfile=None,edlfile=None):
	"""Add Filled In EDLEntry To EDL"""

	DbgMsg("Entering edl::AddEDLEntry",dbglabel="edl")

	existing = False
	excluded = False
	success = True

	if entry != None:
		if not Excluded(entry.IP()):
			found = FindEntry(entry.IP(),masterfile)

			if found == None:
				if entry.Owner() == None or entry.Abuse() == None:
					entry.GetWhois()

				AppendToEDL(entry,masterfile=masterfile,edlfile=edlfile)
			else:
				existing = True
		else:
			exclude = True
			success = False
	else:
		success = False

	DbgMsg("Exiting edl::AddEDLEntry",dbglabel="edl")

	return (success,excluded,existing)

# Add IP (or list of IPs, or DNS Names) To EDL Master
def Add(host,user=None,timestamp=None,owner=None,abuse=None,comment=None,protect=False,nosleep=False,masterfile=None,edlfile=None):
	"""Add Host/List of Hosts/Subnets/List of subnets/DNS names to EDL Master"""

	global LastAdd, Responses, EDLRowTemplate

	DbgMsg("Entering edl::Add",dbglabel="edl")

	if user == None:
		user = getpass.getuser()

	if timestamp == None:
		timestamp = datetime.now()
	elif type(timestamp) == str:
		old_timestamp,timestamp = Timestamp(timestamp)

	if comment == None:
		if len(Responses) > 0:
			comment = Responses[0]
		else:
			comment = "No comment supplied"

	results = list()
	hosts = None

	if type(host) is list:
		hosts = host
	else:
		hosts = [ host ]

	sanity_check = list()

	# Check for DNS names
	for host in hosts:
		if IsNetwork(host) or ValidIP(host):
			sanity_check.append(host)
		else:
			# Not subnet or IP
			ips = HostIPCheck(host)

			sanity_check.extend(ips)

	hosts = sanity_check

	# Builds a list of 5-tuples (invalid-flag,exists-flag,entry,excluded-flag,edl_entry)
	for host in hosts:
		# Determine if Excluded
		# Determine if already in EDL
		# If abuse/owner None, do Whois
		# if comment None, try to get comment
		# Create EDLEntry, give to AppendToEDL

		entry = EDLEntry(ip=host,user=user,timestamp=timestamp,owner=owner,abuse=abuse,comment=comment,status=protect)

		result = {
			"invalid" : False,
			"exists" : False,
			"entry" : None,
			"excluded" : False,
			"edl_entry" : None
			}

		if not Excluded(host):
			found = FindEntry(host)

			if found == None:
				if owner == None or abuse == None:
					entry.GetWhois()

				AppendToEDL(entry,masterfile=masterfile,edlfile=edlfile)
				result["entry"] = list(entry.GetRow())
				result["edl_entry"] = entry
				results.append(tuple(result.values()))
			else:
				# Exists
				result["exists"] = True
				result["entry"] = found.GetRow()
				result["edl_entry"] = found
				results.append(tuple(result.values()))
		else:
			# Excluded
			result["excluded"] = True
			results.append(tuple(entry.Values()))

		if len(hosts) > 1 and not nosleep:
			time.sleep(4)

	DbgMsg("Exiting edl::Add",dbglabel="edl")

	return results

# Bulk Add
def BulkAdd(fname,user=None,timestamp=None,owner=None,abuse=None,comment=None,protect=False,masterfile=None,edlfile=None):
	"""
	Bulk add file of IP address to EDL. The file should be on IP per line.
	"""

	global AutoSave

	success = True

	adds = list()

	# Because of the way this is built, disable autosave temporarily to prevent excessive i/o
	tmp = AutoSave
	AutoSave = False

	try:
		if os.path.exists(fname):
			with open(fname,"rt") as ip_list:
				for line in ip_list:
					results = Add(line.strip(),user,timestamp,owner,abuse,comment,protect=protect,nosleep=True,masterfile=masterfile,edlfile=edlfile)

					for result in results:
						invalidFlag, existingFlag, entry,excluded, edl_entry = result

						if existingFlag or invalidFlag: # If exists or is invalid, skip the sleep interval
							continue
						else:
							adds.append(entry)

					# Must sleep to avoid WHOIS from Rate Limiting us
					time.sleep(4)
		else:
			success = False
	finally:
		AutoSave = tmp

	if len(adds) > 0 and AutoSave: Save(edlfile,masterfile)

	return success, adds

# Remove Block
def Remove(hosts,masterfile=None,edlfile=None):
	"""
	Remove an IP from the EDL Master if an audit trail is defined, it is recorded in the audit trail.
	The EDL is backed up before changes
	"""
	global EDLMaster, EDLFile, Columns, AutoSave

	if masterfile == None: masterfile = EDLMaster
	if edlfile == None: edlfile = EDLFile

	hitcount = 0

	TMP=TmpFilename(".edl_rem")

	# Backup Existing EDLMaster
	BackUp(masterfile)

	hosts = HostIPCheck(hosts)

	# Open Existing EDL Master
	with open(masterfile,newline='') as csvfile:
		reader = csv.DictReader(csvfile,Columns)

		# Open Temp file
		with open(TMP,"a",newline='') as tmpfile:
			writer = csv.writer(tmpfile)

			# Read in existing entries and compare to the item(s) we want removed
			# Only copy non-hits to temp file
			for row in reader:
				if type(hosts) is list:
					if not row["ip"] in hosts:
						r = list(row.values())
						writer.writerow(r)
					else:
						Audit(f"{row['ip']} removed from EDL")
						hitcount += 1
				elif row["ip"] != hosts:
					r = list(row.values())
					writer.writerow(r)
				else:
					Audit(f"{row['ip']} removed from EDL")
					hitcount += 1

	if hitcount == 0:
		os.remove(TMP)
	else:
		SwapFile(TMP,masterfile)

		if AutoSave:
			Save(edlfile,masterfile)

	return hitcount

# Bulk Remove
def BulkRemove(fname,masterfile=None,edlfile=None):
	"""
	Given a file with one IP per line, remove the given IPs from the EDL if they are in there
	"""
	global AutoSave

	success = True

	removes = list()

	if os.path.exists(fname):
		with open(fname,"rt") as ip_list:
			for ip in ip_list:
				removes.append(ip.strip())

		Remove(removes,masterfile,edlfile)
	else:
		success = False

	return success

# Replace IP Record in EDL
def Replace(fields,masterfile=None,edlfile=None):
	"""
	Replace an EDL line (defined by the blocked IP), with the new supplied one.
	"""

	Remove(fields[0])

	DbgMsg(fields)

	Add(fields[0],fields[1],fields[2],fields[3],fields[4],fields[5],masterfile=masterfile,edlfile=edlfile)

# Cull Records
def Cull(max_age=None,masterfile=None,edlfile=None,simulate=False):
	"""
	Using the module level MaxAge, remove entries from the EDL, older then the interval.
	"""

	global EDLMaster, MaxAge, Columns, AutoSave

	if masterfile == None: masterfile = EDLMaster

	if max_age == None:
		max_age = MaxAge
	else:
		if type(max_age) == str and max_age.isdigit():
			max_age = timedelta(days=int(max_age))
		elif type(max_age) == int:
			max_age = timedelta(days=max_age)
		elif type(max_age) == timedelta:
			pass
		else:
			max_age = MaxAge

	too_old = datetime.now() - max_age

	matches = list()

	lines = 0

	with open(masterfile,newline='') as csvfile:
		# Assuming here that the CSV file has a header... if not, an error may result here
		# reader = csv.DictReader(csvfile,Columns)
		reader = csv.DictReader(csvfile)

		for row in reader:
			lines += 1

			entry = EDLEntry()
			entry.ReadRow(row)

			if entry.Status():
				continue

			if entry.Timestamp() <= too_old:
				DbgMsg(f"removing {row['ip']}")
				Audit(f"{entry.IP()} was culled from edl, inserted on {entry.Timestamp()}")
				matches.append(entry.IP())

	if len(matches) > 0 and not simulate:
		DbgMsg(f"Removing old items")

		if not DebugMode(): Remove(matches,masterfile,edlfile)

		# No need to autosave here, Remove will handle it

	DbgMsg(f"{lines} processed, {len(matches)} matched")

	if DebugMode():
		Dump(masterfile)

	return matches

# Search for item in Exclude List
def SearchExclude(entry,exclude_file=None):
	"""
	Search for, and return, exclude item in the exlude file (if defined and exists)
	"""
	global Excludes

	if exclude_file == None: exclude_file = Excludes

	found = False

	if exclude_file and os.path.exists(exclude_file):
		with open(exclude_file,"rt") as excludes:
			for line in excludes:
				items = line.split("#")

				if entry in items[0].strip():
					found = True
					break

	return found

# Add Item to Exclude List
def AddExclude(item,comment=None,exclude_file=None):
	"""
	Add an IP or network exclude to the exclusion list
	"""
	global Excludes

	if exclude_file == None: exclude_file = Excludes

	success = True

	BackUp(exclude_file)

	if SearchExclude(item,exclude_file):
		Msg("{} already in list".format(item))
		success = False
	else:
		with open(exclude_file,"at") as excludes:
			line = item

			if comment:
				line += (" # " + comment)

			excludes.write(line + "\n")
			DbgMsg("{}, added".format(line))
			Audit("{} added to excludes".format(item))

	return success

# Remove Excluded IP/Range
def RemoveExclude(item,exclude_file=None):
	"""
	Remove the given IP or subnet from the excludes list
	"""

	global Excludes

	if exclude_file == None: exclude_file = Excludes

	success = False

	tmp = TmpFilename()

	BackUp(exclude_file)

	hits = 0

	with open(tmp,"wt") as tmpfile:
		with open(exclude_file,"rt") as excludes:
			for line in excludes:
				if item in line:
					hits += 1
					DbgMsg("{}, being removed".format(line))
					Audit("{} removed from excludes".format(item))
				else:
					line = line.strip()
					tmpfile.write(line + "\n")

	if hits > 0:
		os.remove(exclude_file)
		SwapFile(tmp,exclude_file)
		success = True
	else:
		os.remove(tmp)

	return success

#
# Interactive Functions
#

# Direct Edit EDL List
def DirectEditEDL(masterfile=None,edlfile=None,filename=None,save=False):
	"""
	Direct edit the EDL
	Only active in CmdLineMode
	"""
	global EDLMaster, EDLFile, AutoSave

	if masterfile != None:
		filename = EDLMaster
	elif edlfile != None:
		filename = EDLFile
	elif filename == None:
		filename = EDLMaster

	if ModuleMode(): return

	reply = "y"

	if not NoPrompt:
		reply = input("====> WARNING : Direct Editting is discouraged, continue anyway (y/N)? ").strip()

	if reply in [ "y", "Y", "yes" ]:
		BackUp(filename)

		# Execute nano with the edl master
		subprocess.call(["nano",filename])
		Audit("EDL Master was editted manually")

		if save or AutoSave: Save(EDLFile,EDLMaster)

# Get Comment
def GetComment():
	"""Get the currently cached comments"""

	global Responses

	if ModuleMode(): return

	comment = None

	if len(Responses) > 0:
		Msg("q|quit To Quit\nEnter any string for new comment\nOr select provided comment(s)\n=============")

		eo = { "q":"Quit", }
		reply = Menu(Responses,extra_options=eo,no_match=True)

		if reply and not reply == "q":
			if reply == "":
				reply = "No comment"

			if not reply in Responses:
				AddResponse(reply)

			comment = reply
	else:
		comment = input("Enter comment : ")

		if comment == "":
			comment = "No Comment"

		AddResponse(comment)

	return comment

# Edit One Entry
def EditEntry(ip,masterfile=None,edlfile=None):
	"""
	Edit a single entry of the EDL (based on the IP).
	Only active in CmdLineMode
	"""

	global EDLMaster, EDLFile

	if masterfile == None: masterfile = EDLMaster
	if edlfile == None: edlfile = EDLFILE

	entry = FindEntry(ip,masterfile)

	if entry and CmdLineMode():
		DbgMsg(entry)

		TMP = TmpFilename()

		ip = entry["ip"]
		old_line = ",".join(list(entry.values())[1:])

		with open(TMP,"wt") as tmpfile:
			print(old_line,file=tmpfile)

		subprocess.call([ "nano", TMP ])

		new_line = ""

		with open(TMP,"rt") as tmpfile:
			new_line = tmpfile.read().strip()

		if old_line != new_line:
			fields = new_line.split(",")
			fields.insert(0,ip)

			Replace(fields,masterfile,edlfile)
			Audit("{} entry was manually editted - was '{}'".format(ip,old_line))

#
# Parser functions, Diags and tests
#

# Prep for Debug Mode Ops
def PrepDebug():
	"""Prep the module for DebugMode, which include operating on a temporary copy of the EDL"""

	global EDLFile, DMEDLFile
	# Copies current EDLFile to temp and
	# switchs to temp file for test operations
	# This way the actual EDLFile does not get messed up by accident

	DebugMode(True)

	print("Entering Debug Mode...")

	# Setup working file
	shutil.copyfile(EDLFile,DMEDLFile)

	EDLFile=DMEDLFile

	print("Working EDL is now {}".format(EDLFile))

# Build Parser
def BuildParser():
	"""Build Parser"""

	global __Parser__

	if __Parser__ == None:
		# Config Argument Parser
		__Parser__ = argparse.ArgumentParser(description="EDL Manager")

		__Parser__.add_argument("-v","--version",action="store_true",help="Show version")
		__Parser__.add_argument("-d","--debug",action="store_true",help="Place app in debug mode")
		__Parser__.add_argument("--noipv6",action="store_true",help="IPv6 addresses are not accepted")
		__Parser__.add_argument("--master",help="Set Master file")
		__Parser__.add_argument("--edl",help="Set EDL Data file")
		__Parser__.add_argument("--exclude",help="Set Exclude file")
		__Parser__.add_argument("--age",help="Set maximum age in days for cull operations")
		__Parser__.add_argument("-p","--prompt",action="store_true",help="Set prompt before actions committed")
		__Parser__.add_argument("-n","--noprompt", action="store_true",help="Set to no prompting")
		__Parser__.add_argument("--autosave",action="store_true",help="Set Autosave mode for EDLFile")

		# Create Sub parsers
		subparsers = __Parser__.add_subparsers(help="Operations on EDL",dest="operation")

		# One offs

		# Shell Mode (Completely Interactive Feature)
		shell_parser = subparsers.add_parser("shell",help="Enter shell mode")

		# Test Mode
		test_parser = subparsers.add_parser("test",help="Enter Test Code")

		# Save Command
		save_parser = subparsers.add_parser("save",help="Save master file contents to EDL File")
		save_parser.add_argument("edl",nargs="?",default=None,help="Optional EDL file path")
		save_parser.add_argument("master",nargs="?",default=None,help="Optional masterfile, EDL file must be supplied first")

		# Edit Master File (Completely Interactive Feature)
		edit_master_parser = subparsers.add_parser("edit",help="Edit Masterfile")
		edit_master_parser.add_argument("-s","--save",action="store_true",help="Once master edit completes, save EDL")
		edit_master_parser.add_argument("file",nargs="?",help="File to edit, masterfile by default")

		# Backup command (Non-Interactive)
		backup_parser = subparsers.add_parser("backup",help="Backup data files")

		# Restore command (Non-Interactive)
		restore_parser = subparsers.add_parser("restore",help="restore data files")

		# Cull (Non-Interactive Feature)
		cull_parser = subparsers.add_parser("cull",aliases=["expire"],help="Cull Master/EDL file of older records")
		cull_parser.add_argument("days",nargs="?",default=None,help="Maximum age of entry in days")

		# Dump Master File (Completely Interactive)
		dump_parser = subparsers.add_parser("dump",aliases=["get","getmaster","getm"],help="Dump Master file")
		dump_parser.add_argument("file_spec",nargs="?",choices=["master","masterfile","edl","edlfile","excludes"],help="Optional file specification")

		# More complex sub commands

		# Add Functionality (Partially Interactive Feature)
		add_parser = subparsers.add_parser("add",help="Add an entry to EDL")
		add_parser.add_argument("-p","--protect",action="store_true",help="Set protection for record")
		add_parser.add_argument("host",help="Host to block (by default, IPv4 or hostname")
		add_parser.add_argument("comment",help="Comment for EDL Entry")

		# Bulk Add (Partially Interactive Feature)
		bulkadd_parser = subparsers.add_parser("bulkadd",aliases=['ba',"bulk"],help="Bulk Add")
		bulkadd_parser.add_argument("-p","--protect",action="store_true",help="Set protection")
		bulkadd_parser.add_argument("file",help="File to import for bulk add")
		bulkadd_parser.add_argument("comment",nargs="?",help="Bulk comment (optional)")

		# Remove (Partially Interactive)
		remove_parser = subparsers.add_parser("remove",aliases=["rm","del","delete"],help="Remove specified EDL Entry")
		remove_parser.add_argument("host",help="Host to remove (IP or DNS name)")

		# Bulk Remove (Partially Interactive)
		bulkremove_parser = subparsers.add_parser("bulkremove",aliases=["bulkrm","bulkdel","bulkdelete"],help="Bulk Remove")
		bulkremove_parser.add_argument("file",help="File to import for bulk remove")

		# Search (Partially Interactive)
		search_parser = subparsers.add_parser("search",help="Search Master file")
		search_parser.add_argument("by_type",choices=["ip","cidr","dns","user","owner","abuse","timestamp"],default="ip",help="Search by given type")
		search_parser.add_argument("search_str",help="Thing to search for")

		# Exclude (Non-Interactive)
		exclude_parser = subparsers.add_parser("exclude",aliases=["ex"],help="Exclude Host or CIDR Range")
		exclude_parser.add_argument("host",help="Host or CIDR Range to add to excludes")
		exclude_parser.add_argument("comment",nargs="?",help="Comment for entry")

		# Remove Exclusion (Partially Interactive)
		rmexclude_parser = subparsers.add_parser("removeexclude",aliases=["rmex","removeex","rmx","removex"],help="Remove exclusion")
		rmexclude_parser.add_argument("hosts",help="Host or CIDR to remove from excludes")

		# View/Dump Exclude List (Partially Interactive)
		viewexclude_parser = subparsers.add_parser("viewexcludes",aliases=["vex","viewx","dumpex","getex"],help="View/Dump/Return excludes list")

		# Check Exclude for item
		checkex_parser = subparsers.add_parser("checkexcludes",aliases=["chex","checkex"],help="Check excludes for item")
		checkex_parser.add_argument("exclude",help="Exclude to check for")

		# Edit IP (Completely Interactive)
		editip_parser = subparsers.add_parser("edithost",aliases=["editip"],help="Edit single host record in master")
		editip_parser.add_argument("host",help="Host entry to edit")

	return __Parser__

# Parse Arguments
def ParseArgs(arguments=None):
	"""Parse Arguments"""

	global __Parser__, MaxAge, Confirm, NoPrompt, EDLMaster, EDLFile, Excludes, AutoSave, NoIPv6
	global Version

	args = None

	if arguments == None:
		args,unknowns = __Parser__.parse_known_args()
	else:
		args,unknowns = __Parser__.parse_known_args(arguments)

	#
	# Set State Items that need to come first
	#

	# Set Debug Mode
	if args.debug:
		PrepDebug()

	if args.noipv6:
		NoIPv6 = True

	# Check for Master File Change
	if args.master:
		EDLMaster = args.master

	# Check for EDL File Change
	if args.edl:
		EDLFile = args.edl

	# Check for Exclude Change
	if args.exclude:
		Excludes = args.exclude

	# Set Interval
	if args.age and args.age.isdigit():
		MaxAge = timedelta(days=int(args.age))
	elif args.age:
		Msg("Interval must be in days and numeric")

	# Prompt state
	if args.prompt:
		Confirm = True
		NoPrompt = False

	if args.noprompt:
		Confirm = False
		NoPrompt = True

	# Check Autosave
	if args.autosave:
		AutoSave = True

	if args.version:
		Msg(f"Version : {Version}")

	return args, unknowns

# Plugin Run Pattern Handler
def run(**kwargs):
	"""Plugin Pattern Handler"""

	global EDLMaster, EDLFile, __Parser__

	# Check Kwargs

	arguments = kwargs.get("arguments",None)
	args = kwargs.get("args",None)

	# Make sure EDL Exists, if not, provision a new one
	if not os.path.isfile(EDLFile):
		Touch(EDLFile)

	if args == None:
		# Parse 'dem args my brother
		if arguments != None:
			args,unknowns = ParseArgs(arguments)
		else:
			args,unknowns = ParseArgs()

	DbgMsg("Entering run",dbglabel="edl")

	#
	# Now Check for actions
	#

	results = None
	success = True

	op = args.operation

	if op == "test":
		results = test()
	elif op == "shell" and CmdLineMode():
		shell = EDLShell()
		shell.SetParser(__Parser__)
		shell.cmdloop()
	elif op == "save":
		edlfile = args.edl
		masterfile = args.master

		Save(edlfile,masterfile)
	elif op == "edit" and CmdLineMode():
		filename = args.file

		if filename == None or filename in [ "masterfile", "master" ]:
			filename = EDLMaster
		elif filename == "edl":
			filename = EDLFile

		if filename != None and os.path.exists(filename):
			DirectEditEDL(filename=filename,save=args.save)
		else:
			Msg(f"'{filename}' either doesn't exist, is not readable or is pure pish, fix it")
	elif op in [ "cull", "expire" ]:
		results = Cull(args.days)

		items_culled = len(results)

		if items_culled > 0:
			Msg(f"{items_culled} records culled")

			for item in results:
				Msg(item)

	elif op == "backup":
		Backup(EDLMaster)
		Backup(EDLFile)
	elif op == "restore":
		Restore(EDLMaster)
		Restore(EDLFile)
	elif op in [ "dump", "get", "getmaster", "getm" ]:
		try:
			if args.file_spec != None:
				if args.file_spec in ["edl","edlfile"]:
					Dump(EDLFile)
				elif args.file_spec in ["excludes"]:
					Dump(Excludes)
				else:
					Dump(EDLMaster)

			else:
				Dump(EDLMaster)
		except Exception as err:
			Msg("Could not open requested file")

	elif op in [ "edithost", "editip" ]:
		host = args.host
		EditEntry(host)
	elif op == "add":
		Msg("Fast repeated adds may get rate limited by WHOIS")

		host = args.host
		comment = args.comment

		results = Add(host,comment=comment,protect=args.protect)

		for result in results:
			invalid,existing,entry,excluded,edl_entry = result

			if invalid:
				Msg(f"Invalid entry - {entry}")
			if existing:
				Msg(f"{entry['ip']} exists in EDL")

				if NoPrompt: continue

				reply = input("Exists, Show (y/n)? ")

				if reply == "y":
					Msg(entry)

			if excluded:
				Msg(f"{entry[0]} is an excluded address, it was not added to the EDL")

		success = True
	elif op in [ "bulkadd","ba","bulk" ]:
		Msg("Bulk adds have a builtin pause to prevent WHOIS from rate limiting this functionality")

		file = args.file
		comment = args.comment

		if len(Responses) > 0 and comment == None:
			comment = Responses[0]
		elif comment == None:
			comment = "No comment provided"
		else:
			AddResponse(comment)

		success,results = BulkAdd(file,comment=comment,protect=args.protect)
	elif op in [ "remove", "rm", "del", "delete" ]:
		Remove(args.host)
	elif op in [ "bulkremove", "bulkrm", "bulkdel", "bulkdelete" ]:
		BulkRemove(args.file)
	elif op == "search":
		results = Search(args.search_str,by_type=args.by_type)

		for result in results:
			Msg(result)
	elif op in [ "exclude", "ex" ]:
		AddExclude(args.host,args.comment)
	elif op in [ "removeexclude", "rmex", "removeex", "rmx", "removex" ]:
		RemoveExclude(args.hosts)
	elif op in [ "viewexcludes", "vex", "viewx", "dumpex","getex" ]:
		Dump(Excludes)
	elif op in [ "checkexcludes", "chex", "checkex", "checkx" ]:
		if SearchExclude(args.exclude):
			Msg("Is in exclude list")
		else:
			Msg("Not in exclude list")

	DbgMsg("Exiting run",dbglabel="edl")

	return results

#
# Initialization
#

# Initialize Module
def Initialize():
	"""Initialize Module"""

	global __EnvEDLMaster__,__EnvEDLFile__,__EnvEDLExcludes__,__EnvComment__,__Config__
	global EDLFile, EDLMaster, Excludes

	random.seed()

	comment = "No comment at this time"

	if os.path.exists(__Config__):
		cfg = configparser.ConfigParser()

		cfg.read(__Config__)

		EDLMaster = cfg.get("appsettings","EDLMaster",fallback="/tmp/edlmaster.csv")
		EDLFile = cfg.get("appsettings","EDLFile",fallback="/tmp/edlfile.csv")
		Excludes = cfg.get("appsettings","Excludes",fallback="/tmp/excludes.txt")

		comment = cfg.get("appsettings","Comment",fallback=comment)

	# Check Environment
	if __EnvEDLMaster__ in os.environ: EDLMaster = os.environ.get(__EnvEDLMaster__,EDLMaster)
	if __EnvEDLFile__ in os.environ: EDLFile = os.environ.get(__EnvEDLFile__,EDLFile)
	if __EnvEDLExcludes__ in os.environ: Excludes = os.environ.get(__EnvEDLExcludes__,Excludes)

	# Check for preset comment in ENV, preset if there
	comment = os.environ.get(__EnvComment__,None)

	if comment:
		AddResponse(comment)

	BuildParser()

# Init Instance
Initialize()

#
# Test Stub
#

# Run Test
def test():
	"""Test stub"""
	PrepDebug()

	DbgMsg("Life is THE test... don't fail it.")

#
# Main Loop
#

if __name__ == "__main__":
	"""Main loop for using the EDL as a cmd line script"""

	CmdLineMode(True)	# Place instance in cmdline mode

	run()


for num in range(1,41):

    for i in range(1,11):

        table = f"{num} X {i} = {num * i}\n"
        print(table,end="")

        file_name = f"File{num}.txt"
        with open(file_name,"a") as open_file:
            write_file = open_file.write(table)


#variaveis
erros = 0

def convert_to_int(numList):
    for item in numList:
        try:
            num = float(item)
            numList.append(num)
        except ValueError:
            print(f"Codejacks hated your {item}coal too much!")
    return numList

def soma_elementos(numVal):
    soma = 0
    for item in numVal:
        soma += item

print("Digite uma lista de números: ")
numEnt = input().split()

lista = convert_to_int(numEnt)

#output final
print("Números válidos: ", lista)
print("A soma dos itens é: ", soma_elementos(lista))
print("Total de erros: ",erros)


#!/usr/bin/env python
# -*- coding: utf-8 -*-

# third-party modules
from flask_restx import Resource, reqparse

# own modules
from app.modules.topic.bookmark.bookmark_controller import TopicBookmarkController
from app.modules.topic.bookmark.bookmark_dto import TopicBookmarkDto
from common.utils.decorator import token_required

__author__ = "hoovada.com team"
__maintainer__ = "hoovada.com team"
__email__ = "admin@hoovada.com"
__copyright__ = "Copyright (c) 2020 - 2020 hoovada.com . All Rights Reserved."

api = TopicBookmarkDto.api
_bookmark_request = TopicBookmarkDto.model_request
_bookmark_response = TopicBookmarkDto.model_response
_bookmark_get_params = TopicBookmarkDto.model_get_parser
_model_bookmark_multiple_topics_request = TopicBookmarkDto.model_bookmark_multiple_topics_request

@api.route('/all/bookmark')
class BookmarkTopicAll(Resource):
    @api.expect(_bookmark_get_params)
    def get(self):
        """Search all topic bookmark"""

        args = _bookmark_get_params.parse_args()
        controller = TopicBookmarkController()
        return controller.get(args=args)

    @token_required
    @api.expect(_model_bookmark_multiple_topics_request)
    def post(self):
        """Create multiple bookmarks for current user"""

        args = api.payload
        controller = TopicBookmarkController()
        return controller.create_multiple_topics_bookmarks(args=args)

@api.route('/<int:topic_id>/bookmark')
class BookmarkTopic(Resource):
    @api.expect(_bookmark_get_params)
    def get(self, topic_id):
        """Search all bookmarks from topic Id"""

        args = _bookmark_get_params.parse_args()
        args['topic_id'] = topic_id
        controller = TopicBookmarkController()
        return controller.get(args=args)

    @token_required
    @api.response(code=200, model=_bookmark_response, description='The model for bookmark.')
    def post(self, topic_id):
        """Create a bookmark on current user from topic id"""

        controller = TopicBookmarkController()
        return controller.create(topic_id=topic_id)

    @token_required
    def delete(self, topic_id):
        """Delete bookmark on current user from topic Id """

        controller = TopicBookmarkController()
        return controller.delete(topic_id=topic_id)


import os
from .Errors import PyAdfError

class JobRunnerConfiguration:
    """
    Configuration class for job runners.
    """

    def __init__(self, conffile=None, jobbasedir=None):

        # set default values, might be overwritten by initialize_from_conffile
        self._read_from_file = None

        self.default_shell = '/bin/bash'
        self.env_modules_load = {}
        self.parallel_config = {'job': {'parallel': True}}

        # number of cores to use for the parallel execution of jobs (using MPI etc.)
        # The following environment variables are evaluated (those appearing earlier
        # in this list take precedence over those appearing later)
        # - $PYADF_NPROC
        # - $TC_NUM_PROCESSES
        # - $NSCM (included to preserve previous behavior; could be removed in the future)
        #
        # All of these are overridden by nproc options included in parallel_config
        self.default_nproc = 1
        if 'PYADF_NPROC' in os.environ:
            self.default_nproc = int(os.environ['PYADF_NPROC'])
        elif 'TC_NUM_PROCESSES' in os.environ:
            self.default_nproc = int(os.environ['TC_NUM_PROCESSES'])
        elif 'NSCM' in os.environ:
            self.default_nproc = int(os.environ['NSCM'])

        if conffile is not None:
            self.initialize_from_conffile(conffile)
        else:
            if jobbasedir is None:
                default_conffile = os.path.join(os.environ['HOME'], '.pyadfconfig')
            else:
                default_conffile = self._find_default_conffile_in_parentdirs(jobbasedir)
            if (default_conffile is not None) and os.path.exists(default_conffile):
                self.initialize_from_conffile(default_conffile)

        if not self._validate_jobclass_dict(self.env_modules_load):
            raise PyAdfError('Error in JobRunnerConfiguration (env_module_load)')
        if not self._validate_jobclass_dict(self.parallel_config):
            raise PyAdfError('Error in JobRunnerConfiguration (parallel_config)')

    # noinspection PyUnresolvedReferences
    def initialize_from_conffile(self, fn):
        if not os.path.exists(fn):
            raise PyAdfError(f'JobRunner configuration file {fn} does not exist')
        self._read_from_file = os.path.abspath(fn)

        import importlib.util
        from importlib.machinery import SourceFileLoader
        spec = importlib.util.spec_from_loader("pyadf.jobrunnerconf.conffile",
                                               SourceFileLoader("pyadf.jobrunnerconf.conffile", fn))
        conf = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(conf)

        if 'default_shell' in dir(conf):
            self.default_shell = conf.default_shell
        if 'env_modules_load' in dir(conf):
            self.env_modules_load = conf.env_modules_load
        if 'parallel_config' in dir(conf):
            self.parallel_config.update(conf.parallel_config)

    def print_configuration(self):
        print("JobRunner configuration read from file: ", self._read_from_file)
        print("     default shell: ", self.default_shell)
        print("     env_modules_load: ", self.env_modules_load)

    @staticmethod
    def _find_default_conffile_in_parentdirs(jobbasedir):
        cur_dir = jobbasedir
        while not cur_dir == '/':
            test_file = os.path.join(cur_dir, '.pyadfconfig')
            if os.path.exists(test_file):
                return test_file
            else:
                cur_dir = os.path.dirname(cur_dir)
        return None

    @staticmethod
    def _pyadf_class_from_string(clsname):
        import inspect
        import importlib
        m = importlib.import_module('pyadf')

        try:
            cls = getattr(m, clsname)
        except AttributeError:
            cls = None

        if not inspect.isclass(cls):
            cls = None

        return cls

    def _get_relevant_entries_for_cls_from_dict(self, config_dict, cls):
        relevant_entries = []
        for jobclassname in config_dict:
            jobcls = self._pyadf_class_from_string(jobclassname)
            if issubclass(cls, jobcls):
                relevant_entries.append((jobclassname, jobcls))

        # sort list of relevant entries such that parent classes appear before their childs
        relevant_entries.sort(key=lambda e: len(e[1].mro()))

        return relevant_entries

    def _validate_jobclass_dict(self, jobclass_dict):

        pyadf_base_jobclass = self._pyadf_class_from_string('job')

        for jobclass in list(jobclass_dict.keys()):
            cls = self._pyadf_class_from_string(jobclass)

            if (cls is None) or not (issubclass(cls, pyadf_base_jobclass)):
                print(f'Error: {jobclass} is not a valid job (base) class')
                return False

        return True

    def get_env_modules_for_class(self, cls):
        """
        Return a list with the environment module names that should be loaded
        for a given job class.
        """
        relevant_entries = self._get_relevant_entries_for_cls_from_dict(self.env_modules_load, cls)

        if len(relevant_entries) == 0:
            mods = []
        else:
            mods = self.env_modules_load[relevant_entries[-1][0]]
            if not isinstance(mods, list):
                mods = [mods]

        return mods

    def get_env_modules_for_job(self, job):
        """
        Return a list with the environment module names that should be loaded
        for a given job object.
        """
        return self.get_env_modules_for_class(job.__class__)

    @staticmethod
    def _env_output_to_environ_dict(env_output):
        environ_dict = {}
        for line in env_output.splitlines():
            if line:
                e = line.strip().split('=', 1)
                if len(e) == 2:
                    environ_dict[e[0]] = e[1]
        return environ_dict

    def get_environ_for_class(self, cls):
        """
        Return a dictionary of the environment variables for a given job class.
        """
        from subprocess import Popen, PIPE

        env_modules = self.get_env_modules_for_class(cls)

        if len(env_modules) > 0:
            module_cmd = ''
            for mod in env_modules:
                module_cmd += f'module load {mod}; '

            DEVNULL = open(os.devnull, 'wb')
            env_output_new = Popen(module_cmd+'env', shell=True, executable=self.default_shell,
                                   stdout=PIPE, stderr=DEVNULL).communicate()[0].decode('utf-8')
            environ = self._env_output_to_environ_dict(env_output_new)
        else:
            environ = os.environ
        return environ

    def get_environ_for_job(self, job):
        """
        Return a dictionary of the environment variables for a given job.
        """
        return self.get_environ_for_class(job.__class__)

    def get_parallel_conf_for_class(self, cls):
        """
        Return dictionary with parallel configuration for a given job class.
        """

        relevant_entries = self._get_relevant_entries_for_cls_from_dict(self.parallel_config, cls)
        return self.parallel_config[relevant_entries[-1][0]]

    def get_parallel_conf_for_job(self, job):
        """
        Return dictionary with parallel configuration for a given job.
        """
        return self.get_parallel_conf_for_class(job.__class__)

    def get_nproc_for_class(self, cls):
        """
        Return the number of parallel processes for a given job class.
        """
        nproc = self.default_nproc

        pconf = self.get_parallel_conf_for_class(cls)
        if 'nproc' in pconf:
            nproc = pconf['nproc']
        if not pconf.get('parallel', True):
            nproc = 1

        return nproc

    def get_nproc_for_job(self, job):
        """
        Return the number of parallel processes for a given job.
        """
        return self.get_nproc_for_class(job.__class__)


import random

class LotoCard:
    """
    Класс для генерации чисел лото-карточки
    """
    def __init__(self):
        self.card_lst = []
        self.card_player = str
        self.card_computer = str

    def create_card(self):
        # Создание общего списка с элементами игровых карточек player[1] и computer[0]
        self.card_lst = [[random.sample([
            item for elem in [('  ', num)
                              for _, num in zip(range(5), random.sample(list(range(91)), 5))]
            for item in elem], 10) for _ in range(3)] for _ in range(2)]
        [self.card_lst[j].insert(i * 2, '\n') for j in range(len(self.card_lst)) for i in range(0, 3)]

class Computer(LotoCard):
    """
    Создание индивидуальной карточки компьютера
    """
    def __init__(self):
        super().__init__()
        LotoCard.create_card(self)
        self.card_lst_computer = [i for e in self.card_lst[0] for i in e]

    def create_card_computer(self):
        # Исключение повторяющихся элементов и добавление спец. символов для создания интерфейса карточки.
        for i in self.card_lst_computer:
            if self.card_lst_computer.count(i) > 1 and i not in ['  ', '\n']:
                self.card_lst_computer[self.card_lst_computer.index(i)] = random.randint(1, 91)
        self.card_lst_computer.insert(0, ('-' * 6 + 'Карточка компьютера' + '-' * 6))
        self.card_lst_computer.insert(34, '\n' + ('-' * 31))
        self.card_computer = ' '.join(list(map(str, self.card_lst_computer)))
        return self.card_computer

class Player(Computer):
    """
        Создание индивидуальной карточки игрока
    """
    def __init__(self):
        super().__init__()
        LotoCard.create_card(self)
        self.card_lst_gamer = [i for e in self.card_lst[0] for i in e]

    def create_card_player(self):
        # Исключение повторяющихся элементов и добавление спец. символов для создания интерфейса карточки.
        for i in self.card_lst_gamer:
            if self.card_lst_gamer.count(i) > 1 and i not in ['  ', '\n']:
                self.card_lst_gamer[self.card_lst_gamer.index(i)] = random.randrange(1, 91)
        for i in self.card_lst_gamer:
            if i in self.card_lst_computer and i not in ['  ', '\n']:
                self.card_lst_gamer[self.card_lst_gamer.index(i)] = random.randrange(1, 91)
        self.card_lst_gamer.insert(0, ('-' * 9 + 'Ваша карточка' + '-' * 9))
        self.card_lst_gamer.insert(34, '\n' + ('-' * 31))
        self.card_player = ' '.join(list(map(str, self.card_lst_gamer)))
        return self.card_player

class LottoGame(Player):
    """Игра"""
    @staticmethod
    def barrel_choice():
        # Функция-имитация случайного выбора бочонка
        bag = list(range(1, 91))
        random.shuffle(bag)
        for barrel in bag:
            yield barrel

    def game(self):
        # Создание карточек игроков.
        Computer.create_card_computer(self)
        Player.create_card_player(self)
        for num in LottoGame.barrel_choice():
            print(self.card_player)
            print(self.card_computer)
            anw = input(f'Номер бочонка: {num}\nВычеркиваем из карточки?\ny/n\n')
            if anw == 'y':
                if num in self.card_lst_gamer:
                    # Реализация вычеркивания номера бочонка из карточки
                    self.card_lst_gamer[self.card_lst_gamer.index(num)] = ' -'
                    self.card_player = ' '.join(list(map(str, self.card_lst_gamer)))
                    # Проверка есть ли еще числа в карточке игрока.
                    count_num = 0
                    for elem in self.card_player:
                        if elem.isdigit():
                            count_num += 1
                    if count_num == 0:
                        print('Вы выиграли!')
                        break
                else:
                    print('Вы проиграли!\nКомпьютер победил!')
                    break
            else:
                # Реализация хода компьютера
                if num in self.card_lst_computer:
                    self.card_lst_computer[self.card_lst_computer.index(num)] = ' -'
                    self.card_computer = ' '.join(list(map(str, self.card_lst_computer)))
                    # Проверка есть ли еще числа в карточке.
                    count_num = 0
                    for elem in self.card_computer:
                        if elem.isdigit():
                            count_num += 1
                    if count_num == 0:
                        print('Вы проиграли!\nКомпьютер победил!')
                        break
                if num in self.card_lst_gamer:
                    print('Вы проиграли!\nКомпьютер победил!')
                    break
                else:
                    pass

game = LottoGame()
game.game()


__author__ = "jacobvanthoog"

import math
import wpilib
import sys
import JoystickLib.joystickLib
from HolonomicDrive.HolonomicDrive import HolonomicDrive
from Shooter import ShootController
from JoystickLib.Gamepad import Gamepad
import Vision
import networktables
from networktables import NetworkTable
from TheNewArm import NewArm
from Lifter import Lifter
#import globalListener

NetworkTable.setServerMode()

num_array = networktables.NumberArray()

# noinspection PyInterpreter,PyInterpreter
class MainRobot (wpilib.IterativeRobot):
    def robotInit(self):

        #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        self.BELT_BROKEN = False
        #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        self.Lift = Lifter.Lifter()
        self.Vision = Vision.Vision()
        self.usinggamepad = True
        self.FL = wpilib.CANTalon(2)
        self.FR = wpilib.CANTalon(1)
        self.BL = wpilib.CANTalon(0)
        self.BR = wpilib.CANTalon(3)
        self.FL.setFeedbackDevice(wpilib.CANTalon.FeedbackDevice.QuadEncoder)
        self.FR.setFeedbackDevice(wpilib.CANTalon.FeedbackDevice.QuadEncoder)
        self.BL.setFeedbackDevice(wpilib.CANTalon.FeedbackDevice.QuadEncoder)
        self.BR.setFeedbackDevice(wpilib.CANTalon.FeedbackDevice.QuadEncoder)
        self.FL.setPID(1.0, 0.0, 3.0, 0.0)
        self.FR.setPID(1.0, 0.0, 3.0, 0.0)
        self.BL.setPID(1.0, 0.0, 3.0, 0.0)
        self.BR.setPID(1.0, 0.0, 3.0, 0.0)

        self.movegamepad = JoystickLib.Gamepad.Gamepad(port = 0)
        self.shootgamepad = JoystickLib.Gamepad.Gamepad(port = 1)

        self.MoveJoy = JoystickLib.joystickLib.createJoystick(0)
        self.MoveJoy.invertY()
        self.TurnJoy = JoystickLib.joystickLib.createJoystick(1)
        self.TurnJoy.invertY()

        self.Drive = HolonomicDrive(self.FL, self.FR, self.BL, self.BR)
        self.Drive.invertDrive(True)
        # TODO: move magic number to constant
        self.Drive.setWheelOffset(math.radians(27)) #angle of wheels
        self.Drive.setDriveMode(HolonomicDrive.DriveMode.JEFF)

        self.LeftFly = wpilib.CANTalon(4)
        self.RightFly = wpilib.CANTalon(5)
        self.LimitSwitch = wpilib.DigitalInput(0)
        self.LimitSwitch2 = wpilib.DigitalInput(1)
        self.Intake = wpilib.CANTalon(8)
        self.Shooter = ShootController.ShootController(\
            self.LeftFly, self.RightFly,\
            self.Intake, self.LimitSwitch, self.LimitSwitch2)
        #self.Shooter.invertFlywheels()
        self.armcan = wpilib.CANTalon(7)
        self.armcan.changeControlMode(wpilib.CANTalon.ControlMode.Voltage)
        self.armcan.set(0)

    def autonomousInit(self):
        self.shoot = False
        self.rev = 0
        self.time = 0
        self.shoot = False
        self.turn = 0
        self.Drive.zeroEncoderTargets()
        self.needed = 0
        self.start = True
        self.FR.changeControlMode(wpilib.CANTalon.ControlMode.Speed)
        self.FL.changeControlMode(wpilib.CANTalon.ControlMode.Speed)
        self.BL.changeControlMode(wpilib.CANTalon.ControlMode.Speed)
        self.BR.changeControlMode(wpilib.CANTalon.ControlMode.Speed)
        self.turn = 0
        self.need = 0
        self.armcan.changeControlMode(wpilib.CANTalon.ControlMode.Voltage)
        self.armcan.set(0)

    def autonomousPeriodic(self):
        if not self.BELT_BROKEN:
            self.time += 1
            if self.time < 250:
                self.FL.set(8.5 * 819.2 / 8)
                self.BR.set(-9 * 819.2 / 8)
                self.FR.set(-9 * 819.2 / 8)
                self.BL.set(8.5 * 819.2 / 8)
            elif (self.time > 300 and self.time < 310):
                self.FL.set(0)
                self.BR.set(0)
                self.FR.set(0)
                self.BL.set(0)
            if self.time > 310:
                if not self.Vision.centerX().__len__() == 0:
                    self.need = abs(self.Vision.centerX()[0] - 240)
                    print (self.need)
                    if self.need > 50:
                        self.turn = self.need * .0005
                    else:
                        self.turn = self.need * .0002
                    if abs(self.Vision.centerX()[0] - 240) < 10:
                        self.shoot = True
                    elif self.Vision.centerX()[0] - 240 > 0:
                        self.Drive.drive(0, 0, -self.turn)
                        self.shoot = False
                    elif self.Vision.centerX()[0] - 240 < 0:
                        self.Drive.drive(0, 0, self.turn)
                        self.shoot = False
                    else:
                        self.shoot = False
                if self.time > 260 and self.time < 320:
                    pass #used to have arm
                if self.time > 320:
                    pass
                else:
                    self.shoot = False
                # if self.shoot == True:
                #     self.rev += 1
                #     if self.rev < 100:
                #         self.Shooter.update(False, True, False, False)
                #     else:
                #         self.Shooter.update(False, True, True, False)
        else:
            #self.Drive.driveSpeedJeffMode(.5,math.pi/2,0)
            pass
    # def autonomousPeriodic(self):
    #
    #     self.time += 1
    #     if self.time < 350:
    #         self.start = True
    #     if self.start == True:
    #         self.Drive.drive(1, math.pi/2, 0)
    #     if self.time > 350:
    #         self.start = False
    #         print ("time is at 350")
    #         # self.Drive.drive(0, 0, 0)
    #         if not self.Vision.centerX().__len__() == 0:
    #             print("passed")
    #             self.needed = abs(self.Vision.centerX()[0] - 235)
    #             print (self.needed)
    #             if self.needed > 50:
    #                 self.turn = self.needed * .009
    #             else:
    #                 self.turn = self.needed * .0008
    #
    #             if abs(self.Vision.centerX()[0] - 235) < 10:
    #                 self.shoot = True
    #                 print ("Alligned")
    #             elif self.Vision.centerX()[0] - 235 > 0:
    #                 self.shoot = False
    #             elif self.Vision.centerX()[0] - 235 < 0:
    #                 self.shoot = False
    #             else:
    #                 self.shoot = False
    #         else:
    #             self.shoot = False
    #         if self.shoot == True:
    #             if self.time < 600:
    #                 print ("Rev wheels")
    #                 self.Shooter.update(False, True, False, False)
    #             elif self.time < 750:
    #                 print ("shooting")
    #                 self.Shooter.update(False, True, True, False)
    #             else:
    #                 self.Shooter.update(False, False, False, False)
    #     self.Drive.drive(0, 0, self.turn)
    def teleopInit(self):
        self.Drive.zeroEncoderTargets()
        self.readyToShoot = False
        self.Arm = NewArm(self.shootgamepad, self.armcan)

    def teleopPeriodic(self):
        averageFlySpeed = (abs(self.LeftFly.getEncVelocity()) + abs(self.RightFly.getEncVelocity()))/2
        # print(str(self.Vision.centerX()))
        print ("FlyWheelSpeed is : " + str(averageFlySpeed))

        if self.usinggamepad == False: # using joystick
            self.TurnJoy.updateButtons();
            self.MoveJoy.updateButtons();
            turn = -self.TurnJoy.getX()
            magnitude = self.MoveJoy.getMagnitude()
            direction = self.MoveJoy.getDirection()
            self.Drive.drive(magnitude, direction, turn) # jeff mode
            self.Shooter.update(self.MoveJoy.getRawButton(2),\
                            self.MoveJoy.getRawButton(3),\
                            self.MoveJoy.getTrigger(),\
                            self.MoveJoy.getRawButton(5))

        else: # using gamepad
            if not self.Vision.centerX().__len__() == 0:
                self.needed = abs(self.Vision.centerX()[0] - 240)
                print (self.needed)
                if self.needed > 50:
                    self.turnSpeed = self.needed * .009
                else:
                    self.turnSpeed = self.needed * .0008
                if self.shootgamepad.getButtonByLetter("RB"):
                    # TODO: move 240 to center pixel constant
                    if abs(self.Vision.centerX()[0] - 240) < 15:
                        self.readyToShoot = True
                    elif self.Vision.centerX()[0] - 240 > 0:
                        self.Drive.drive(0, 0, -self.turnSpeed)
                        self.readyToShoot = False
                    elif self.Vision.centerX()[0] - 240 < 0:
                        self.Drive.drive(0, 0, self.turnSpeed)
                        self.readyToShoot = False
                    else:
                        self.readyToShoot = False
            else:
                self.readyToShoot = False

            # if self.readyToShoot:
            #     if averageFlySpeed < 1900:
            #         self.LeftFly.set(2000)
            #         self.RightFly.set(-2000)
            #     elif averageFlySpeed > 1900:
            #         self.LeftFly.set(2000)
            #         self.RightFly.set(-2000)
            #         self.Intake.set(1)
            #     else:
            #         self.Shooter.update(False, False, False, False)
            if self.movegamepad.getButtonByLetter("LJ"): # faster button
                self.slowed = 1
            elif self.movegamepad.getButtonByLetter("LB"): # slower button
                self.slowed = .2
            else: # no button pressed
                # TODO: move to constant
                self.slowed = .55
            # print ("Slowed: " + str(self.slowed))
            # switch drive mode with gamepad
            if   self.movegamepad.getRawButton(Gamepad.A):
                self.Drive.setDriveMode(HolonomicDrive.DriveMode.VOLTAGE)
            elif self.movegamepad.getRawButton(Gamepad.B):
                self.Drive.setDriveMode(HolonomicDrive.DriveMode.SPEED)
            elif self.movegamepad.getRawButton(Gamepad.X):
                self.Drive.setDriveMode(HolonomicDrive.DriveMode.JEFF)
            # print(str(self.Drive.getDriveMode()))

            # TODO: refactor duplicate code
            if self.movegamepad.getButtonByLetter("RB"):
                turn = -self.movegamepad.getRX() * abs(self.movegamepad.getRX()) * (self.slowed / 2)
                #magnitude = self.movegamepad.getLMagnitude() * self.slowed
                magnitude = self.movegamepad.getLMagnitudePower(2) * self.slowed
                direction = self.movegamepad.getLDirection() + math.pi
            else:
                turn = -self.movegamepad.getRX() * abs(self.movegamepad.getRX()) * (self.slowed / 2)
                #magnitude = self.movegamepad.getLMagnitude() * self.slowed
                magnitude = self.movegamepad.getLMagnitudePower(2) * self.slowed
                direction = self.movegamepad.getLDirection()
            self.Drive.drive(magnitude, direction, turn)
            self.Shooter.update(self.shootgamepad.getButtonByLetter("B"),\
                                self.shootgamepad.getButtonByLetter("X"),\
                                self.shootgamepad.getButtonByLetter("A"),\
                                self.shootgamepad.getButtonByLetter("Y"))
            # if self.shootgamepad.getButtonByLetter("RB"):
            #     self.Lift.liftUp()
            # elif self.shootgamepad.getButtonByLetter("LB"):
            #     self.Lift.pullUp()
            # else:
            #     self.Lift.stop()

            self.Arm.update()

            # else:
            #     self.Shooter.update(self.shootgamepad.getButtonByLetter("B"), self.shootgamepad.getButtonByLetter("X"),
            #                                 False, self.shootgamepad.getButtonByLetter("LB"))

            # print ("Slowed:" + str(self.slowed))
            # print ("FL: " + str(self.FL.getEncVelocity()))
        #self.Logger.printCurrents()
        #print("turn: " + str(turn)\
        #    + " mag: " + str(magnitude)\
        #    + " dir: " + str(direction))

if __name__ == "__main__":
    wpilib.run(MainRobot)


# Jimi Spier
# currency_converter.py
# 20220914

# ----------------------Import------------------------------------------ #
import lib_currency_converter as lcc

# ----------------------Program-Instructions---------------------------- #
print("\nYour choices are:\n")

for key in lcc.country_codes:
    print(f"{key} --> {lcc.country_codes[key]}")
print()

# ----------------------User-Data-Collect------------------------------- #
user_choice_currency = input("Convert US Dollar to what currency: ")
user_money_exchange = input("How much would you like to convert: ")
# ----------------------Processing-------------------------------------- #
user_money_exchange = float(user_money_exchange)

grand_total = lcc.is_exchange_math(user_choice_currency, user_money_exchange)

# ----------------------Final-Result------------------------------------ #

print(f"${user_money_exchange} converted is: {grand_total} {lcc.country_codes[user_choice_currency]}\n")


class Solution:
    def isIsomorphic(self, s: str, t: str) -> bool:
        map ={}
        if len(s) != len(t):
            return False
        for i in range(len(s)):
            if t[i] in map.values() and s[i] not in map.keys():
                return False
            if s[i] in map:
                if map[s[i]] != t[i]:
                    return False
            else:
                map[s[i]] = t[i]
        return True


import math
import numpy as np
import matplotlib.pyplot as plt
import random as rd
import copy
import sqlalchemy
import pandas as pd
from sqlalchemy import create_engine

class RSRSP:
    def __init__(self, DP_flag, rc=0.5, alpha1=0.1, alpha2=0.01):
        self.DP_flag = DP_flag
        self.rc = rc
        self.alpha1 = alpha1
        self.alpha2 = alpha2
        self.engine = create_engine("mysql+pymysql://root:3751ueoxjwgixjw3913@39.98.41.126:3306/qg_final")
        self.location_address = 'RSRSP_10_1000_location'  # the name is RSRSP_x_xxx_location
        self.adjacency_address = 'RSRSP_10_1000_adjacency'  # the name is RSRSP_x_xxx_adjacency
        self.npy_path = r'C:\Users\86177\Desktop\QG_Summer_Camp\Last Assessment\Algorithm\数据集\Agents-1000\010log_neat_radiation_1000\010log_neat_radiation_1000.npy'  # the address of the .npy used to create the topology matrix
        self.file = ""
        self.G_original = None
        self.G = None

    def G_create(self):
        """
        Create the topology
        """
        self.G_original = np.load(self.npy_path, allow_pickle=True)

    def G_merge(self, A_cnt):
        """
        merge the topology matrix
        :param A_cnt: the adjacency matrix with 0.05 * rc, DS: array
        :return: a more simple matrix , DS: array
        """
        self.G = [[], []]
        delete = []
        for index1 in range(len(A_cnt)):
            if index1 not in delete:
                temp = [index1]
                delete.append(index1)
                for index2 in range(len(A_cnt)):
                    if index2 not in delete and A_cnt[index1][index2] == 1:
                        temp.append(index2)
                        delete.append(index2)
                self.G[0].append(temp)
        for element in self.G[0]:
            self.G[1].append(self.G_original[element[0]])
        self.G[1] = np.array(self.G[1])

    def norm2(self, a):  # 2范数
        """
        Calculate the 2-norm
        :param a: A vector, DS:array
        :return: the 2-norm of the vector, DS:float/double
        """
        return np.linalg.norm(a)

    def rad(self, a):  # 弧度
        """
        Calculate the radian between the vector and x_axis
        :param a: A vector, DS:array
        :return: the radian between the vector and x_axis, DS:float/double
        """
        if RSRSP.norm2(self, a) == 0:  # 重合
            return 0
        else:
            if a[1] >= 0:  # 纵坐标大于0
                return np.arccos(np.dot(a, np.array([1, 0])) / (RSRSP.norm2(self, a)))
            else:
                return 2 * np.pi - np.arccos(np.dot(a, np.array([1, 0])) / (RSRSP.norm2(self, a)))

    def dst(self, a, b):  # 计算另一直角边长度
        """
        Use the pythagorean theorem to calculate the square edge
        :param a:The hypotenuse, DS: float/double
        :param b:The square, DS: float/double
        :return:Another square, DS: float/double
        """
        return math.fabs(a ** 2 - b ** 2) ** 0.5

    def sim_agent(self, num, N_i):  # 选取最远的智能体
        """
        Choose the nearest agent in num_th agent's neighbor sets
        :param num:The index of the agent, DS: int
        :param N_i:one of the num_th agent's neighbor sets, DS: list
        :return:the farthest neighbor's index, DS: int
        """
        min_ = num  # 若无则返回自身
        temp = self.rc
        for n in N_i:
            if RSRSP.norm2(self, self.G[1][num] - self.G[1][n]) <= temp:
                min_ = n
                temp = RSRSP.norm2(self, self.G[1][num] - self.G[1][n])
        return min_

    def D(self, A):  # 求度矩阵
        """
        Create the degree matrix according to adjacency matrix
        :param A:topology's adjacency matrix, DS:array
        :return:the degree matrix, DS: array
        """
        result = np.zeros(np.shape(A))
        for i in range(len(A)):
            result[i][i] = np.sum(A[i])
        return result

    def A(self, G, r):  # 根据通信距离构建邻接矩阵
        """
        Create the adjacency matrix according to topology and the communication distance
        :param G: the topology matrix
        :param r:the communication distance, DS:float/double
        :return:the adjacency matrix, DS: array
        """
        result = np.zeros((np.shape(G)[0], np.shape(G)[0]))
        for i in range(len(G)):
            for j in range(len(G)):
                if ((G[i][0] - G[j][0]) ** 2 + (G[i][1] - G[j][1]) ** 2) <= r ** 2 \
                        and i != j:
                    result[i][j] = 1
        return np.array(result)

    def TPfig(self, A, start, end):  # 拓扑绘画
        """
        draw  the figure of the topology
        :param A:the adjacency of the topology, DS:array
        :param start:the start point of the axis, DS:int
        :param end:the end point of the axis, DS:int
        """
        plt.figure(figsize=(5, 5))
        plt.xlim(start, end)
        plt.ylim(start, end)
        for i in range(len(self.G[1])):
            for j in range(len(self.G[1])):
                if A[i][j] == 1:
                    Gpic = [[self.G[1][i][0], self.G[1][j][0]], [self.G[1][i][1], self.G[1][j][1]]]
                    plt.plot(Gpic[0], Gpic[1], c='k', linewidth=0.1, zorder=1)
        plt.scatter(self.G[1].T[0], self.G[1].T[1], c='r', zorder=2)
        # plt.savefig('time_{}.jpg'.format(time))
        plt.show()

    def MAS(self, A, alpha):  # 一致性算法
        """
        The consensus algorithm
        :param alpha: the parameter that controls the rate of convergence, DS: array
        :param A:the adjacency, DS: array
        :return: the location of the topology at next time, DS : array
        """
        return np.dot(alpha * (A - RSRSP.D(self, A)) + np.eye(np.shape(A)[0]), self.G[1])

    def RSRSP(self, A_trad):  # 基于RSRSP的邻接矩阵
        """
        Create the adjacency matrix according to the RSRSP algorithm
        :param A_trad:the adjacency matrix with rc, DS : array
        :return: the RSRSP adjacency matrix, DS : array
        """
        result = np.zeros((np.shape(self.G[1])[0], np.shape(self.G[1])[0]))
        beta = [n * 0.5 * np.pi / 360 for n in range(0, 360, 8)]
        for num in range(np.shape(self.G[1])[0]):
            N_num = []  # 存放分扇区后智能体标号
            var_beta = []
            I = [index for index in range(len(A_trad[num])) if A_trad[num][index] == 1]
            for b in beta:
                temp = {'N_1': [], 'N_2': [], 'N_3': [], 'N_4': []}
                for index in I:
                    rad_temp = RSRSP.rad(self, self.G[1][index] - self.G[1][num]) - b
                    if np.pi / 2 * 0 <= rad_temp < np.pi / 2 * 1:
                        temp['N_1'].append(index)
                    if np.pi / 2 * 1 <= rad_temp < np.pi / 2 * 2:
                        temp['N_2'].append(index)
                    if np.pi / 2 * 2 <= rad_temp < np.pi / 2 * 3:
                        temp['N_3'].append(index)
                    if np.pi / 2 * 3 <= rad_temp < np.pi / 2 * 4 \
                            or np.pi / 2 * (-1) <= rad_temp < np.pi / 2 * 0:  # 注意第一象限减去beta后存在负数的情况
                        temp['N_4'].append(index)
                N_num.append(temp)
                var_beta.append(np.var([len(N_num[-1]['N_1']), len(N_num[-1]['N_2']),
                                        len(N_num[-1]['N_3']), len(N_num[-1]['N_4'])]))
            min_index = var_beta.index(min(var_beta))
            cooperation = [RSRSP.sim_agent(self, num, N_num[min_index]['N_1']),
                           RSRSP.sim_agent(self, num, N_num[min_index]['N_2']),
                           RSRSP.sim_agent(self, num, N_num[min_index]['N_3']),
                           RSRSP.sim_agent(self, num, N_num[min_index]['N_4'])]  # 请求合作
            for index in cooperation:
                if index != num:
                    result[num][index] = 1
                    result[index][num] = 1
        return result

    def component(self, A):  # 生成连通分支
        """
        find all connected components in the topology
        :param A:the adjacency matrix, DS:array
        :return:the list of connected components, DS: list (of lists)
        """
        A_sp = A + np.eye(np.shape(A)[0])
        result = []
        delete = copy.deepcopy(set([]))
        for row in range(len(A_sp)):
            if row not in delete:
                temp = [num for num in range(len(A_sp)) if A_sp[row][num] == 1]
                result.append(copy.deepcopy(set(temp)))
                delete.add(row)
                for index in range(len(A_sp)):
                    if index not in delete:
                        temp = [num for num in range(len(A_sp)) if A_sp[index][num] == 1]
                        if not result[-1].isdisjoint(set(temp)):
                            result[-1] = result[-1] | set(temp)
                            delete.add(index)
        end_result = []  # 再次清洗
        for element in result:
            if len(end_result) == 0:
                end_result.append(element)
            else:
                for index in range(len(end_result)):
                    if not end_result[index].isdisjoint(element):
                        end_result[index] = end_result[index] | element
                        break
                    elif index == len(end_result) - 1:
                        end_result.append(element)
        return end_result

    def lct_list(self, location_list, time):
        """
        append the data in the list which will be written into xxx_x_xxx_location
        :param location_list:Used to save the data of the topology, DS:list (of dict)
        :param time: The number of iterations, DS: int
        """
        id = time * (len(self.G_original) + 1)
        for element in self.G_original:
            temp = {'id': id, 'x': element[0], 'y': element[1]}
            location_list.append(temp)
            id += 1
        location_list.append({'id': id, 'x': None, 'y': None})

    def ajc_list(self, A, adjacency_list, time):
        """
        append the data in the list which will be written into xxx_x_xxx_adjacency
        :param A: the adjacency matrix, DS: array
        :param adjacency_list: Used to save the adjacency matrix of the topology, DS:list (of dict)
        :param time: The number of iterations, DS: int
        """
        id = time * (len(A) + 1)
        for element in A:
            index_temp = [index for index in range(len(element)) if element[index] == 1]
            temp = {'id': id, 'ad_index': str(index_temp)[1: -1]}
            adjacency_list.append(temp)
            id += 1
        adjacency_list.append({'id': id, 'ad_index': None})

    def file_write(self, location_list, adjacency_list):
        """
        write the data into xxx_x_xxx_adjacency
        :param location_list: Used to save the location matrix of the topology, DS:list (of dict)
        :param adjacency_list:Used to save the adjacency matrix of the topology, DS:list (of dict)
        """
        path = r"RSRSP_Data"

        lct_csv_name = self.location_address + '.csv'
        location = pd.DataFrame(location_list)
        location.to_csv(path + '/' + lct_csv_name, encoding='utf-8')

        ajc_csv_name = self.adjacency_address + '.csv'
        adjacency = pd.DataFrame(adjacency_list)
        adjacency.to_csv(path + '/' + ajc_csv_name, encoding='utf-8')

        conn = self.engine.connect()

        location.to_sql(name=self.location_address, con=conn, if_exists='replace', index=False)
        print("成功写入" + self.location_address)

        adjacency.to_sql(name=self.adjacency_address, con=conn, if_exists='replace', index=False)
        print("成功写入" + self.adjacency_address)

    def main(self, num=3):
        """
        The main function
        :param num: Choose the algorithm, DS: int
        :return: the successful signal, DS: string
        """
        location_list = []
        adjacency_list = []
        time = 0
        RSRSP.G_create(self)
        while 1:
            RSRSP.G_merge(self, RSRSP.A(self, self.G_original, 0.05 * self.rc))  # 收敛判定精度
            test1 = self.G
            RSRSP.G_merge(self, RSRSP.A(self, self.G_original, self.rc))  # 通信距离
            test2 = self.G
            if len(test1[0]) == len(test2[0]):
                RSRSP.lct_list(self, location_list, time)
                A_rc = RSRSP.A(self, self.G_original, self.rc)
                RSRSP.ajc_list(self, A_rc, adjacency_list, time)
                print(self.location_address[: -9] + "聚类完毕")
                break
            A_cnt = RSRSP.A(self, self.G_original, 0.000001 * self.rc)  # 去重精度
            A_rc = RSRSP.A(self, self.G_original, self.rc)
            RSRSP.G_merge(self, A_cnt)
            A_trad = RSRSP.A(self, self.G[1], self.rc)
            if time % 150 == 0 and time != 0:
                RSRSP.TPfig(self, A_trad, start=-6, end=6)
            RSRSP.lct_list(self, location_list, time)
            RSRSP.ajc_list(self, A_rc, adjacency_list, time)

            if num == 1:  # 使用SAN算法
                self.G[1] = RSRSP.MAS(self, A_trad, self.alpha2)

            elif num == 2:  # 使用RSRSP算法
                A_rsrsp = RSRSP.RSRSP(self, A_trad)
                self.G[1] = RSRSP.MAS(self, A_rsrsp, self.alpha1)

            elif num == 3:  # 改良算法
                if len(RSRSP.component(self, A_rc)) == len(test2[0]):  # SAN算法
                    print('SAN')
                    self.G[1] = RSRSP.MAS(self, A_trad, self.alpha2)

                else:  # RSRSP算法
                    print('RSRSP')
                    A_rsrsp = RSRSP.RSRSP(self, A_trad)
                    self.G[1] = RSRSP.MAS(self, A_rsrsp, self.alpha1)

            for index in range(len(self.G[0])):
                for element in self.G[0][index]:
                    self.G_original[element] = self.G[1][index]
            print(time)
            time += 1
        RSRSP.file_write(self, location_list, adjacency_list)
        return "SUCCESS"

rsrsp = RSRSP(False)
rsrsp.main()
exit(1)


import os

def check_file_extension(files):
    for f in files:
        extension = os.path.splitext(f.name)[1]
        valid_extensions = [".jpg", ".png", ".jpeg", ".gif", ".tiff", ".docs", ".txt", ".pdf", ".html", ".rtf", "docx", ".pptx", ".ppt"]
        if extension.lower() in valid_extensions:
            return True
        return False


# Write a program to accept a number 'n', and display the following pattern
# 1
# 2 3
# 4 5 6
num=int(input("enter range :"))
A=1
for i in range(0,num):
  for j in range(0,i):
    print(A,end=" ")
    A=A+1
  print("")


from PyQt5.QtWidgets import QWidget, QDialog
from PyQt5.QtWidgets import QPushButton, QApplication
from PyQt5.QtGui import QImage, QPalette, QBrush
from PyQt5.QtCore import QSize
import button_config
import button_treatment
import invent
import win_window
import lost_window
import game_info
import make_history
import final_window
import os

SCREEN_SIZE = [700, 700]

class Engine(QDialog, QWidget):
    def __init__(self):
        super().__init__()
        self.setModal(True)
        self.initUI()

    def initUI(self):
        game_info.info.clear_files()

        self.setWindowTitle('Undertale')
        self.setFixedSize(*SCREEN_SIZE)

        self.button_lst = list()

        self.button_lst.append(QPushButton(self))
        self.button_lst[0].clicked.connect(lambda: self.pushed_button('button_1.txt'))

        self.button_lst.append(QPushButton(self))
        self.button_lst[1].clicked.connect(lambda: self.pushed_button('button_2.txt'))

        self.button_lst.append(QPushButton(self))
        self.button_lst[2].clicked.connect(lambda: self.pushed_button('button_3.txt'))

        self.button_lst.append(QPushButton(self))
        self.button_lst[3].clicked.connect(lambda: self.pushed_button('button_4.txt'))

        self.button_lst.append(QPushButton(self))
        self.button_lst[4].clicked.connect(lambda: self.pushed_button('button_5.txt'))

        self.button_invent = QPushButton('ИНВЕНТАРЬ', self)
        self.button_invent.clicked.connect(self.invent_show)

        self.make_room(f'{game_info.info.level}/{game_info.info.file}/background.png')

    def make_room(self, text):
        self.make_background(text)
        self.make_buttons()
        self.make_room_history()

    def make_buttons(self):
        button_config.button_config(self)
        self.show()

    def make_background(self, file_name):
        oImage = QImage(file_name)
        sImage = oImage.scaled(QSize(*SCREEN_SIZE))
        palette = QPalette()
        palette.setBrush(QPalette.Window, QBrush(sImage))
        self.setPalette(palette)

    def make_room_history(self):
        if not game_info.info.get_used_history() and game_info.info.get_history() != 'This room has no history':
            self.make_history = make_history.HistoryWindow()
            self.make_history.show()

    def pushed_button(self, button_name):
        text, command, new_file = game_info.info.get_button_info(button_name)
        code = button_treatment.button_treatment(self, command, button_name)
        game_info.info.put_file(new_file)
        if code == 1:
            self.new_room()
        elif code == 2:
            self.do_win_window()
        elif code == 3:
            self.do_lost_window()
        elif code == 4:
            self.do_game_end_wind()

    def new_room(self):
        self.update()
        self.make_room(os.path.join(fr'{game_info.info.level}/{game_info.info.file}', 'background.png'))
        QApplication.processEvents()

    def invent_show(self):
        self.invent_view = invent.Invent()
        self.invent_view.show()

    def do_win_window(self):
        self.close()
        self.win_window = win_window.WinWindow()
        self.win_window.show()

    def do_lost_window(self):
        self.close()
        self.lost_window = lost_window.LostWindow()
        self.lost_window.show()

    def do_game_end_wind(self):
        self.close()
        self.game_end_win = final_window.FinalWindow()
        self.game_end_win.show()


from django.contrib import admin

from .models import Order

# =======================================================
# Controls the fields to display in admin panel
# =======================================================

class OrderAdmin(admin.ModelAdmin):
    list_display = ('id',
                    'order_id',
                    'book_id',
                    'order_date',
                    'delivery_date',
                    'user_id',
                    'contact_no',
                    'order_status',
                    'item_count',
                    'total_amount',
                    'total_discount',
                    'tax',
                    'net_amount'
                    )

admin.site.register(Order, OrderAdmin)