#!/usr/bin/env python3
# -*- python -*-

import mmap
import struct
import os
import subprocess
import ctypes as ct
import signal
import fcntl
# from pconst import const

from sony_fpga_driver import (fpga_reg_write_arg, SONY_FPGA_REG_WRITE,
                              SONY_FPGA_REG_UPDATE_OK,
                              SONY_FPGA_REG_UPDATE_COMPLETED,
                              SONY_FPGA_WAIT_VD_INFO)

PCI_BUS = {
    "GIF": {"dev": "gif", "dev_file": "/dev/sony_fpga_gif"},
    "GIF2": {"dev": "gif2", "dev_file": "/dev/sony_fpga_gif2"},
    "GIF-Video": {"dev": "gif_video", "dev_file": "/dev/sony_fpga_gif_video"},
    "SWR1": {"dev": "swr1", "dev_file": "/dev/sony_fpga_swr1"},
    "SWR2": {"dev": "swr2", "dev_file": "/dev/sony_fpga_swr2"},
    "SDI1": {"dev": "sdi1", "dev_file": "/dev/sony_fpga_sdi1"},
    "SDI2": {"dev": "sdi2", "dev_file": "/dev/sony_fpga_sdi2"},
    "XPT": {"dev": "xpt", "dev_file": "/dev/sony_fpga_xpt"},
    "NET1_BIMA": {"dev": "net1_bima", "dev_file": "/dev/sony_fpga_net1_bima"},
    "NET1_BIMB": {"dev": "net1_bimb", "dev_file": "/dev/sony_fpga_net1_bimb"},
    "NET1_NEM": {"dev": "net1_nem", "dev_file": "/dev/sony_fpga_net1_nem"},
    "NET2_BIMA": {"dev": "net2_bima", "dev_file": "/dev/sony_fpga_net2_bima"},
    "NET2_BIMB": {"dev": "net2_bimb", "dev_file": "/dev/sony_fpga_net2_bimb"},
    "NET2_NEM": {"dev": "net2_nem", "dev_file": "/dev/sony_fpga_net2_nem"},
    "IO1_SISA": {"dev": "io1_sisa", "dev_file": "/dev/sony_fpga_io1_sisa"},
    "IO1_SISB": {"dev": "io1_sisb", "dev_file": "/dev/sony_fpga_io1_sisb"},
    "IO2_SISA": {"dev": "io2_sisa", "dev_file": "/dev/sony_fpga_io2_sisa"},
    "IO2_SISB": {"dev": "io2_sisb", "dev_file": "/dev/sony_fpga_io2_sisb"},
    }


class SonyFpgaDriver():
    def __init__(self, fpga_name):
        if fpga_name in PCI_BUS:
            self.__fpga_name = fpga_name
            dev_file = PCI_BUS[fpga_name]["dev_file"]
            self.__fp = open(dev_file, "w+b", buffering=0)

    def __del__(self):
        self.__fp.close()

    def IOCTL_SONY_FPGA_REG_WRITE(self, dst_adrs, reg_top, size):
        # print("IOCTL_SONY_FPGA_REG_WRITE")
        if self.__fp is None:
            return False
        info = fpga_reg_write_arg()
        info.dst_address = dst_adrs
        info.reg_top = reg_top
        info.size = size
        fcntl.ioctl(self.__fp, SONY_FPGA_REG_WRITE, info)
        return True

    def IOCTL_SONY_FPGA_REG_UPDATE_OK(self):
        # print("IOCTL_SONY_FPGA_REG_UPDATE_OK")
        if self.__fp is None:
            return False
        fcntl.ioctl(self.__fp, SONY_FPGA_REG_UPDATE_OK)
        return True

    def IOCTL_SONY_FPGA_REG_UPDATE_COMPLETED(self):
        # print("IOCTL_SONY_FPGA_REG_UPDATE_COMPLETED")
        if self.__fp is None:
            return False
        if self.__fpga_name != "GIF":
            return False
        fcntl.ioctl(self.__fp, SONY_FPGA_REG_UPDATE_COMPLETED)
        return True

    def IOCTL_SONY_FPGA_WAIT_VD_INFO(self):
        # print("\nIOCTL_SONY_FPGA_WAIT_VD_INFO")
        if self.__fp is None:
            return False
        if self.__fpga_name != "GIF":
            return False
        vd_count = ct.c_uint(0)
        fcntl.ioctl(self.__fp, SONY_FPGA_WAIT_VD_INFO, vd_count)
        return True, vd_count.value


