import torch
import torch.nn as nn
import ultralytics.nn.modules as um
import ultralytics.nn.tasks as ut
import sys


class ChannelAttentionDyn(nn.Module):
    def __init__(self, reduction=16):
        super().__init__()
        self.reduction = reduction
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.max_pool = nn.AdaptiveMaxPool2d(1)
        self.mlp = None
        self.sigmoid = nn.Sigmoid()

    def _build(self, c, device, dtype):
        hidden = max(c // self.reduction, 1)
        self.mlp = nn.Sequential(
            nn.Conv2d(c, hidden, 1, bias=False),
            nn.ReLU(inplace=True),
            nn.Conv2d(hidden, c, 1, bias=False),
        ).to(device=device, dtype=dtype)

    def forward(self, x):
        if self.mlp is None:
            self._build(x.shape[1], x.device, x.dtype)
        else:
            p = next(self.mlp.parameters())
            if p.device != x.device or p.dtype != x.dtype:
                self.mlp = self.mlp.to(device=x.device, dtype=x.dtype)

        a = self.mlp(self.avg_pool(x))
        m = self.mlp(self.max_pool(x))
        return x * self.sigmoid(a + m)


class SpatialAttention(nn.Module):
    def __init__(self, kernel_size=7):
        super().__init__()
        padding = kernel_size // 2
        self.conv = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        if self.conv.weight.device != x.device or self.conv.weight.dtype != x.dtype:
            self.conv = self.conv.to(device=x.device, dtype=x.dtype)

        avg = torch.mean(x, dim=1, keepdim=True)
        mx, _ = torch.max(x, dim=1, keepdim=True)
        w = self.sigmoid(self.conv(torch.cat([avg, mx], dim=1)))
        return x * w


class CBAM(nn.Module):
    def __init__(self, reduction=16, sa_kernel=7):
        super().__init__()
        self.ca = ChannelAttentionDyn(reduction=reduction)
        self.sa = SpatialAttention(kernel_size=sa_kernel)

    def forward(self, x):
        return self.sa(self.ca(x))


# Register so torch/ultralytics can deserialize CBAM checkpoints.
um.CBAM = CBAM
ut.CBAM = CBAM
um.ChannelAttentionDyn = ChannelAttentionDyn
ut.ChannelAttentionDyn = ChannelAttentionDyn
um.SpatialAttention = SpatialAttention
ut.SpatialAttention = SpatialAttention
globals()["ChannelAttentionDyn"] = ChannelAttentionDyn
globals()["SpatialAttention"] = SpatialAttention
globals()["CBAM"] = CBAM

# Compatibility for checkpoints serialized with __main__.CBAM
main_mod = sys.modules.get("__main__")
if main_mod is not None:
    setattr(main_mod, "ChannelAttentionDyn", ChannelAttentionDyn)
    setattr(main_mod, "SpatialAttention", SpatialAttention)
    setattr(main_mod, "CBAM", CBAM)
