# -*- coding: utf-8 -*-

import ctypes, io, logging, re, struct, time
import cfg

sf2B=struct.Struct("<B")
sf2H=struct.Struct("<H")
sf2I=struct.Struct("<I")

logger = logging.getLogger(__name__)

class Directory:
    def __init__(self, fat, clust, parent):
        self.fat = fat
        self.first_clust = clust
        self.cur_clust = clust
        self.cur_adr = self.fat.clust2adr(clust)
        if parent:
            self.mkentry('.', 0x10, clust)
            self.mkentry('..', 0x10, [parent,0][parent==2])
        else:
            self.mkentry('wbfs2fat', 0x08)
        sf2I.pack_into(self.fat.raw, self.fat.clust2fat(clust), 0x0fffffff)

    def extend(self):
        new = self.fat.getfreeclust()
        sf2I.pack_into(self.fat.raw, self.fat.clust2fat(self.cur_clust), new)
        sf2I.pack_into(self.fat.raw, self.fat.clust2fat(new), 0x0fffffff)
        self.cur_clust = new
        self.cur_adr = self.fat.clust2adr(new)

    def mkentry(self, name, typ=0, target=0, size=0):
        name = '{:<11}'.format(name.upper()).encode("ascii")
        if self.cur_adr >= self.fat.clust2adr(self.cur_clust+1): self.extend()
        #0x00  8  name
        #0x08  3  extension
        self.fat.raw[self.cur_adr+0:self.cur_adr+0x0b] = name
        #0x0b  1  attributes
        sf2B.pack_into(self.fat.raw,self.cur_adr+0x0b,typ)
        #0x0c  1  reserved
        #0x0d  1  ctime fine
        #0x0E  2  ctime time
        self.fat.raw[self.cur_adr+0x0e:self.cur_adr+0x10] = self.fat.fat_time
        #0x10  2  ctime date
        self.fat.raw[self.cur_adr+0x10:self.cur_adr+0x12] = self.fat.fat_date
        #0x12  2  atime date
        self.fat.raw[self.cur_adr+0x12:self.cur_adr+0x14] = self.fat.fat_date
        #0x14  2  cluster (high)
        sf2H.pack_into(self.fat.raw,self.cur_adr+0x014,target>>16)
        #0x16  2  mtime time
        self.fat.raw[self.cur_adr+0x16:self.cur_adr+0x18] = self.fat.fat_time
        #0x18  2  mtime date
        self.fat.raw[self.cur_adr+0x18:self.cur_adr+0x1a] = self.fat.fat_date
        #0x1a  2  cluster (low)
        sf2H.pack_into(self.fat.raw,self.cur_adr+0x01a,target%2**16)
        #0x1c  4  size
        sf2I.pack_into(self.fat.raw,self.cur_adr+0x1c,size)
        self.cur_adr += 0x20

    def mklfnpart(self, seq, name, checksum):
        if self.cur_adr >= self.fat.clust2adr(self.cur_clust+1): self.extend()
        #0x00  1  Sequence Number (bit 6: last logical, first physical LFN entry, bit 5: 0; bits 4-0: number 0x01..0x14 (0x1F), deleted entry: 0xE5)
        sf2B.pack_into(self.fat.raw,self.cur_adr+0x00,seq)
        #0x01  10 Name characters (five UTF-16 characters)
        self.fat.raw[self.cur_adr+0x01:self.cur_adr+0x0b] = name[0:10]
        #0x0B  1  Attributes (always 0x0F)
        sf2B.pack_into(self.fat.raw,self.cur_adr+0x0b,0x0f)
        #0x0C  1  Type (always 0x00 for VFAT LFN, other values reserved for future use; for special usage of bits 4 and 3 in SFNs see below)
        #0x0D  1  Checksum of DOS file name
        sf2B.pack_into(self.fat.raw,self.cur_adr+0x0d,checksum)
        #0x0E  12 Name characters (six UTF-16 characters)
        self.fat.raw[self.cur_adr+0x0e:self.cur_adr+0x1a] = name[10:22]
        #0x1A  2  First cluster (always 0x0000)
        #0x1C  4  Name characters (two UTF-16 characters)
        self.fat.raw[self.cur_adr+0x1c:self.cur_adr+0x20] = name[22:26]
        self.cur_adr += 0x20

    def mklfn(self, name, longname):
        name = '{:<11}'.format(name.upper())
        longname = longname.encode("utf16")[2:]+sf2H.pack(0x0000)
        chksum = 0
        for asc in name:
            chksum = (0x80 if chksum & 0x01 else 0) + (chksum >> 1)
            chksum = (chksum + ord(asc)) & 0xff
        parts = enumerate(longname[i:i+26] for i in range(0, len(longname), 26))
        parts = [x for x in parts]
        parts.reverse()
        parts[0] = (parts[0][0]+0x40, parts[0][1])
        for seq, part in parts:
            part += sf2B.pack(0xff)*(26-len(part))
            self.mklfnpart(seq+1, part, chksum)

    def mkdir(self, name, longname):
        clust = self.fat.getfreeclust()
        if longname: self.mklfn(name, longname)
        self.mkentry(name, 0x10, clust)
        return Directory(self.fat, clust, self.first_clust)

    def mkfile(self, name, longname, extends):
        name = name.upper()
        if longname: self.mklfn(name, longname)
        self.mkentry(name, 0, self.fat.adr2clust(extends[0][0]), sum(e[1]-e[0] for e in extends))
        adr = self.fat.adr2fat(extends[0][0])
        for extend in extends:
            for clust, newadr in zip(range(self.fat.adr2clust(extend[0]), self.fat.adr2clust(extend[1])), range(self.fat.adr2fat(extend[0]), self.fat.adr2fat(extend[1]), 4)):
                sf2I.pack_into(self.fat.raw, adr, clust)
                adr = newadr
        sf2I.pack_into(self.fat.raw, adr, 0x0FFFFFFFF)


