from ...vision import *

__all__ = ['xception']

def sep_conv(ni,nf,pad=None,pool=False,act=True):
    layers =  [nn.ReLU()] if act else []
    layers += [
        nn.Conv2d(ni,ni,3,1,1,groups=ni,bias=False),
        nn.Conv2d(ni,nf,1,bias=False),
        nn.BatchNorm2d(nf)
    ]
    if pool: layers.append(nn.MaxPool2d(2))
    return nn.Sequential(*layers)

def conv(ni,nf,ks=1,stride=1, pad=None, act=True):
    if pad is None: pad=ks//2
    layers = [
        nn.Conv2d(ni,nf,ks,stride,pad,bias=False),
        nn.BatchNorm2d(nf),
    ]
    if act: layers.append(nn.ReLU())
    return nn.Sequential(*layers)

class ConvSkip(nn.Module):
    def __init__(self,ni,nf=None,act=True):
        super().__init__()
        self.nf,self.ni = nf,ni
        if self.nf is None: self.nf = ni
        self.conv = conv(ni,nf,stride=2, act=False)
        self.m = nn.Sequential(
            sep_conv(ni,ni,act=act),
            sep_conv(ni,nf,pool=True)
        )

    def forward(self,x): return self.conv(x) + self.m(x)

def middle_flow(nf):
    layers = [sep_conv(nf,nf) for i in range(3)]
    return SequentialEx(*layers, MergeLayer())

def xception(c, k=8, n_middle=8):
    "Preview version of Xception network. Not tested yet - use at own risk. No pretrained model yet."
    layers = [
        conv(3, k*4, 3, 2),
        conv(k*4, k*8, 3),
        ConvSkip(k*8, k*16, act=False),
        ConvSkip(k*16, k*32),
        ConvSkip(k*32, k*91),
    ]
    for i in range(n_middle): layers.append(middle_flow(k*91))
    layers += [
        ConvSkip(k*91,k*128),
        sep_conv(k*128,k*192,act=False),
        sep_conv(k*192,k*256),
        nn.ReLU(),
        nn.AdaptiveAvgPool2d(1),
        Flatten(),
        nn.Linear(k*256,c)
    ]
    return nn.Sequential(*layers)

