{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Datatypes\n",
    "> Classes para modelar as diferentes estruturas de dados a serem enviadas para o Redmine"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| default_exp datatypes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "from dataclasses import dataclass\n",
    "from itertools import product\n",
    "from functools import cached_property\n",
    "\n",
    "from fastcore.xtras import listify\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "@dataclass\n",
    "class AtomicField:\n",
    "    name: str\n",
    "    keyword: str\n",
    "    mandatory: bool = True\n",
    "\n",
    "    @classmethod\n",
    "    def reset(cls, instance):\n",
    "        new_instance = cls(\n",
    "            **{k: v for k, v in instance.__dict__.items() if k != \"value\"}\n",
    "        )\n",
    "        return new_instance\n",
    "\n",
    "    @property\n",
    "    def dtype(self):\n",
    "        return \"string\"\n",
    "\n",
    "    @cached_property\n",
    "    def value(self):\n",
    "        return \"\"\n",
    "\n",
    "    def __call__(self, value):\n",
    "        self.value = value\n",
    "        return value\n",
    "\n",
    "    def __repr__(self) -> str:\n",
    "        string = \"\"\n",
    "        if hasattr(self, \"value\"):\n",
    "            string = f\"(value: {self.value})\"\n",
    "        if self.mandatory:\n",
    "            string += \" | <mandatory>\"\n",
    "        return string\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# | export\n",
    "@dataclass\n",
    "class SimpleField:\n",
    "    id: int\n",
    "    name: str\n",
    "    mandatory: bool = False\n",
    "    multiple: bool = False\n",
    "    format_value: bool = False\n",
    "    _dtype: str = \"string\"\n",
    "\n",
    "    @classmethod\n",
    "    def reset(cls, instance):\n",
    "        new_instance = cls(\n",
    "            **{k: v for k, v in instance.__dict__.items() if k != \"value\"}\n",
    "        )\n",
    "        return new_instance\n",
    "\n",
    "    @property\n",
    "    def dtype(self):\n",
    "        return self._dtype\n",
    "\n",
    "    @dtype.setter\n",
    "    def dtype(self, value):\n",
    "        self._dtype = value\n",
    "\n",
    "    @cached_property\n",
    "    def value(self):\n",
    "        match self.dtype:\n",
    "            case \"string\":\n",
    "                return \"\"\n",
    "            case \"int\":\n",
    "                return 0\n",
    "            case \"float\":\n",
    "                return 0.0\n",
    "            case \"list\":\n",
    "                return []\n",
    "            case _:\n",
    "                return None\n",
    "\n",
    "    def format_value_string(self, value: str) -> str:\n",
    "        if self.format_value:\n",
    "            return \"{\" + '\"valor\":\"{0}\",\"texto\":\"{0}\"'.format(value) + \"}\"\n",
    "        try:\n",
    "            value = str(value)\n",
    "        except TypeError as e:\n",
    "            raise ValueError(\n",
    "                f\"The value: {value} cannot be converted to a string\"\n",
    "            ) from e\n",
    "\n",
    "        return value\n",
    "\n",
    "    def validate_value(self, value: str | list) -> str | list:\n",
    "        if self.multiple:\n",
    "            return [self.format_value_string(v) for v in listify(value)]\n",
    "        else:\n",
    "            return self.format_value_string(value)\n",
    "\n",
    "    def __call__(self, value: str | list) -> dict[str, str | list]:\n",
    "        self.value = value\n",
    "        return {\"id\": self.id, \"value\": self.validate_value(value)}\n",
    "\n",
    "    def __repr__(self) -> str:\n",
    "        string = f'(value: {getattr(self, \"value\", \"\")})'\n",
    "        if self.mandatory:\n",
    "            string += \" | <mandatory>\"\n",
    "        if self.multiple:\n",
    "            string += \", <multiple>\"\n",
    "        return string\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# | export\n",
    "@dataclass\n",
    "class EncodedString(SimpleField):\n",
    "    \"\"\"This class always format the value string\n",
    "    The json key is called 'numero'\n",
    "    \"\"\"\n",
    "\n",
    "    def format_value_string(self, value: str) -> str:\n",
    "        return \"{\" + '\"numero\"=>\"{0}\"'.format(value) + \"}\"\n",
    "\n",
    "    def __repr__(self) -> str:\n",
    "        return super().__repr__()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# | export\n",
    "@dataclass\n",
    "class FieldWithOptions(SimpleField):\n",
    "    options: list[str] = None\n",
    "    mapping: dict[str, list] = None\n",
    "\n",
    "    @property\n",
    "    def dtype(self):\n",
    "        if self.multiple:\n",
    "            self._dtype = \"list\"\n",
    "        return self._dtype\n",
    "\n",
    "    def __call__(self, value: str | list) -> dict[str, str | list]:\n",
    "        if self.multiple:\n",
    "            value = [str(v) for v in listify(value)]\n",
    "            for v in value:\n",
    "                if self.options and v not in self.options:\n",
    "                    raise ValueError(\n",
    "                        f\"The value {v} must be one of the valid options: {self.options} for field {self.name}\"\n",
    "                    )\n",
    "            if isinstance(self.options, dict):\n",
    "                value = [self.options[v] for v in value]\n",
    "        else:\n",
    "            value = str(value)\n",
    "            if self.options and value not in self.options:\n",
    "                raise ValueError(\n",
    "                    f\"The value {value} must be one of the valid options: {self.options} for field {self.name}\"\n",
    "                )\n",
    "            if isinstance(self.options, dict):\n",
    "                value = self.options[value]\n",
    "        self.value = value\n",
    "        return {\"id\": self.id, \"value\": self.validate_value(value)}\n",
    "\n",
    "    def __repr__(self) -> str:\n",
    "        string = \"\"\n",
    "        if hasattr(self, \"value\"):\n",
    "            string = f\"(value: {self.value})\"\n",
    "        if self.mandatory:\n",
    "            string += \" | <mandatory>\"\n",
    "        if self.multiple:\n",
    "            string += \", <multiple>\"\n",
    "        if self.options:\n",
    "            string += \", <options>\"\n",
    "        if self.mapping is not None:\n",
    "            string += \", conditional\"\n",
    "        return string\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# | export\n",
    "@dataclass\n",
    "class Coordenadas:\n",
    "    id: int\n",
    "    name: str\n",
    "    mandatory: bool = False\n",
    "\n",
    "    @classmethod\n",
    "    def reset(cls, instance):\n",
    "        new_instance = cls(\n",
    "            **{k: v for k, v in instance.__dict__.items() if k != \"value\"}\n",
    "        )\n",
    "        return new_instance\n",
    "\n",
    "    @cached_property\n",
    "    def value(self):\n",
    "        return None\n",
    "\n",
    "    def format_value_string(self, latitude: str, longitude: str) -> str:\n",
    "        return (\n",
    "            \"{\"\n",
    "            + '\"latitude\"=>\"{0}\",\"longitude\"=>\"{1}\"'.format(latitude, longitude)\n",
    "            + \"}\"\n",
    "        )\n",
    "\n",
    "    def __call__(self, coords) -> dict[str, str]:\n",
    "        latitude, longitude = coords[0], coords[1]\n",
    "        self.value = (latitude, longitude)\n",
    "        return {\n",
    "            \"id\": self.id,\n",
    "            \"value\": self.format_value_string(latitude, longitude),\n",
    "        }\n",
    "\n",
    "    def __repr__(self) -> str:\n",
    "        string = \"\"\n",
    "        if hasattr(self, \"value\"):\n",
    "            string = f\"(value: {self.value})\"\n",
    "        if self.mandatory:\n",
    "            string += \" | <mandatory>\"\n",
    "        return string\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# | export\n",
    "@dataclass\n",
    "class GerarPlai:\n",
    "    id: int\n",
    "    name: str\n",
    "    mandatory: bool = False\n",
    "    TIPO_DE_PROCESSO = [\n",
    "        \"Gestão da Fiscalização: Lacração, Apreensão e Interrupção\",\n",
    "        \"Gestão da Fiscalização: Processo de Guarda\",\n",
    "    ]\n",
    "    COORD_FI = [\"FI\", \"FI1\", \"FI2\", \"FI3\"]\n",
    "    CODES = [\"100000539\", \"100000618\"]\n",
    "    options = list(product(TIPO_DE_PROCESSO, COORD_FI))\n",
    "\n",
    "    @classmethod\n",
    "    def reset(cls, instance):\n",
    "        new_instance = cls(\n",
    "            **{k: v for k, v in instance.__dict__.items() if k != \"value\"}\n",
    "        )\n",
    "        return new_instance\n",
    "\n",
    "    @cached_property\n",
    "    def value(self):\n",
    "        return \"0\"\n",
    "\n",
    "    def validate_tipo_processo(self, value: str) -> str:\n",
    "        options = dict(zip(self.TIPO_DE_PROCESSO, self.CODES))\n",
    "        if value not in options:\n",
    "            raise ValueError(\"tipo de processo PLAI é inválido\")\n",
    "        return options[value]\n",
    "\n",
    "    def validate_coord_fi(self, value: str) -> str:\n",
    "        if value not in self.COORD_FI:\n",
    "            raise ValueError(\"coord_fi is invalid\")\n",
    "        return value\n",
    "\n",
    "    def validate_values(self, tipo_processo: str = \"\", coord_fi: str = \"\") -> str:\n",
    "        tipo_processo = self.validate_tipo_processo(tipo_processo)\n",
    "        coord_fi = self.validate_coord_fi(coord_fi)\n",
    "        self.value = (tipo_processo, coord_fi)\n",
    "        return (\n",
    "            \"{\"\n",
    "            + '\"criar_processo\"=>\"1\",\"tipo_processo\"=>\"{0}\",\"coord_fi\"=>\"{1}\"'.format(\n",
    "                tipo_processo, coord_fi\n",
    "            )\n",
    "            + \"}\"\n",
    "        )\n",
    "\n",
    "    def __call__(self, args: tuple) -> dict[str, str]:\n",
    "        if len(args) == 2:\n",
    "            tipo_processo, coord_fi = args\n",
    "        else:\n",
    "            raise ValueError(\"tipo_de_processo_plai and coord_fi_plai are required\")\n",
    "\n",
    "        value = self.validate_values(tipo_processo, coord_fi)\n",
    "        return {\"id\": self.id, \"value\": value}\n",
    "\n",
    "    def __repr__(self) -> str:\n",
    "        string = \"\"\n",
    "        if hasattr(self, \"value\"):\n",
    "            string = f\"(value: {self.value})\"\n",
    "        if self.mandatory:\n",
    "            string += \" | <mandatory>\"\n",
    "        return string"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# | hide\n",
    "import nbdev\n",
    "\n",
    "nbdev.nbdev_export()\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
