#!/usr/bin/env python
#
# This file describes eFuses controller for ESP32-C2 chip
#
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later

from bitstring import BitString

import reedsolo

from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalError


class EmulateEfuseController(EmulateEfuseControllerBase):
    """The class for virtual efuse operation. Using for HOST_TEST."""

    CHIP_NAME = "ESP32-C2"
    mem = None
    debug = False
    Blocks = EfuseDefineBlocks
    Fields = EfuseDefineFields
    REGS = EfuseDefineRegisters

    def __init__(self, efuse_file=None, debug=False):
        super(EmulateEfuseController, self).__init__(efuse_file, debug)
        self.write_reg(self.REGS.EFUSE_STATUS_REG, 1)

    """ esptool method start >>"""

    def get_major_chip_version(self):
        return 1

    def get_minor_chip_version(self):
        return 0

    def get_crystal_freq(self):
        return 40  # MHz

    def get_security_info(self):
        return {
            "flags": 0,
            "flash_crypt_cnt": 0,
            "key_purposes": 0,
            "chip_id": 0,
            "api_version": 0,
        }

    """ << esptool method end """

    def handle_writing_event(self, addr, value):
        if addr == self.REGS.EFUSE_CMD_REG:
            if value & self.REGS.EFUSE_PGM_CMD:
                self.copy_blocks_wr_regs_to_rd_regs(updated_block=(value >> 2) & 0xF)
                self.clean_blocks_wr_regs()
                self.check_rd_protection_area()
                self.write_reg(addr, 0)
                self.write_reg(self.REGS.EFUSE_STATUS_REG, 1)
            elif value == self.REGS.EFUSE_READ_CMD:
                self.write_reg(addr, 0)
                self.write_reg(self.REGS.EFUSE_STATUS_REG, 1)
                self.save_to_file()

    def get_bitlen_of_block(self, blk, wr=False):
        if blk.id == 0:
            if wr:
                return 32 * 8
            else:
                return 32 * blk.len
        else:
            if wr:
                rs_coding = 32 * 3
                return 32 * 8 + rs_coding
            else:
                return 32 * blk.len

    def handle_coding_scheme(self, blk, data):
        if blk.id != 0:
            # CODING_SCHEME RS applied only for all blocks except BLK0.
            coded_bytes = 12
            data.pos = coded_bytes * 8
            plain_data = data.readlist("32*uint:8")[::-1]
            # takes 32 bytes
            # apply RS encoding
            rs = reedsolo.RSCodec(coded_bytes)
            # 32 byte of data + 12 bytes RS
            calc_encoded_data = list(rs.encode([x for x in plain_data]))
            data.pos = 0
            if calc_encoded_data != data.readlist("44*uint:8")[::-1]:
                raise FatalError("Error in coding scheme data")
            data = data[coded_bytes * 8 :]
        if blk.len < 8:
            data = data[(8 - blk.len) * 32 :]
        return data

    def check_rd_protection_area(self):
        # checks fields which have the read protection bits.
        # if the read protection bit is set then we need to reset this field to 0.

        def get_read_disable_mask(blk):
            mask = 0
            if isinstance(blk.read_disable_bit, list):
                for i in blk.read_disable_bit:
                    mask |= 1 << i
            else:
                mask = 1 << blk.read_disable_bit
            return mask

        read_disable_bit = self.read_field("RD_DIS", bitstring=False)
        for b in self.Blocks.BLOCKS:
            blk = self.Blocks.get(b)
            block = self.read_block(blk.id)
            if (
                blk.read_disable_bit is not None
                and read_disable_bit & get_read_disable_mask(blk)
            ):
                if isinstance(blk.read_disable_bit, list):
                    if read_disable_bit & (1 << blk.read_disable_bit[0]):
                        block.set(
                            0, [i for i in range(blk.len * 32 // 2, blk.len * 32)]
                        )
                    if read_disable_bit & (1 << blk.read_disable_bit[1]):
                        block.set(0, [i for i in range(0, blk.len * 32 // 2)])
                else:
                    block.set(0)
            else:
                for e in self.Fields.EFUSES:
                    field = self.Fields.get(e)
                    if (
                        blk.id == field.block
                        and field.read_disable_bit is not None
                        and read_disable_bit & get_read_disable_mask(field)
                    ):
                        raw_data = self.read_field(field.name)
                        raw_data.set(0)
                        block.pos = block.length - (
                            field.word * 32 + field.pos + raw_data.length
                        )
                        block.overwrite(BitString(raw_data.length))
            self.overwrite_mem_from_block(blk, block)