class Fat:
    res_sects_need = 7
    res_sects_flex = 64
    res_sects = res_sects_need + res_sects_flex

    def __init__(self,wbfs):
        self.wbfs = wbfs
        self.raw = ctypes.create_string_buffer(wbfs.fat_blocks << 21)
        self.res_sects = (wbfs.block_sects - wbfs.fat_head_sects + Fat.res_sects_flex) % wbfs.clust_sects + Fat.res_sects_need
        self.fat_head_sects = wbfs.fat_head_sects - Fat.res_sects + self.res_sects
        self.sect_cnt = wbfs.sect_cnt - (cfg.options.create_mbr << wbfs.block_sects_p)
        self.sect_size_p = wbfs.sect_size_p
        self.sect_size = 1<<self.sect_size_p
        self.clust_size_p = wbfs.clust_size_p
        self.clust_size = 1<<self.clust_size_p
        self.clust_sects_p = wbfs.clust_sects_p
        self.clust_sects = 1<<self.clust_sects_p
        ltime = time.localtime()
        self.fat_date = sf2H.pack(((ltime[0]-1980)<<9) + (ltime[1]<<5) + ltime[2])
        self.fat_time = sf2H.pack((ltime[3]<<11) + (ltime[4]<<5) + (ltime[5]>>1))
        self.lastusedclust = 2

    def adr2clust(self,adr):
        return adr-self.clust_start >> self.clust_size_p

    def adr2fat(self,adr):
        return self.clust2fat(self.adr2clust(adr))

    def clust2adr(self,clust):
        return self.clust_start + (clust<<self.clust_size_p)

    def clust2fat(self,clust):
        return self.fat_start + (clust<<2)

    def getfreeclust(self):
        self.lastusedclust+=1
        return self.lastusedclust

    def mkfs(self):
        self.fat_start = self.res_sects << self.sect_size_p
        self.clust_start = (self.fat_head_sects - (2<<self.clust_sects_p)) << self.sect_size_p

        #Sector 0
        #0x00  3  Jump
        struct.pack_into("<BBB",self.raw,0x00,0x0eb,0x058,0x090)
        #0x03  8  OEM Name
        self.raw[0x03:0x0b] = b'wbfs2fat'
        #0x0b  2  Bytes per sector
        sf2H.pack_into(self.raw,0x0b,self.sect_size)
        #0x0d  1  Sectors per cluster
        sf2B.pack_into(self.raw,0x0d,self.clust_sects)
        #0x0e  2  Reserved sector count
        sf2H.pack_into(self.raw,0x0e,self.res_sects)
        #0x10  1  Number of FATs
        sf2B.pack_into(self.raw,0x10,1)
        #0x11  2  0
        #0x13  2  Total sectors 2
        if self.sect_cnt <= 65535: sf2H.pack_into(self.raw,0x13,self.sect_cnt)
        #0x15  1  Media descriptor
        sf2B.pack_into(self.raw,0x15,0x0f8)
        #0x16  2  0
        #0x18  2  Sectors per track
        #0x1a  2  Number of heads
        #0x1c  4  Count of hidden sectors preceding
        #0x20  4  Total sectors 4
        if self.sect_cnt > 65535: sf2I.pack_into(self.raw,0x20,self.sect_cnt)
        #0x24  4  Sectors per FAT
        sf2I.pack_into(self.raw,0x24,self.fat_head_sects - self.res_sects)
        #0x28  2  FAT Flags
        #0x2a  2  Version (Defined as 0)
        #0x2c  4  Cluster number of root directory start
        sf2I.pack_into(self.raw,0x2c,2)
        #0x30  2  Sector number of FS Information Sector
        sf2H.pack_into(self.raw,0x30,1)
        #0x32  2  Sector number of a copy of this boot sector (0 if no backup copy exists)
        sf2H.pack_into(self.raw,0x32,6)
        #0x34  12 Reserved
        #0x40  1  Physical Drive Number
        sf2B.pack_into(self.raw,0x40,0x080)
        #0x41  1  Reserved
        #0x42  1  Extended boot signature
        sf2B.pack_into(self.raw,0x42,0x029)
        #0x43  4  ID (serial number)
        #0x47  11  Volume Label
        self.raw[0x47:0x52] = b'wbfs2fat   '
        #0x52  8  FAT file system type: "FAT32   "
        self.raw[0x52:0x5a] = b'FAT32   '
        #0x5a  420  Operating system boot code
        #0x1FE  2  Boot sector signature (0x55 0xAA)
        sf2H.pack_into(self.raw,0x01FE,0x0AA55)

        #sector 1
        #0x00  4  FS information sector signature
        sf2I.pack_into(self.raw,self.sect_size+0x00,0x041615252)
        #0x04  480  Reserved (byte values are 0x00)
        #0x1e4  4  FS information sector signature
        sf2I.pack_into(self.raw,self.sect_size+0x01e4,0x061417272)
        #0x1e8  4  Number of free clusters on the drive, or -1 if unknown
        struct.pack_into("<i",self.raw,self.sect_size+0x01e8,-1)
        #0x1ec  4  Number of the most recently allocated cluster
        #0x1f0  14  Reserved (byte values are 0x00)
        #0x1fe  2  FS information sector signature
        sf2H.pack_into(self.raw,self.sect_size+0x01fe,0x0AA55)

        #sector 6
        #backup of sector 0
        self.raw[6<<self.sect_size_p:7<<self.sect_size_p]=self.raw[0:self.sect_size]

        #FAT
        sf2I.pack_into(self.raw,self.fat_start+0x00,0x0fffffff8)
        sf2I.pack_into(self.raw,self.fat_start+0x04,0x00fffffff)

        #root
        self.root = Directory(self, 2, 0)
        self.wbfsdir = self.root.mkdir('wbfs', 'wbfs')
