# coding: utf-8
from __future__ import print_function, unicode_literals

import calendar
import stat
import time

from .authsrv import AuthSrv
from .bos import bos
from .sutil import StreamArc, errdesc
from .util import min_ex, sanitize_fn, spack, sunpack, yieldfile, zlib

def dostime2unix(buf )  :
    t, d = sunpack(b"<HH", buf)

    ts = (t & 0x1F) * 2
    tm = (t >> 5) & 0x3F
    th = t >> 11

    dd = d & 0x1F
    dm = (d >> 5) & 0xF
    dy = (d >> 9) + 1980

    tt = (dy, dm, dd, th, tm, ts)
    tf = "{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}"
    iso = tf.format(*tt)

    dt = time.strptime(iso, "%Y-%m-%d %H:%M:%S")
    return int(calendar.timegm(dt))


def unixtime2dos(ts )  :
    dy, dm, dd, th, tm, ts, _, _, _ = time.gmtime(ts + 1)
    bd = ((dy - 1980) << 9) + (dm << 5) + dd
    bt = (th << 11) + (tm << 5) + ts // 2
    try:
        return spack(b"<HH", bt, bd)
    except:
        return b"\x00\x00\x21\x00"


def gen_fdesc(sz , crc32 , z64 )  :
    ret = b"\x50\x4b\x07\x08"
    fmt = b"<LQQ" if z64 else b"<LLL"
    ret += spack(fmt, crc32, sz, sz)
    return ret


def gen_hdr(
    h_pos ,
    z64 ,
    fn ,
    sz ,
    lastmod ,
    utf8 ,
    icrc32 ,
    pre_crc ,
)  :
    "a"

    z64v = [sz, sz] if z64 else []
    if h_pos and h_pos >= 0xFFFFFFFF:

        z64v.append(h_pos)

    req_ver = b"\x2d\x00" if z64 else b"\x0a\x00"

    if icrc32:
        crc32 = spack(b"<L", icrc32)
    else:
        crc32 = b"\x00" * 4

    if h_pos is None:

        ret = b"\x50\x4b\x03\x04" + req_ver
    else:

        ret = b"\x50\x4b\x01\x02\x1e\x03" + req_ver

    ret += b"\x00" if pre_crc else b"\x08"
    ret += b"\x08" if utf8 else b"\x00"

    ret += b"\x00\x00" + unixtime2dos(lastmod) + crc32

    vsz = 0xFFFFFFFF if z64 else sz
    ret += spack(b"<LL", vsz, vsz)

    fn = sanitize_fn(fn, "/")
    bfn = fn.encode("utf-8" if utf8 else "cp437", "replace").replace(b"?", b"_")

    z64_len = len(z64v) * 8 + 4 if z64v else 0
    ret += spack(b"<HH", len(bfn), 0x10 + z64_len)

    if h_pos is not None:

        ret += b"\x00" * 4

        ret += b"\x00\x00\x00\x00\xa4\x81"

        ret += spack(b"<L", min(h_pos, 0xFFFFFFFF))

    ret += bfn

    ret += spack(b"<HHLLHH", 0xD, 0xC, int(lastmod), int(lastmod), 1000, 1000)

    if z64v:
        ret += spack(b"<HH" + b"Q" * len(z64v), 1, len(z64v) * 8, *z64v)

    return ret


def gen_ecdr(
    items     , cdir_pos , cdir_end 
)   :
    "a"

    ret = b"\x50\x4b\x05\x06"

    ret += b"\x00" * 4

    cdir_sz = cdir_end - cdir_pos

    nitems = min(0xFFFF, len(items))
    csz = min(0xFFFFFFFF, cdir_sz)
    cpos = min(0xFFFFFFFF, cdir_pos)

    need_64 = nitems == 0xFFFF or 0xFFFFFFFF in [csz, cpos]

    ret += spack(b"<HHLL", nitems, nitems, csz, cpos)

    ret += b"\x00\x00"

    return ret, need_64


