'''ResNet in PyTorch.

For Pre-activation ResNet, see 'preact_resnet.py'.

Reference:Companion
[1] <NAME>, <NAME>, <NAME>, <NAME>
    Deep Residual Learning for Image Recognition. arXiv:1512.03385
'''
import torch
import torch.nn as nn
import torch.nn.functional as F

import encoder # getting the encoder from the trained autoencoder


class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, in_planes, planes, stride=1):
        super(BasicBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)

        self.shortcut = nn.Sequential()
        if stride != 1 or in_planes != self.expansion*planes:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(self.expansion*planes)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out += self.shortcut(x)
        out = F.relu(out)
        return out


class Bottleneck(nn.Module):
    expansion = 4

    def __init__(self, in_planes, planes, stride=1):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)
        self.conv3 = nn.Conv2d(planes, self.expansion*planes, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(self.expansion*planes)

        self.shortcut = nn.Sequential()
        if stride != 1 or in_planes != self.expansion*planes:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(self.expansion*planes)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = F.relu(self.bn2(self.conv2(out)))
        out = self.bn3(self.conv3(out))
        out += self.shortcut(x)
        out = F.relu(out)
        return out


# DONE: rework resnet, 
    # input filter 3->1
    # out filter 2 classes (galaxy and not galaxy) check line 105
    # print(galaxy_name)
class ResNet(nn.Module):
    def __init__(self, block, num_blocks, encoder, num_classes=2): # used to b 10
        super(ResNet, self).__init__()      
        self.in_planes = 16

        self.encoder = encoder

        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1, bias=False) # NEW 
        self.bn1 = nn.BatchNorm2d(16)
        self.layer1 = self._make_layer(block, 16, num_blocks[0], stride=1)
        self.layer2 = self._make_layer(block, 32, num_blocks[1], stride=2)
        self.layer3 = self._make_layer(block, 64, num_blocks[2], stride=2)
        self.layer4 = self._make_layer(block, 128, num_blocks[3], stride=2)
        self.linear = nn.Linear(512*block.expansion, num_classes)
        self.softie = nn.Softmax(dim=1)


# HERE IS THE OLD VERSION 
        
# END OF OLD VERSION 

    def _make_layer(self, block, planes, num_blocks, stride):
        strides = [stride] + [1]*(num_blocks-1)
        layers = []
        for stride in strides:
            layers.append(block(self.in_planes, planes, stride))
            self.in_planes = planes * block.expansion
        return nn.Sequential(*layers)

    def forward(self, x):
        # out = self.encoder(x) TODO: use the encoder
        # print("SIZE OF THIS", out.shape)
        out = F.relu(self.bn1(self.conv1(x)))
        # print("SIZE OF THIS", out.shape)
        out = self.layer1(out)
        # print("SIZE OF THIS", out.shape)
        out = self.layer2(out)
        # print("SIZE OF THIS", out.shape)
        out = self.layer3(out)
        # print("SIZE OF THIS", out.shape)
        out = self.layer4(out)
        # print("SIZE OF THIS", out.shape)
        out = F.avg_pool2d(out, 4)
        # print("SIZE OF THIS", out.shape) # 64, 8, 8
        out = out.view(out.size(0), -1)
        # print("SIZE OF THIS", out.shape) # 4096
        out = self.linear(out)
        # print("SIZE OF THIS", out.shape)
        return out


def ResNet18(encoder):
    return ResNet(BasicBlock, [2,2,2,2], encoder, 2) # NEW 

def ResNet34(encoder):
    return ResNet(BasicBlock, [3,4,6,3], encoder, 2)

def ResNet50():
    return ResNet(Bottleneck, [3,4,6,3])

def ResNet101():
    return ResNet(Bottleneck, [3,4,23,3])

def ResNet152():
    return ResNet(Bottleneck, [3,8,36,3])


def test():
    net = ResNet18()
    y = net(torch.randn(1,1,32,32))
    print(y.size())

# test()

'''ResNet in PyTorch.

For Pre-activation ResNet, see 'preact_resnet.py'.

Reference:Companion
[1] <NAME>, <NAME>, <NAME>, <NAME>
    Deep Residual Learning for Image Recognition. arXiv:1512.03385
'''
import torch
import torch.nn as nn
import torch.nn.functional as F

import encoder # getting the encoder from the trained autoencoder


