from __future__ import print_function
'''
# Copyright (C) 2015, Elphel.inc.
# Parsing Verilog parameters from the header files
# 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/>.

@author:     Andrey Filippov
@copyright:  2015 Elphel, Inc.
@license:    GPLv3.0+
@contact:    andrey@elphel.coml
@deffield    updated: Updated
'''
__author__ = "Andrey Filippov"
__copyright__ = "Copyright 2015, Elphel, Inc."
__license__ = "GPL"
__version__ = "3.0+"
__maintainer__ = "Andrey Filippov"
__email__ = "andrey@elphel.com"
__status__ = "Development"

import os
#from import_verilog_parameters import VerilogParameters
from x393_mem import X393Mem
#from verilog_utils import hx,concat, bits 
#from verilog_utils import hx
#from subprocess import call
from time import sleep
import vrlg # global parameters
import x393_axi_control_status
import shutil
DEFAULT_BITFILE="/usr/local/verilog/x393.bit"
#DEFAULT_BITFILE="/tmp/x393.bit"
FPGA_RST_CTRL =    0xf8000240
FPGA0_THR_CTRL =   0xf8000178
FPGA_LVL_SHFTR =   0xf8000900 # 0xf: all enabled, 0x0 - disable all
FPGA_DEVCFG_CTRL = 0xf8007000 # &= (1 << 30) - reset


FPGA_LOAD_BITSTREAM="/dev/xdevcfg"
INT_STS=       0xf800700c
#SAVE_FILE_NAME="Some_name"# None
class X393Utils(object):
#    global SAVE_FILE_NAME
    DRY_MODE= True # True
    DEBUG_MODE=1
#    vpars=None
    x393_mem=None
#    enabled_channels=0 # currently enabled channels
    saveFileName=None
    x393_axi_tasks=None
#    verbose=1
    def __init__(self, debug_mode=1,dry_mode=True ,saveFileName=None):
        self.DEBUG_MODE=debug_mode
        self.DRY_MODE=dry_mode
        if saveFileName:
            self.saveFileName=saveFileName.strip()
        self.x393_mem=X393Mem(debug_mode,dry_mode)
#        self.x393_axi_tasks=X393AxiControlStatus(debug_mode,dry_mode)
        self.x393_axi_tasks=x393_axi_control_status.X393AxiControlStatus(debug_mode,dry_mode)
#        self.__dict__.update(VerilogParameters.__dict__["_VerilogParameters__shared_state"]) # Add verilog parameters to the class namespace
    def reset_get(self):
        """
        Get current reset state
        """
        return self.x393_mem.read_mem(FPGA_RST_CTRL)
    def reset_once(self):
        """
        Pulse reset ON, then OFF
        """
        self.reset((0,0xa))
    def reset(self,data):
        """
        Write data to FPGA_RST_CTRL register
        <data> currently data=1 - reset on, data=0 - reset on
               data can also be a list/tuple of integers, then it will be applied
               in sequence (0,0xe) will turn reset on, then off
        """
        if isinstance(data, (int,long)):
            self.x393_mem.write_mem(FPGA_RST_CTRL,data)
        else:
            for d in data:
                self.x393_mem.write_mem(FPGA_RST_CTRL,d)
                
    def fpga_shutdown(self,
                      quiet = 1):
        if quiet < 2:
            print ("fpga_shutdown(): FPGA clock OFF")
        self.x393_mem.write_mem(FPGA0_THR_CTRL,1)
        if quiet < 2:
            print ("fpga_shutdown(): Reset ON")
        self.reset(0)
        if quiet < 2:
            print ("fpga_shutdown(): Disabling level shifters")
        # turn off level shifters
        self.x393_mem.write_mem(FPGA_LVL_SHFTR,0)
        old_devcfg_ctrl = self.x393_mem.read_mem(FPGA_DEVCFG_CTRL)
        if quiet < 2:
            print ("fpga_shutdown():FPGA_DEVCFG_CTRL was 0x%08x"%(old_devcfg_ctrl))
            print ("fpga_shutdown(): Applying PROG_B")
        self.x393_mem.write_mem(FPGA_DEVCFG_CTRL,old_devcfg_ctrl & ~(1 << 30))
                    
    def bitstream_get_path(self):
        return DEFAULT_BITFILE
    
    def bitstream_set_path(self, bitfile):
        global DEFAULT_BITFILE
        DEFAULT_BITFILE = bitfile
                    
    def bitstream(self,
                  bitfile=None,
                  quiet=1):
        """
        Turn FPGA clock OFF, reset ON, load bitfile, turn clock ON and reset OFF
        @param bitfile path to bitfile if provided, otherwise default bitfile will be used
        @param quiet Reduce output
        """
        if bitfile is None:
            bitfile=DEFAULT_BITFILE
        print ("Sensor ports power off")
