"Global stuff for Python software."

##  GLOBDEFS.py
##  Python globals, to be imported by almost everything.
##  R.S. Forsyth.
##  First version: 20/11/2006
##  Last revision: 03/04/2012


##  globals :
NL = '\n'
Commasep = ','
Fullstop = '.'
Quotes1 = "'"
Quotes2 = '"'
Tabchar = '\t'
Htab = '\t'
Lets = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Nicechar = Lets + "0123456789" + " ,."
Anyquote = Quotes1+Quotes2+"`"

Gold = 1.61803398875
Goldchar = "1.61803398875"
Goldstr = Goldchar
Huge = 3e303
Tiny = 3e-303

import  string


##  Sack :
class Sack:
    "generic class to hold together associated bits of data."

    def __init__(self,n=None):
        self.id = n


##  Tokstuff :
class Tokstuff:
    "Class to keep tokenization stuff together."
    ##  perhaps defaults should go here?

    def __init__(self,n):
        "Initializer for Tokstuff."
        self.name = n
        self.alphanum = string.ascii_letters + string.digits
        self.andsign = '&'
        self.wordcont = self.alphanum + "'_"  ##  remove underline ?
        self.brackets = "[]{}()"
        self.puncmarx = ",.;:!?/*-$&_@" + '\\'
        ##  should puncmarx include brax ?
        self.numstart = "+-0123456789"
        self.numcont = self.numstart + ".,"
        self.markopen = '<'
        self.markshut = '>'


##  Settings :
class Settings:
    "Class for info on parameter settings."

    def __init__(self,n):
        "Initializer for Settings."
        self.name = n
        ##  default values should perhaps go here :

    def getinfo (self,parafile):
        "Gets parameter info from open file."

        p = 0
        while 1:
            paraline = parafile.readline()
            if not paraline:  break  ##  null line means quit
            paralist = paraline.split()
            if len(paralist) < 2:
                print("Probable error in parameter line : ",paraline)
            else:  ##  looks okay
                p += 1
                parattr = paralist[0]
                paraval = paralist[1]
                if parattr == "pathlist":
                    paraval = paralist[1:]
                setattr(self,parattr,paraval)
                ##  no validity checking done here at present.
        print(p,"parameter settings read from file.")


    def getinfo1 (self,parafile):
        "Gets parameter info from open file, list version."

        p = 0
        while 1:
            paraline = parafile.readline()
            if not paraline:  break  ##  null line means quit
            paralist = string.split(paraline)
            if len(paralist) < 2:
                print("Probable error in parameter line : ",paraline)
            else:  ##  looks okay
                p += 1
                parattr = paralist[0]
                paraval = paralist[1]
                if parattr == "pathlist":
                    paraval = paralist[1:]
                pv = getattr(self,parattr,[])
                pv.append(paraval)
                setattr(self,parattr,pv)
                ##  no validity checking done here at present.
        print(p,"parameter settings read from file.")

        
    def showinfo (self,outfile):
        "Shows parameter settings on o/p file."

        ##  print self.__dict__
        outfile.write("Parameter settings after reading " + self.name + ':' + NL)
        paralist = list(self.__dict__.items())
        paralist.sort()
        for pair in paralist:
            outfile.write(str(pair) + NL)
        pc = len(paralist)
        outfile.write(str(pc) + " parameter values set." + NL + NL)


class Gregcal:
    "class for Gregorian calendar stuff."

    Monthlen = [0,31,28,31,30,31,30,31,31,30,31,30,31]
    Baseyear = 1200  ##  should be positive & divisible by 400
    Minyear = 1582 ; Maxyear = 4902  ##  limits of valid working
    Thisyear = 2010

    def __init__ (self,showdat=0):
        "initializer for Gregcal objects."

        Gregcal.Predays = []
        pd = 0
        for m in range(13):
            pd += Gregcal.Monthlen[m]
            Gregcal.Predays.append(pd)
        if showdat:
            print(Gregcal.Monthlen)
            print(Gregcal.Predays)
            print(Gregcal.Minyear,Gregcal.Maxyear)
            

    def goodyear (self,y):
        "y is a year."

        if type(y) != type(9):
            return  False  ##  must b an integer
        if y > Gregcal.Maxyear or y < Gregcal.Minyear:
            return False
        return  True


    def leapyear (self,y):
        "self is a year."

        if not self.goodyear(y):
            return  None
        if (y % 4 == 0 and y % 100 != 0) or y % 400 == 0:
            return  True
        return  False

    
    def nicedate (self,dt):
        "checks dt as date, tuple or list."

        if len(dt) < 2 or len(dt) > 3:
            return  False
        d = dt[0] ; m = dt[1]
        if len(dt) == 2:
            y = Gregcal.Thisyear
        else:  ## normal case
            y = dt[2]
        if y < 100:
            y += 2000  ##  2-digit years go to 21st century
        if d < 1:
            return  False
        if m < 1 or m > 12:
            return  False
        if y < Gregcal.Minyear or y > Gregcal.Maxyear:
            return  False
        mm = Gregcal.Monthlen[m]
        if self.leapyear(y) and m == 2:
            mm += 1
        if d > mm:
            return  False
        return  True


    def leapdays (self,y):
        "number of leapdays (=Feb29's) from Baseyear up to start of year y."

        if not self.goodyear(y):
            return  0
        yy = y - Gregcal.Baseyear  ##  N.B. Baseyear itself is leap
        c4 = (yy+3) // 4
        c100 = (yy+99) // 100
        c400 = (yy+399) // 400
        return  c4 + c400 - c100

    def gregdays (self,dt):
        "days from Base date to date dt."

        if not (type(dt)==type((9,)) or type(dt)==type([])):
            print("tuple or list expected.")
            return  None
        if not self.nicedate(dt):
            return  0
        d = dt[0] ; m = dt[1] ; y = dt[2]  ##  conventional order
        if y < 100:
            y += 2000  ##  2-digit abbreviation allowed in 21st century
        year = y - Gregcal.Baseyear  ##  relativized
        days = year * 365
        days += Gregcal.Predays[m-1]
        days += d
        ld = self.leapdays(y)
        if self.leapyear(y) and m > 2:
            days += 1

        return  days+ld
		##  days % 7 == 0 implies Friday (?)

    def gregdate (self,gd):
        "goes from days since Baseyear to a tuple(d,m,y)."

        if gd > (Gregcal.Maxyear-Gregcal.Baseyear)*366:
            return (32,12,Gregcal.Maxyear)  ##  too late
        if gd < (Gregcal.Minyear-Gregcal.Baseyear)*365:
            return (0,1,Gregcal.Minyear)  ##  too early
        miny = gd // 366  ##  can't over-estimate year
        dt = (0,0,0)  ##  failure signal
        for y in range(miny,miny+8):
            yy = y + Gregcal.Baseyear
            for m in range(1,13):
                for d in range(1,32):
                    dt = (d,m,yy)
                    if self.nicedate(dt):
                        xd = self.gregdays(dt)
                        if xd == gd:
                            return  dt

        return  dt  ##  should never happen !

    def str2date (self,s):
        "splits date string into 3 numbers."

        ds = s.split('/')
        if len(ds) != 3:
            ds = s.split('-')
        if len(ds) != 3:
            print("date delimiters are / or - only.",s)
            return (0,0,0)
        for j in range(len(ds)):
            try:
                ds[j] = int(ds[j])
            except ValueError as ve:
                ds[j] = 0
                
        return  ds  ##  checking can be done outside
    