class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, in_planes, planes, stride=1):
        super(BasicBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)

        self.shortcut = nn.Sequential()
        if stride != 1 or in_planes != self.expansion*planes:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(self.expansion*planes)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out += self.shortcut(x)
        out = F.relu(out)
        return out


class Bottleneck(nn.Module):
    expansion = 4

    def __init__(self, in_planes, planes, stride=1):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)
        self.conv3 = nn.Conv2d(planes, self.expansion*planes, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(self.expansion*planes)

        self.shortcut = nn.Sequential()
        if stride != 1 or in_planes != self.expansion*planes:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(self.expansion*planes)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = F.relu(self.bn2(self.conv2(out)))
        out = self.bn3(self.conv3(out))
        out += self.shortcut(x)
        out = F.relu(out)
        return out


# DONE: rework resnet, 
    # input filter 3->1
    # out filter 2 classes (galaxy and not galaxy) check line 105
    # print(galaxy_name)
class ResNet(nn.Module):
    def __init__(self, block, num_blocks, encoder, num_classes=2): # used to b 10
        super(ResNet, self).__init__()      
        self.in_planes = 16

        self.encoder = encoder

        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1, bias=False) # NEW 
        self.bn1 = nn.BatchNorm2d(16)
        self.layer1 = self._make_layer(block, 16, num_blocks[0], stride=1)
        self.layer2 = self._make_layer(block, 32, num_blocks[1], stride=2)
        self.layer3 = self._make_layer(block, 64, num_blocks[2], stride=2)
        self.layer4 = self._make_layer(block, 128, num_blocks[3], stride=2)
        self.linear = nn.Linear(512*block.expansion, num_classes)
        self.softie = nn.Softmax(dim=1)


# HERE IS THE OLD VERSION 
        
# END OF OLD VERSION 

    def _make_layer(self, block, planes, num_blocks, stride):
        strides = [stride] + [1]*(num_blocks-1)
        layers = []
        for stride in strides:
            layers.append(block(self.in_planes, planes, stride))
            self.in_planes = planes * block.expansion
        return nn.Sequential(*layers)

    def forward(self, x):
        # out = self.encoder(x) TODO: use the encoder
        # print("SIZE OF THIS", out.shape)
        out = F.relu(self.bn1(self.conv1(x)))
        # print("SIZE OF THIS", out.shape)
        out = self.layer1(out)
        # print("SIZE OF THIS", out.shape)
        out = self.layer2(out)
        # print("SIZE OF THIS", out.shape)
        out = self.layer3(out)
        # print("SIZE OF THIS", out.shape)
        out = self.layer4(out)
        # print("SIZE OF THIS", out.shape)
        out = F.avg_pool2d(out, 4)
        # print("SIZE OF THIS", out.shape) # 64, 8, 8
        out = out.view(out.size(0), -1)
        # print("SIZE OF THIS", out.shape) # 4096
        out = self.linear(out)
        # print("SIZE OF THIS", out.shape)
        return out


def ResNet18(encoder):
    return ResNet(BasicBlock, [2,2,2,2], encoder, 2) # NEW 

def ResNet34(encoder):
    return ResNet(BasicBlock, [3,4,6,3], encoder, 2)

def ResNet50():
    return ResNet(Bottleneck, [3,4,6,3])

def ResNet101():
    return ResNet(Bottleneck, [3,4,23,3])

def ResNet152():
    return ResNet(Bottleneck, [3,8,36,3])


def test():
    net = ResNet18()
    y = net(torch.randn(1,1,32,32))
    print(y.size())

# test()

ResNet in PyTorch.  For Pre-activation ResNet, see 'preact_resnet.py'.  Reference:Companion [1] <NAME>, <NAME>, <NAME>, <NAME>     Deep Residual Learning for Image Recognition. arXiv:1512.03385 # getting the encoder from the trained autoencoder # DONE: rework resnet, # input filter 3->1 # out filter 2 classes (galaxy and not galaxy) check line 105 # print(galaxy_name) # used to b 10 # NEW # HERE IS THE OLD VERSION # END OF OLD VERSION # out = self.encoder(x) TODO: use the encoder # print("SIZE OF THIS", out.shape) # print("SIZE OF THIS", out.shape) # print("SIZE OF THIS", out.shape) # print("SIZE OF THIS", out.shape) # print("SIZE OF THIS", out.shape) # print("SIZE OF THIS", out.shape) # print("SIZE OF THIS", out.shape) # 64, 8, 8 # print("SIZE OF THIS", out.shape) # 4096 # print("SIZE OF THIS", out.shape) # NEW # test()