#        POWER393_PATH = '/sys/devices/elphel393-pwr.1'
        POWER393_PATH = '/sys/devices/soc0/elphel393-pwr@0'
        
#        with open (POWER393_PATH + "/channels_dis","w") as f:
#            print("vcc_sens01 vp33sens01 vcc_sens23 vp33sens23", file = f)
        print ("FPGA clock OFF")
        self.x393_mem.write_mem(FPGA0_THR_CTRL,1)
        print ("Reset ON")
        self.reset(0)
        print ("cat %s >%s"%(bitfile,FPGA_LOAD_BITSTREAM))
        if not self.DRY_MODE:
            l=0
            with open(bitfile, 'rb') as src, open(FPGA_LOAD_BITSTREAM, 'wb') as dst:
                buffer_size=1024*1024
                while True:
                    copy_buffer=src.read(buffer_size)
                    if not copy_buffer:
                        break
                    dst.write(copy_buffer)
                    l+=len(copy_buffer)
                    if quiet < 4 :
                        print("sent %d bytes to FPGA"%l)                            

            print("Loaded %d bytes to FPGA"%l)                            
#            call(("cat",bitfile,">"+FPGA_LOAD_BITSTREAM))
        if quiet < 4 :
            print("Wait for DONE")
        if not self.DRY_MODE:
            for _ in range(100):
                if (self.x393_mem.read_mem(INT_STS) & 4) != 0:
                    break
                sleep(0.1)
            else:
                print("Timeout waiting for DONE, [0x%x]=0x%x"%(INT_STS,self.x393_mem.read_mem(INT_STS)))
                return
        if quiet < 4 :
            print ("FPGA clock ON")
        self.x393_mem.write_mem(FPGA0_THR_CTRL,0)
        if quiet < 4 :
            print ("Reset OFF")
        self.reset(0xa)
        self.x393_axi_tasks.init_state()
    
    def exp_gpio (self,
                  mode="in",
                  gpio_low=54,
                  gpio_high=None):
        """
        Export GPIO pins connected to PL (full range is 54..117)
        <mode>     GPIO mode: "in" or "out"
        <gpio_low> lowest GPIO to export     
        <gpio_hi>  Highest GPIO to export. Set to <gpio_low> if not provided     
        """
        if gpio_high is None:
            gpio_high=gpio_low
        print ("Exporting as \""+mode+"\":", end=""),    
        for gpio_n in range (gpio_low, gpio_high + 1):
            print (" %d"%gpio_n, end="")
        print() 
        if not self.DRY_MODE:
            for gpio in range (gpio_low, gpio_high + 1):
                try:
                    with open ("/sys/class/gpio/export","w") as f:
                        print (gpio,file=f)
                except:
                    print ("failed \"echo %d > /sys/class/gpio/export"%gpio)
                try:
                    with open ("/sys/class/gpio/gpio%d/direction"%gpio,"w") as f:
                        print (mode,file=f)
                except:
                    print ("failed \"echo %s > /sys/class/gpio/gpio%d/direction"%(mode,gpio))

    def mon_gpio (self,
                  gpio_low=54,
                  gpio_high=None):
        """
        Get state of the GPIO pins connected to PL (full range is 54..117)
        <gpio_low> lowest GPIO to export     
        <gpio_hi>  Highest GPIO to export. Set to <gpio_low> if not provided
        Returns data as list of 0,1 or None    
        """
        if gpio_high is None:
            gpio_high=gpio_low
        print ("gpio %d.%d: "%(gpio_high,gpio_low), end="")
        d=[]
        for gpio in range (gpio_high, gpio_low-1,-1):
            if gpio != gpio_high and ((gpio-gpio_low+1) % 4) == 0:
                print (".",end="")
            if not self.DRY_MODE:
                try:
                    with open ("/sys/class/gpio/gpio%d/value"%gpio,"r") as f:
                        b=int(f.read(1))
                        print ("%d"%b,end="")
                        d.append(b)
                except:
                    print ("X",end="")
                    d.append(None)
            else:
                print ("X",end="")
                d.append(None)
        print()
        return d
    
    def getParTmpl(self):
        return ({"name":"DLY_LANE0_ODELAY", "width": 80, "decl_width":"","disable":False}, # decl_width can be "[7:0]", "integer", etc
                {"name":"DLY_LANE0_IDELAY", "width": 72, "decl_width":"","disable":False},
                {"name":"DLY_LANE1_ODELAY", "width": 80, "decl_width":"","disable":False},
                {"name":"DLY_LANE1_IDELAY", "width": 72, "decl_width":"","disable":False},
                {"name":"DLY_CMDA",         "width":256, "decl_width":"","disable":False},
                {"name":"DLY_PHASE",        "width": 8,  "decl_width":"","disable":False},
                {"name":"DFLT_WBUF_DELAY",  "width": 4,  "decl_width":"","disable":True},
                {"name":"DFLT_WSEL",        "width": 1,  "decl_width":"","disable":True},
                {"name":"DFLT_RSEL",        "width": 1,  "decl_width":"","disable":True},
                )
 
    def localparams(self,
                    quiet=False):
        """
        Generate verilog include file with localparam definitions for the DDR3 timing parameters
        Returns definition as a string
        """
        nameLen=0
        declWidth=0
        for p in self.getParTmpl(): #parTmpl:
            nameLen=max(nameLen,len(p['name']))
            declWidth=max(declWidth,len(p['decl_width']))
        txt=""
        for p in self.getParTmpl(): # parTmpl:
            numDigits = (p["width"]+3)/4
            frmt="localparam %%%ds %%%ds %3d'h%%0%dx;\n"%(declWidth,nameLen+2,p["width"],numDigits)
            try:
                pv=vrlg.__dict__[p['name']]
                if p['disable']:
                    txt += '// '
                txt+=frmt%(p['decl_width'],p['name']+" =",pv)
            except: # parameter does not exist
                pass
        if not quiet:
            print (txt)
        return txt
    
    def save_defaults(self,
                      allPars=False):
        """
        Save current parameter values to defaults (as read at start up)
        <allPars>  use all parameters, if false - only for the ones used in
                   'save' file  
        """