def gen_ecdr64(
    items     , cdir_pos , cdir_end 
)  :
    "a"

    ret = b"\x50\x4b\x06\x06"

    ret += b"\x2c" + b"\x00" * 7

    ret += b"\x1e\x03\x2d\x00"

    ret += b"\x00" * 8

    cdir_sz = cdir_end - cdir_pos
    ret += spack(b"<QQQQ", len(items), len(items), cdir_sz, cdir_pos)

    return ret


def gen_ecdr64_loc(ecdr64_pos )  :
    "a"

    ret = b"\x50\x4b\x06\x07"

    ret += spack(b"<LQL", 0, ecdr64_pos, 1)

    return ret


class StreamZip(StreamArc):
    def __init__(
        self,
        log ,
        asrv ,
        fgen    ,
        utf8  = False,
        pre_crc  = False,
        **kwargs 
    )  :
        super(StreamZip, self).__init__(log, asrv, fgen)

        self.utf8 = utf8
        self.pre_crc = pre_crc

        self.pos = 0
        self.items      = []

    def _ct(self, buf )  :
        self.pos += len(buf)
        return buf

    def ser(self, f  )    :
        name = f["vp"]
        src = f["ap"]
        st = f["st"]

        if stat.S_ISDIR(st.st_mode):
            return

        sz = st.st_size
        ts = st.st_mtime
        h_pos = self.pos

        crc = 0
        if self.pre_crc:
            for buf in yieldfile(src, self.args.iobuf):
                crc = zlib.crc32(buf, crc)

            crc &= 0xFFFFFFFF

        z64 = h_pos >= 0xFFFFFFFF or sz >= 0xFFFFFFFF

        buf = gen_hdr(None, z64, name, sz, ts, self.utf8, crc, self.pre_crc)
        yield self._ct(buf)

        for buf in yieldfile(src, self.args.iobuf):
            if not self.pre_crc:
                crc = zlib.crc32(buf, crc)

            yield self._ct(buf)

        crc &= 0xFFFFFFFF

        self.items.append((name, sz, ts, crc, h_pos))

        if z64 or not self.pre_crc:
            buf = gen_fdesc(sz, crc, z64)
            yield self._ct(buf)

    def gen(self)    :
        errf   = {}
        errors = []
        mbuf = b""
        try:
            for f in self.fgen:
                if "err" in f:
                    errors.append((f["vp"], f["err"]))
                    continue

                try:
                    for x in self.ser(f):
                        mbuf += x
                        if len(mbuf) >= 16384:
                            yield mbuf
                            mbuf = b""
                except GeneratorExit:
                    raise
                except:
                    ex = min_ex(5, True).replace("\n", "\n-- ")
                    errors.append((f["vp"], ex))

            if mbuf:
                yield mbuf
                mbuf = b""

            if errors:
                errf, txt = errdesc(self.asrv.vfs, errors)
                self.log("\n".join(([repr(errf)] + txt[1:])))
                for x in self.ser(errf):
                    yield x

            cdir_pos = self.pos
            for name, sz, ts, crc, h_pos in self.items:
                z64 = h_pos >= 0xFFFFFFFF or sz >= 0xFFFFFFFF
                buf = gen_hdr(h_pos, z64, name, sz, ts, self.utf8, crc, self.pre_crc)
                mbuf += self._ct(buf)
                if len(mbuf) >= 16384:
                    yield mbuf
                    mbuf = b""
            cdir_end = self.pos

            _, need_64 = gen_ecdr(self.items, cdir_pos, cdir_end)
            if need_64:
                ecdir64_pos = self.pos
                buf = gen_ecdr64(self.items, cdir_pos, cdir_end)
                mbuf += self._ct(buf)

                buf = gen_ecdr64_loc(ecdir64_pos)
                mbuf += self._ct(buf)

            ecdr, _ = gen_ecdr(self.items, cdir_pos, cdir_end)
            yield mbuf + self._ct(ecdr)
        finally:
            if errf:
                bos.unlink(errf["ap"])
