#This verions needs all web files to be downloaded seperatly - see https://git.microIDE.com/setup.py
import usocket, gc, uos,  machine,  network
from time import localtime, time, sleep_ms , ticks_ms
from uio import StringIO
#User functions
def millis():
  return ticks_ms()
def epoch(epoch=0,ow=False):
  #Returns HTML Epoch time, can also be used to set the epoch time
  if epoch == 0:
    return time()+946710000;
  else:
    if ow or (time()+946710000 < epoch):
      tm = localtime(epoch-946710000)
      tm = tm[0:3] + (0,) + tm[3:6] + (0,)
      machine.RTC().datetime(tm)
def exists(f):
    try:
      return uos.stat(f)[0] == 32768
    except:
      return False
def wget(file,ow=False): #Method to pull support files from git.microide.com
  if ow or not exists(file): #Overwrite Protection
    while not network.WLAN(network.STA_IF).isconnected():
      sleep_ms(10) #Wait on Network
    addr = usocket.getaddrinfo('git.microIDE.com', 80)[0][-1]
    s = usocket.socket()
    s.connect(addr)
    s.send(bytes('GET /%s HTTP/1.0\r\nHost: git.microIDE.com\r\n\r\n' % file, 'utf8'))
    data=s.readline()
    while not '200 OK' in data: #Locks up the function - on purpose
      data=s.readline()
    while data != b'\r\n': #Strip the Header
      data=s.readline()
    #Dump the rest into a file
    f=open(file,'w')
    sleep_ms(10)
    while True:
      data = s.recv(4096)
      sleep_ms(10)
      if data:
        f.write(data)
      else:
        break
    f.close()
    s.close()
#Self-Contained Class for Webserver and Operations
class webServer:
  _con_gz  = b'Content-Encoding: gzip\r\nCache-control: max-age=86400\r\n'
  _con_200 = b'\r\n HTTP/1.1 200 OK\r\nConnection: close\r\n'
  _con_302 = b'\r\n HTTP/1.1 302 FOUND\r\nLocation:{}\r\n\r\n'
  _con_404 = b'\r\n HTTP/1.1 404 NOT FOUND\r\nConnection: close\r\nCache-Control: no-cache\r\n\r\n'
  _con_500 = b'\r\n HTTP/1.1 500 FAIL\r\nConnection: close\r\nCache-Control: no-cache\r\n\r\n'
  _reply=b'\r\n{}\r\n'
  _tmp = b'.tmp'
  _port = 80
  _led = 2
  _mem = 0
  _boot=0
  _id = ''.join('{:02X}'.format(xx) for xx in bytearray(machine.unique_id()))
  def tree(self):
    root=uos.listdir('/')
    for x in root:
        if uos.stat(x)[0]!=32768: #File
          for y in uos.listdir(x):
            root.append(x + '/' + y)
    root.sort()
    return root
  def search(self,file):
    #Looks for a filename and returns the full path
    if len (file)<=2:
      return file
    for x in self.tree():
      if x.find(file.decode("utf-8"))>=0:
        return "/" + x
    return file
  def _sendMime(self,f,file):
    if '.js' in file:
      f.write(b'Content-Type: text/javascript\r\n')
    elif '.ico' in file:
      f.write(b'Content-Type: image/x-icon\r\n')
    elif '.html' in file:
      f.write(b'Content-Type: text/html\r\n')
    elif '.css' in file:
      f.write(b'Content-Type: text/css\r\n')    
    elif '.png' in file:
      f.write(b'Content-Type: image/png\r\n')
    elif '.svg' in file:
      f.write(b'Content-Type: image/svg+xml\r\n')
    elif '.gif' in file:
      f.write(b'Content-Type: image/gif\r\n')
    else:
      f.write(b'Content-Type: text/plain\r\n')
  def _sendStatic(self,f,file):
    # THESE ARE HARD-CODED FILES
    #sub_pack.py will insert files here 
  	#<STAICFILEGLAG>
    if file == b'/favicon.svg' or file == b'/favicon.svg.gz' : #Auto Generated gz file - using https://dl.microIDE.com/sub_pack.py
      f.write(self._con_200)
      f.write(self._con_gz)
      self._sendMime(f,file)
      f.write('\r\n')
      f.write(b'\x1f\x8b\x08\x08\xe0\xc6\x1f\x61\x04\x03\x66\x61\x76\x69\x63\x6f\x6e\x2e\x73\x76\x67\x00\x01\x11\x00\xee\xff\x2e\x2f\x73\x72\x63\x2f\x66\x61\x76\x69\x63\x6f\x6e\x2e\x73\x76\x67\x1c\x26\x4f\xe9\x11\x00\x00\x00')
      gc.collect()
      return True; #end
    return False; #let the calling function know that the file was not found
  def _sendFile(self,c_s,file):
    f=open(file,'r')
    c_s.write(self._con_200)
    self._sendMime(c_s,file)
    if '.gz' in file:
     c_s.write(self._con_gz)
    c_s.write(b'\r\n')
    tmp=f.read(1024)
    if '.py' in file and not file in tmp.encode('utf-8'): # File is python and filename is not in headers - adds commented header
      c_s.write(b'# microIDE Filename: {}\r\n'.format(file.decode()))
    while len(tmp)>1:
      gc.collect()
      c_s.write(tmp)
      tmp=f.read(1024)
    f.close()
  def _clearlog(self):
    uos.dupterm(None,0)
    self.ioStr.close()
    gc.collect()
    self.ioStr=StringIO()
    self.ioStr.write("Boot: {} / Cleared: {}\r\n--- Start ---\r\n".format(self._boot,epoch()))
    uos.dupterm(self.ioStr,0)
  def _head(self,c_s):
    h={}
    while True:
      l = c_s.readline()
      if l == b'\r\n':
        break
      i,v=l.split(b': ', 1)
      h[i.strip()]=v.strip()
    if b'Time' in h:
      epoch(int(h[b'Time'][:-3]))
    return h
  def _srvReq(self,s):
    try:
      c_s,client_addr = s.accept()
      cmd = c_s.readline()