class PciBar():
    def __init__(self, fpga_name):
        self.__bar = False
        if fpga_name in PCI_BUS:
            try:
                with open('/sony/tmp/init/pci_busno',
                          'r', encoding='utf-8') as file:
                    lines = file.readlines()
            except Exception as e:
                print(e)
                return
            except FileNotFoundError as e:
                print(e)
                return

            for line in lines:
                if line.split(" ")[0] == PCI_BUS[fpga_name]["dev"]:
                    busno = line.split(" ")[1].replace("\n", "") + ":00.0"
                    proc = subprocess.run(
                        ["/usr/sbin/setpci", "-s",
                         busno, "BASE_ADDRESS_0.L"],
                        stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                    if proc.returncode == 0 and len(proc.stderr) == 0:
                        self.__bar = int(proc.stdout, 16)

    def __del__(self):
        pass

    def exist(self):
        if self.__bar is not False:
            return True
        else:
            return False

    def read(self, _offset, _length):
        if self.__bar is not False:
            with open('/dev/mem', 'r+b') as f:
                f.seek(self.__bar + _offset, 0)
                return int.from_bytes(f.read(_length), 'little')
        return 0

    def burst_read(self, _offset, _length):
        if self.__bar is not False:
            with open('/dev/mem', 'r+b') as f:
                f.seek(self.__bar + _offset, 0)
                return f.read(_length)
        return 0

    def write(self, _offset, _data):
        if self.__bar is not False:
            with open('/dev/mem', 'w+b') as f:
                page = (self.__bar + _offset) & ~0xfff
                page_offset = (self.__bar + _offset) & 0xfff
                mm = mmap.mmap(f.fileno(), 4096, offset=page)
                if page_offset > 0:
                    mm.seek(page_offset)
                mm.write(_data)
                mm.close()
        return


class CtrlDmacMem():
    def __init__(self, fpga_name):
        if fpga_name in PCI_BUS:
            self.__dev_file = PCI_BUS[fpga_name]["dev_file"]
        else:
            self.__pci_bus = ""

    def __del__(self):
        pass

    def read_block(self, offset):
        block = []
        with open(self.__dev_file, "r+b", buffering=0) as f:
            page = offset & ~0xfff
            page_offset = offset & 0xfff
            mm = mmap.mmap(f.fileno(), 4096, offset=page)
            mm.seek(page_offset)

            split_int_num = int((4096 - page_offset)/4)
            if split_int_num < 65:
                for i in range(split_int_num):
                    temp = mm.read(4)
                    block.append(int.from_bytes(temp, 'little'))
                offset += 4096 - page_offset
                remain_int_num = 65 - split_int_num

                page = offset & ~0xfff
                page_offset = offset & 0xfff
                mm = mmap.mmap(f.fileno(), 4096, offset=page)
                mm.seek(page_offset)
                for i in range(remain_int_num):
                    temp = mm.read(4)
                    block.append(int.from_bytes(temp, 'little'))

            else:
                for i in range(65):
                    temp = mm.read(4)
                    block.append(int.from_bytes(temp, 'little'))

            return block


class CtrlDmacMemBurstRead(CtrlDmacMem):
    def __init__(self, name, offset):
        super().__init__(name)
        self.__rp = offset

    def __del__(self):
        pass

    def read_block(self):
        int_list = super().read_block(self.__rp)
        self.__rp += len(int_list) * 4
        return int_list


class BinFile():
    def __init__(self, name):
        self.__file_name = os.path.dirname(os.path.abspath(
            __file__)) + '/test_bin_' + name + '.bin'
        if os.path.isfile(self.__file_name) is False:
            self.__initialize()

    def __del__(self):
        pass

    def read(self, offset, length):
        with open(self.__file_name, "r+b", buffering=0) as f:
            mm = mmap.mmap(f.fileno(), 0)
            mm.seek(offset)
            data = mm.read(length)
            mm.close()
            return data
        return 0

    def read_block(self, offset):
        block = []
        with open(self.__file_name, "r+b", buffering=0) as f:
            mm = mmap.mmap(f.fileno(), 0)
            mm.seek(offset)
            for i in range(65):
                temp = mm.read(4)
                block.append(int.from_bytes(temp, 'little'))
        return block

    def write(self, offset, bin_data):
        with open(self.__file_name, "a+b") as f:
            mm = mmap.mmap(f.fileno(), 16 * 1024)
            mm.seek(offset)
            mm.write(bin_data)
            mm.close()

    def write_block(self, offset, int_list):
        with open(self.__file_name, "a+b") as f:
            mm = mmap.mmap(f.fileno(), 16 * 1024)
            mm.seek(offset)
            for data in int_list:
                temp = int(data, 16)
                mm.write(temp.to_bytes(4, 'little'))
            mm.close()

    def __initialize(self):
        with open(self.__file_name, mode='w+b') as f:
            for i in range(16 * 256):
                f.write(struct.pack("l", 0))


class BinFileBurstRead(BinFile):
    def __init__(self, name, offset):
        super().__init__(name)
        self.__rp = offset

    def __del__(self):
        pass

    def read(self, length):
        result, data = super().read(self.__rp, length)
        if result is True:
            self.__rp += length
            return data
        else:
            return 0

    def read_block(self):
        int_list = super().read_block(self.__rp)
        self.__rp += len(int_list) * 4
        return int_list


class BinFileBurstWrite(BinFile):
    def __init__(self, name, offset):
        super().__init__(name)
        self.__wp = offset

    def __del__(self):
        pass

    def write(self, data):
        super().write(self.__wp, data)
        self.__wp += len(data)

    def write_block(self, int_list):
        super().write_block(self.__wp, int_list)
        self.__wp += len(int_list) * 4


class SyncVD():
    def __init__(self, debug):
        self.__debug = debug
        if not self.__debug:
            self.__intr_cnt = 0
            self.__fpga_driver = SonyFpgaDriver("GIF")
        else:
            self.__intr_cnt = 0
            self.__fpga_driver = False
            signal.signal(signal.SIGALRM, self.__task)
            sec = 0.016683 / 4
            signal.setitimer(signal.ITIMER_REAL, sec, sec)

    def __task(self, arg1, arg2):
        self.__intr_cnt = self.__intr_cnt + 1

    def wait_vd_intr(self):
        if not self.__debug:
            for i in range(4):
                ret, cnt = self.__fpga_driver.IOCTL_SONY_FPGA_WAIT_VD_INFO()
                if ret is True and cnt % 4 == 0:
                    return True
        else:
            while self.__intr_cnt <= 3:
                continue
            self.__intr_cnt = 0
            return True

        return False

    def __del__(self):
        pass
