import math


class FlexBuffer():

    def __init__(self):

        self.blockSize = None
        self.c = None
        self.l = None
        self.buf = None

    def require(self, n):
        
        r = self.c - self.l + n
        if r > 0:
            self.l = self.l + self.blockSize * math.ceil(r / self.blockSize)
            #tmp = bytearray(self.l)
            #for i in len(self.buf):
            #    tmp[i] = self.buf[i]
            #self.buf = tmp
            self.buf = self.buf + bytearray(self.l - len(self.buf))
        self.c = self.c + n
        return self.buf

    def alloc(self, initSize, blockSize):
        
        if blockSize:
            sz = blockSize
        else:
            sz = 4096
        self.blockSize = self.roundUp(sz)
        self.c = 0
        self.l = self.roundUp(initSize) | 0
        self.l += self.blockSize - (self.l % self.blockSize)
        self.buf = bytearray(self.l)
        return self.buf

    def roundUp(self, n):
        
        r = n % 4
        if r == 0:
            return n
        else:
            return n + 4 - r

    def reset(self):

        self.c = 0
        self.l = len(self.buf)

    def pack(self, size):
        
        return self.buf[0:size]

def _decompress(inBuf, outBuf):

    c_top_loop = 1
    c_first_literal_run = 2
    c_match = 3
    c_copy_match = 4
    c_match_done = 5
    c_match_next = 6

    out = outBuf.buf
    op = 0
    ip = 0
    t = inBuf[ip]
    state = c_top_loop
    m_pos = 0
    ip_end = len(inBuf)

    if t > 17:
        ip = ip + 1
        t = t - 17
        if t < 4:
            state = c_match_next
        else:
            out = outBuf.require(t)
            while True:
                out[op] = inBuf[ip]
                op = op + 1
                ip = ip + 1
                t = t - 1
                if not t > 0: break
            state = c_first_literal_run

    while True:
        if_block = False

        ##
        if state == c_top_loop:
            t = inBuf[ip]
            ip = ip + 1
            if t >= 16:
                state = c_match
                continue
            if t == 0:
                while inBuf[ip] == 0:
                    t = t + 255
                    ip = ip + 1
                t = t + 15 + inBuf[ip]
                ip = ip + 1

            t = t + 3
            out = outBuf.require(t)
            while True:
                out[op] = inBuf[ip]
                op = op + 1
                ip = ip + 1
                t = t - 1
                if not t > 0: break
            # emulate c switch
            state = c_first_literal_run

        ##
        if state == c_first_literal_run:
            t = inBuf[ip]
            ip = ip + 1
            if t >= 16:
                state = c_match
                continue
            m_pos = op - 0x801 - (t >> 2) - (inBuf[ip] << 2)
            ip = ip + 1
            out = outBuf.require(3)
            out[op] = out[m_pos]
            op = op + 1
            m_pos = m_pos + 1
            out[op] = out[m_pos]
            op = op + 1
            m_pos = m_pos + 1
            out[op] = out[m_pos]
            op = op + 1

            state = c_match_done
            continue

        ##
        if state == c_match:
            if t >= 64:
                m_pos = op - 1 - ((t >> 2) & 7) - (inBuf[ip] << 3)
                ip = ip + 1
                t = (t >> 5) - 1
                state = c_copy_match
                continue
            elif t >= 32:
                t = t & 31
                if t == 0:
                    while inBuf[ip] == 0:
                        t = t + 255
                        ip = ip + 1
                    t = t + 31 + inBuf[ip]
                    ip = ip + 1
                m_pos = op - 1 - ((inBuf[ip] + (inBuf[ip + 1] << 8)) >> 2)
                ip = ip + 2
            elif t >= 16:
                m_pos = op - ((t & 8) << 11)
                t = t & 7
                if t == 0:
                    while inBuf[ip] == 0:
                        t = t + 255
                        ip = ip + 1
                    t = t + 7 + inBuf[ip]
                    ip = ip + 1
                m_pos = m_pos - ((inBuf[ip] + (inBuf[ip + 1] << 8)) >> 2)
                ip = ip + 2
                if m_pos == op:
                    break
                m_pos = m_pos - 0x4000
            else:
                m_pos = op - 1 - (t >> 2) - (inBuf[ip] << 2);
                ip = ip + 1
                out = outBuf.require(2)
                out[op] = out[m_pos]
                op = op + 1
                m_pos = m_pos + 1
                out[op] = out[m_pos]
                op = op + 1
                state = c_match_done
                continue

            if t >= 6 and (op - m_pos) >= 4:
                if_block = True
                t += 2
                out = outBuf.require(t)
                while True:
                    out[op] = out[m_pos]
                    op += 1
                    m_pos += 1
                    t -= 1
                    if not t > 0: break
            #emulate c switch
            state = c_copy_match
        
        ##
        if state == c_copy_match:
            if not if_block:
                t += 2
                out = outBuf.require(t)
                while True:
                    out[op] = out[m_pos]
                    op += 1
                    m_pos += 1
                    t -= 1
                    if not t > 0: break
            #emulating c switch
            state = c_match_done
 
        ##
        if state == c_match_done:
            t = inBuf[ip - 2] & 3
            if t == 0:
                state = c_top_loop
                continue
            #emulate c switch
            state = c_match_next

        ##
        if state == c_match_next:
            out = outBuf.require(1)
            out[op] = inBuf[ip]
            op += 1
            ip += 1
            if t > 1:
                out = outBuf.require(1)
                out[op] = inBuf[ip]
                op += 1
                ip += 1
                if t > 2:
                    out = outBuf.require(1)
                    out[op] = inBuf[ip]
                    op += 1
                    ip += 1
            t = inBuf[ip]
            ip += 1
            state = c_match
            continue

    return bytes(outBuf.pack(op))

def decompress(input, initSize = 16000, blockSize = 8192):
    output = FlexBuffer()
    output.alloc(initSize, blockSize)
    return _decompress(bytearray(input), output)


