#!/usr/bin/env python
#############################################################
# ubi_reader/ubi
# (c) 2013 Jason Pruitt (jrspruitt@gmail.com)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#############################################################

from __future__ import annotations
from typing import TYPE_CHECKING
from ubireader.debug import error
from ubireader.ubi.block import sort, extract_blocks
from ubireader.ubi import display
from ubireader.ubi.image import description as image
from ubireader.ubi.block import layout, rm_old_blocks

if TYPE_CHECKING:
    from ubireader.ubi_io import ubi_file as UbiFile
    from ubireader.ubi.block import description as Block
    from ubireader.ubi.image import description as Image

class ubi_base(object):
    """UBI Base object

    Arguments:
    Obj:image       -- UBI image object 
    
    Attributes:
    ubi_file:file      -- ubi_file object
    Int:block_count    -- Number of blocks found.
    Int:first_peb_num  -- Number of the first UBI PEB in file.
    Int:leb_size       -- Size of Logical Erase Blocks.
    Int:peb_size       -- Size of Physical Erase Blocks.
    Int:min_io         -- Size of min I/O from vid_hdr_offset.
    Dict:blocks        -- Dict keyed by PEB number of all blocks.
    """

    def __init__(self, ubi_file: UbiFile) -> None:
        self.__name__ = 'UBI'
        self._file = ubi_file
        self._first_peb_num = 0
        self._blocks = extract_blocks(self)
        self._block_count = len(self.blocks)

        if self._block_count <= 0:
            error(self, 'Fatal', 'No blocks found.')

        arbitrary_block = next(iter(self.blocks.values()))
        self._min_io_size = arbitrary_block.ec_hdr.vid_hdr_offset
        self._leb_size = self.file.block_size - arbitrary_block.ec_hdr.data_offset


    def _get_file(self) -> UbiFile:
        """UBI File object

        Returns:
        Obj -- UBI File object.
        """
        return self._file
    file = property(_get_file)


    def _get_block_count(self) -> int:
        """Total amount of UBI blocks in file.

        Returns:
        Int -- Number of blocks
        """
        return self._block_count
    block_count = property(_get_block_count)


    def _set_first_peb_num(self, i: int) -> None:
        self._first_peb_num = i
    def _get_first_peb_num(self) -> int:
        """First Physical Erase Block with UBI data
        
        Returns:
        Int -- Number of the first PEB.
        """
        return self._first_peb_num
    first_peb_num = property(_get_first_peb_num, _set_first_peb_num)


    def _get_leb_size(self) -> int:
        """LEB size of UBI blocks in file.

        Returns:
        Int -- LEB Size.
        """
        return self._leb_size
    leb_size = property(_get_leb_size)


    def _get_peb_size(self) -> int:
        """PEB size of UBI blocks in file.

        Returns:
        Int -- PEB Size.
        """
        return self.file.block_size
    peb_size = property(_get_peb_size)


    def _get_min_io_size(self) -> int:
        """Min I/O Size

        Returns:
        Int -- Min I/O Size.
        """
        return self._min_io_size
    min_io_size = property(_get_min_io_size)


    def _get_blocks(self) -> dict[int, Block]:
        """Main Dict of UBI Blocks

        Passed around for lists of indexes to be made or to be returned
        filtered through a list. So there isn't multiple copies of blocks,
        as there can be thousands.
        """
        return self._blocks
    blocks = property(_get_blocks)


class ubi(ubi_base):
    """UBI object

    Arguments:
    Obj:ubi_file             -- UBI file object.

    Attributes:
    Inherits:ubi_base         -- ubi_base attributes.
    List:images               -- List of UBI image objects.
    List:data_blocks_list     -- List of all data blocks in file.
    List:layout_blocks_list   -- List of all layout blocks in file.
    List:int_vol_blocks_list  -- List of internal volumes minus layout.
    List:unknown_blocks_list  -- List of blocks with unknown types. *
    """

    def __init__(self, ubi_file: UbiFile) -> None:
        super(ubi, self).__init__(ubi_file)

        layout_list, data_list, int_vol_list, unknown_list = sort.by_type(self.blocks)

        self._layout_blocks_list = layout_list
        self._data_blocks_list = data_list
        self._int_vol_blocks_list = int_vol_list
        self._unknown_blocks_list = unknown_list
        
        newest_layout_list = rm_old_blocks(self.blocks, self.layout_blocks_list)
        
        if len(newest_layout_list) < 2:
            error(self, 'Fatal', 'Less than 2 layout blocks found.')

        layout_pairs = layout.group_pairs(self.blocks, newest_layout_list)

        layout_infos = layout.associate_blocks(self.blocks, layout_pairs)

        self._images: list[Image] = []
        for i in range(0, len(layout_infos)):
            self._images.append(image(self.blocks, layout_infos[i]))


    def _get_images(self) -> list[Image]:
        """Get UBI images.

        Returns:
        List -- Of volume objects groupled by image.
        """
        return self._images
    images = property(_get_images)


    def _get_data_blocks_list(self) -> list[int]:
        """Get all UBI blocks found in file that are data blocks.

        Returns:
        List -- List of block objects.
        """
        return self._data_blocks_list
    data_blocks_list = property(_get_data_blocks_list)


    def _get_layout_blocks_list(self) -> list[int]:
        """Get all UBI blocks found in file that are layout volume blocks.

        Returns:
        List -- List of block objects.
        """
        return self._layout_blocks_list
    layout_blocks_list = property(_get_layout_blocks_list)


    def _get_int_vol_blocks_list(self) -> list[int]:
        """Get all UBI blocks found in file that are internal volume blocks.

        Returns:
        List -- List of block objects.

        This does not include layout blocks.
        """
        return self._int_vol_blocks_list
    int_vol_blocks_list = property(_get_int_vol_blocks_list)


    def _get_unknown_blocks_list(self) -> list[int]:
        """Get all UBI blocks found in file of unknown type..

        Returns:
        List -- List of block objects.
        """
        return self._unknown_blocks_list
    unknown_blocks_list = property(_get_unknown_blocks_list)
    
    def display(self, tab: str = '') -> str:
        """Print information about this object.
        
        Argument:
        Str:tab    -- '\t' for spacing this object.
        """
        return display.ubi(self, tab)