#RST Method
      if "RST" in cmd:
      	c_s.write(self._con_200)
        c_s.write('\r\n\r\n\r\n')
        c_s.close()
        machine.reset()
#GET Method
      elif "GET" in cmd:
        file=cmd[4:-11].split(b'?')[0]
        head = self._head(c_s)
    # Priority of files:
    # 1. filename.gz
        if exists(file+'.gz'):
          self._sendFile(c_s,file+'.gz')
    # 2. filename
        elif exists(file):
          self._sendFile(c_s,file)
    # 3. filename.html
        elif exists(file+'.html'):
          self._sendFile(c_s,file+'.html')
    # 4. static version of filename
        elif self._sendStatic(c_s,file):
          pass #Static file is already sent
    # 5. static version of filename.html
        elif self._sendStatic(c_s,file+'.html'):
          pass #Static file is already sent
    # 6. any file that contains filename
        elif exists(self.search(file)):
          self._sendFile(c_s,self.search(file))
    # 7. if no file was requested send index.html or redirect to microIDE.html
        elif file == b'/':
          if exists('index.html'):
            self._sendFile(c_s,'index.html') #send index
          else:
            c_s.write(self._con_302.format("microIDE.html")) #redirect to editor
    # 8. 404 Error
        else:
          c_s.write(self._con_404)
        c_s.close()  
#PUT Method
      elif "PUT" in cmd:
        file=cmd[4:-11].split(b'?')[0]
        head = self._head(c_s)
        content_len=0
        if exists(file + self._tmp):
          uos.remove(file + self._tmp)
        f=open(file + self._tmp,'w')
        while True:
          data = c_s.recv(512)
          content_len += len(data)
          if len(data) == 512:
            f.write(data)
            sleep_ms(50)
            gc.collect()
          else:
            f.write(data)
            break
        f.close()
        if content_len == int(head[b'Content-Length']):
          c_s.write(self. _con_200)
          if  exists(file):
            uos.remove(file)
          uos.rename(file+ self._tmp,file)
        else:
          c_s.write(self._con_500)
        c_s.write(b'\r\n')