#        global parTmpl
        if allPars:
            vrlg.save_default()
        else:
            for par in self.getParTmpl(): # parTmpl:
                vrlg.save_default(par['name'])
            
    def restore_defaults(self,
                         allPars=False):
        """
        Restore parameter values from defaults (as read at start up)
        <allPars>  use all parameters, if false - only for the ones used in
                   'save' file  
        """
        global parTmpl
        if allPars:
            vrlg.restore_default()
        else:
            for par in parTmpl:
                vrlg.restore_default(par['name'])
    
    def save(self,
                    fileName=None):
        """
        Write Verilog include file with localparam definitions for the DDR3 timing parameters
        Also copies the same parameter values to defaults
        <fileName> - optional path to write, pre-defined name if not specified
        """
        header= """/* This is a generated file with the current DDR3 memory timing parameters */

"""
        self.save_defaults(False) # copy current parameters to defaults
        if not fileName:
            fileName=self.saveFileName
        txt=self.localparams(True) #quiet

        if fileName:
            try:
                with open(fileName, "w") as text_file:
                    text_file.write(header)
                    text_file.write(txt)
                    print ("Verilog parameters are written to  %s"%(os.path.abspath(fileName)))

            except:
                print ("Failed to write to %s"%(os.path.abspath(fileName)))
        else:
            print(txt)   
    def copy (self,
              src,
              dst):
        """
        Copy files in the file system
        @param src - source path
        @param dst - destination path/directory
        """
        shutil.copy2(src, dst)