from typing import Any

from pydantic import BaseModel, Field


class Info(BaseModel):
    year: str | int | None = None
    version: str | None = None
    description: str | None = None
    contributor: str | None = None
    url: str | None = None
    date_created: str | None = None


class Image(BaseModel):
    id: int
    file_name: str
    width: int | None = None
    height: int | None = None
    license: int | None = None
    flickr_url: str | None = None
    coco_url: str | None = None
    date_captured: str | None = None
    attributes: dict[str, Any] | None = None


class License(BaseModel):
    id: int
    name: str
    url: str | None = None


class Category(BaseModel):
    id: int
    name: str
    keypoints: list[str] | None = None
    skeleton: list[list[int]] | None = None
    supercategory: str | None = None


class SegmentationRleIn(BaseModel):
    size: list[int]
    counts: str | list[int]


class Annotation(BaseModel):
    id: int
    image_id: int
    category_id: int
    bbox: list[int | float]
    segmentation: list[list[int | float]] | SegmentationRleIn | None = None
    keypoints: list[int | float] | None = None
    num_keypoints: int | None = None
    score: float = 0.0
    iscrowd: int = 0
    area: float = 0.0
    utf8_string: str | None = None
    attributes: dict[str, Any] | None = None

    def bbox_area(self) -> float:
        if self.bbox is None or self.bbox == []:
            raise ValueError("This annotation has no bbox, so it does not have area")
        if len(self.bbox) != 4:
            raise ValueError(
                f"This annotation has a malformed bbox: {self.bbox} should have a length of 4"
            )
        return self.bbox[2] * self.bbox[3]

    def is_rle(self) -> bool:
        return (
            self.segmentation is not None
            and isinstance(self.segmentation, SegmentationRleIn)
            and self.segmentation != {}
        )

    def is_keypoints(self):
        return (
            self.keypoints is not None
            and isinstance(self.keypoints, list)
            and self.keypoints != []
        )

    def is_polygon(self) -> bool:
        return (
            not self.is_keypoints()
            and not self.is_rle()
            and self.segmentation is not None
            and isinstance(self.segmentation, list)
            and self.segmentation != []
        )

    def is_rectangle(self) -> bool:
        return (
            not self.is_keypoints()
            and not self.is_rle()
            and not self.is_polygon()
            and (
                self.segmentation is None
                or self.segmentation == {}
                or self.segmentation == []
            )
            and not self.bbox == []
        )

    def polygon_to_list_coordinates(self) -> list[list[list[int]]]:
        if not self.is_polygon():
            raise ValueError("This is not a polygon")

        k = 0
        polygons = []
        for polygon in self.segmentation:
            if len(polygon) % 2 != 0:
                raise ValueError(
                    f"The {k} element of this segmentation is not a polygon."
                )
            polygons.append(
                [
                    [int(polygon[k]), int(polygon[k + 1])]
                    for k in range(0, len(polygon), 2)
                ]
            )
            k += 1

        return polygons


class COCOFile(BaseModel):
    info: Info = Field(default_factory=Info)
    licenses: list[License] = Field(default_factory=list)
    categories: list[Category] = Field(default_factory=list)
    images: list[Image]
    annotations: list[Annotation]