#DIR Method
      elif "DIR" in cmd:
        file=cmd[4:-11].split(b'?')[0]
        head = self._head(c_s)
        c_s.recv(4096) #dump the rest
        if "tree" in file:
          c_s.write(self._con_200)
          c_s.write('\r\n')
          c_s.write('Time: {}\r\n'.format(epoch()))
          c_s.write('Disk: {} / {}\r\n'.format((uos.statvfs("/")[2]-uos.statvfs("/")[3])*uos.statvfs("/")[1],uos.statvfs("/")[2]*uos.statvfs("/")[1]))
          gc.collect()
          c_s.write('Mem: {} / {} \r\n'.format(gc.mem_alloc(),self._mem))
          c_s.write('RST: {}\r\n'.format(machine.reset_cause()))
          c_s.write('ID: {};\r\n'.format(self._id))
          gc.collect()
          for x in self.tree():
              if uos.stat(x)[0]==32768: #File
                c_s.write('{},{};\r\n'.format(x,uos.stat(x)[6]))
              else:
                c_s.write('{},DIR;\r\n'.format(x))
        elif "runlog" in file:
          c_s.write(self._con_200)
          c_s.write(b'\r\n')
          c_s.write(self.ioStr.getvalue())
        elif "clearlog" in file:
          self._clearlog()
          c_s.write(self._con_200)
          c_s.write(b'\r\n')
          c_s.write(self.ioStr.getvalue())
#CMD Method
      elif "CMD" in cmd:
        file=cmd[4:-11].split(b'?')[0]
        head = self._head(c_s)
        if b'Clear-Log' in head:
          self._clearlog()
        #Means the code to be executed is attached
        if (file==b'/'):
          if b'CMD-Silent' in head:
            #Create temporary buffer StringIO to dump all resposne into
            tmp=StringIO()
            uos.dupterm(tmp,0)
            try:
              #Read & Execute anything that gets sent
              data=c_s.recv(4096)
              exec(data,self._vars)
              #Relpy 200 OK & send temporary buffer
              c_s.write(self._con_200)
              c_s.write(self._reply.format(tmp.getvalue()))
            except:
              #Relpy 500 ERROR & send temporary buffer
              c_s.write(self._con_500)
              c_s.write(self._reply.format(tmp.getvalue()))
            #Continue Logging
            uos.dupterm(self.ioStr,0)
          else:  #Not CMD-Silent
            try:
              data=c_s.recv(4096)
              #Just for show...
              print(">>> exec(String[0:4096],web_locals)")
              exec(data,self._vars)
              c_s.write(self._con_200)
              c_s.write(self._reply.format(self.ioStr.getvalue()))
            except:
              c_s.write(self._con_500)
              c_s.write(self._reply.format(self.ioStr.getvalue()))
        #Means the code to be executed is in a file
        else:
          try:
            c_s.recv(4096)
            print(">>> exec(open({}).read(),web_locals)".format(file))
            exec(open(file).read(),self._vars)
            c_s.write(self._con_200)
            c_s.write(self._reply.format(self.ioStr.getvalue()))
          except:
            c_s.write(self._con_500)
            c_s.write(self._reply.format(self.ioStr.getvalue()))
      else:
        c_s.write(self._con_500)
      c_s.close()
    except:
      pass
    gc.collect()
  def __init__(self):
    gc.collect()
    self._mem=gc.mem_alloc()+gc.mem_free()
    self._boot=epoch()
    self._vars={}
    self.ioStr=StringIO()
    self.ioStr.write("Begin ram log: {}\r\n--- Start ---\r\n".format(epoch()))
    uos.dupterm(self.ioStr,0)
    self.s = usocket.socket()
    self.s.setsockopt(usocket.SOL_SOCKET, usocket.SO_REUSEADDR, 1)
    self.s.bind(usocket.getaddrinfo("0.0.0.0", self._port)[0][-1])
    self.s.listen(8)
    self.s.settimeout(0)
    self.s.setsockopt(usocket.SOL_SOCKET, 20, self._srvReq)

ws=webServer() #Assigning it to a dumb vraible makes gc not delete it later
gc.collect()