from __future__ import division
from __future__ import print_function

'''
# Copyright (C) 2015, Elphel.inc.
# Class to control 10353 GPIO port  
# 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 sys
#import pickle
from x393_mem                import X393Mem
import x393_axi_control_status
import x393_sens_cmprs
import x393_utils

import time
import vrlg
class X393CmprsAfi(object):
    DRY_MODE= True # True
    DEBUG_MODE=1
    x393_mem=None
    x393_axi_tasks=None #x393X393AxiControlStatus
    x393_utils=None
    verbose=1
    def __init__(self, debug_mode=1,dry_mode=True, saveFileName=None):
        self.DEBUG_MODE=  debug_mode
        self.DRY_MODE=    dry_mode
        self.x393_mem=            X393Mem(debug_mode,dry_mode)
        self.x393_axi_tasks=      x393_axi_control_status.X393AxiControlStatus(debug_mode,dry_mode)
        self.x393_utils=          x393_utils.X393Utils(debug_mode,dry_mode, saveFileName) # should not overwrite save file path
        try:
            self.verbose=vrlg.VERBOSE
        except:
            pass
    def afi_mux_program_status (self,
                                port_afi,
                                chn_afi,
                                mode,     # input [1:0] mode;
                                seq_num): # input [5:0] seq_num;
        """
        Set status generation mode for AXI HP multiplexer for compressed data
        @param port_afi - number of AFI port (0 - afi 1, 1 - afi2)
                          configuration controlled by the code. currently both AFI are used: ch0 - cmprs_afi_mux_1.0, ch1 - cmprs_afi_mux_1.1,
                          ch2 - cmprs_afi_mux_2.0, ch3 - cmprs_afi_mux_2.
                          May be changed to (actually already done) ch0 - cmprs_afi_mux_1.0, ch1 -cmprs_afi_mux_1.1,
                          ch2 - cmprs_afi_mux_1.2, ch3 - cmprs_afi_mux_1.3
        @param chn_afi - numer of the afi_mux input port (0..3)
        @param mode -       status generation mode:
                                  0: disable status generation,
                                  1: single status request,
                                  2: auto status, keep specified seq number,
                                  4: auto, inc sequence number 
        @param seq_number - 6-bit sequence number of the status message to be sent
        
        """
        self.x393_axi_tasks.program_status (
                        vrlg.CMPRS_GROUP_ADDR + (vrlg.CMPRS_AFIMUX_RADDR0,vrlg.CMPRS_AFIMUX_RADDR1)[port_afi] + chn_afi,
                        vrlg.CMPRS_AFIMUX_STATUS_CNTRL,
                        mode,
                        seq_num)
    def afi_mux_get_image_pointer(self,
                          port_afi,
                          channel):
        """
        Returns image pointer in bytes inside the circbuf. Status autoupdate should be already set up, mode = 0
        @param port_afi - AFI port (0/1), currently only 0
        @param channel - AFI input channel (0..3) - with 2 AFIs - 0..1 only
        @return - displacement to current image pointer in bytes
        """
#        print ("status reg =      0x%x"%(((vrlg.CMPRS_AFIMUX_REG_ADDR0, vrlg.CMPRS_AFIMUX_REG_ADDR1)[port_afi]+channel) ))
#        print ("status reg data = 0x%x"%(self.x393_axi_tasks.read_status((vrlg.CMPRS_AFIMUX_REG_ADDR0, vrlg.CMPRS_AFIMUX_REG_ADDR1)[port_afi]+channel) ))
        if (self.DRY_MODE):
            return None
        return 32*(self.x393_axi_tasks.read_status((vrlg.CMPRS_AFIMUX_REG_ADDR0, vrlg.CMPRS_AFIMUX_REG_ADDR1)[port_afi]+channel) & 0x3ffffff)
    
    def afi_mux_get_image_meta(self,
                          port_afi,
                          channel,
                          cirbuf_start = 0x27a00000,
                          circbuf_len =  0x1000000,
                          verbose = 1,
                          num_lines_print = 20):
        """
        Returns image metadata (start, length,timestamp) or null
        @param port_afi - AFI port (0/1), currently only 0
        @param channel - AFI input channel (0..3) - with 2 AFIs - 0..1 only
        @return - memory segments (1 or two) with image data, timestamp in numeric and string format
        """
        print ("\n------------ channel %d --------------"%(channel))
        print ("x393_sens_cmprs.GLBL_WINDOW = ", x393_sens_cmprs.GLBL_WINDOW)
        if (self.DRY_MODE):
            return None
        CCAM_MMAP_META        = 12 # extra bytes included at the end of each frame (last aligned to 32 bytes)
        CCAM_MMAP_META_LENGTH =  4 # displacement to length frame length data from the end of the 32-byte aligned frame slot
        CCAM_MMAP_META_USEC   =  8 # // (negative) displacement to USEC data - 20 bits (frame timestamp)
        CCAM_MMAP_META_SEC    = 12 # // (negative) displacement to SEC data - 32 bits (frame timestamp)
        
        
#        offs_len32 = 0x20 - CCAM_MMAP_META_LENGTH # 0x1c #from last image 32-byte chunk to lower of 3-byte image length (MSB == 0xff)
        next_image = self.afi_mux_get_image_pointer(port_afi = port_afi,
                                                    channel = channel)
        last_image_chunk = next_image - 0x40
        if last_image_chunk < 0:
            last_image_chunk += circbuf_len
        len32 = self.x393_mem.read_mem(cirbuf_start + last_image_chunk + (0x20 - CCAM_MMAP_META_LENGTH))
        markerFF = len32 >> 24
        if (markerFF != 0xff):
            print ("Failed to get 0xff marker at offset 0x%08x - length word = 0x%08x)"%(cirbuf_start + last_image_chunk + (0x20 - CCAM_MMAP_META_LENGTH) + 3,len32))
            return None
        len32 &= 0xffffff
#        inserted_bytes = (32 - (((len32 % 32) + CCAM_MMAP_META) % 32)) % 32
        #adjusting to actual...
#        ADJUSTMENT = 2
        ADJUSTMENT = 4 # ???
        inserted_bytes = ((32 - (((len32 % 32) + CCAM_MMAP_META) % 32) - ADJUSTMENT) % 32 ) + ADJUSTMENT
        img_start = last_image_chunk + 32 - CCAM_MMAP_META  - inserted_bytes - len32
        if img_start < 0:
            img_start += circbuf_len
        if verbose >0:
            for a in range ( img_start,  img_start + (0x10 * num_lines_print), 4):
                d = self.x393_mem.read_mem(cirbuf_start + a)
                if (a % 16) == 0:
                    print ("\n%08x: "%(a),end ="" )
                print("%02x %02x %02x %02x "%(d & 0xff, (d >> 8) & 0xff, (d >> 16) & 0xff, (d >> 24) & 0xff), end = "")
            print("\n...",end="")
            for a0 in range ( last_image_chunk - (0x10 * num_lines_print),  last_image_chunk + 0x20, 4):
                a = a0
                if (a < 0):
                    a -=circbuf_len
                d = self.x393_mem.read_mem(cirbuf_start + a)
                if (a % 16) == 0:
                    print ("\n%08x: "%(a),end ="" )
                print("%02x %02x %02x %02x "%(d & 0xff, (d >> 8) & 0xff, (d >> 16) & 0xff, (d >> 24) & 0xff), end = "")
            print()    

        sec  = self.x393_mem.read_mem(cirbuf_start + last_image_chunk + (0x20 - CCAM_MMAP_META_SEC))
        usec = self.x393_mem.read_mem(cirbuf_start + last_image_chunk + (0x20 - CCAM_MMAP_META_USEC))
        fsec=sec + usec/1000000.0
        try:
            tstr = time.strftime("%b %d %Y %H:%M:%S", time.gmtime(fsec))
        except:
            tstr = "%f (0x%x, 0x%x)"%(fsec, sec,usec)
            print ("**** Bad timestamp = ",tstr)
        segments = ((cirbuf_start + img_start, len32 ),)    
        if (img_start + len32) > circbuf_len: # split in two segments
            segments = ((cirbuf_start + img_start, circbuf_len - img_start),
                        (cirbuf_start, len32 - (circbuf_len - img_start)))
        result = {"timestamp": fsec,
                  "timestring": tstr,
                  "segments":segments}
        if verbose >0 :
            print ("Inserted bytes after image before meta = 0x%x"%(inserted_bytes))
            print ("Image start (relative to cirbuf) = 0x%x"%(img_start))
            print ("Image time stamp = %s (%f)"%(tstr, fsec))
            for s in segments:
                print ("start_address = 0x%x, length = 0x%x"%(s[0],s[1]))
        return result    
        
        
    """
>>> hex (0x3dacb * 32)
'0x7b5960'
>>> hex (0x3dacb * 32 + 0x27a00000)
'0x281b5960'
    
    
print time.strftime("%b %d %Y %H:%M:%S", time.gmtime(1442344402.605793))

#define CCAM_MMAP_META 12 // extra bytes included at the end of each frame (last aligned to 32 bytes)
#define CCAM_MMAP_META_LENGTH 4 // displacement to length frame length data from the end of the 32-byte aligned frame slot
#define CCAM_MMAP_META_USEC 8 // (negative) displacement to USEC data - 20 bits (frame timestamp)
#define CCAM_MMAP_META_SEC 12 // (negative) displacement to SEC data - 32 bits (frame timestamp)

**
 * @brief Locate area between frames in the circular buffer
 * @return pointer to interframe parameters structure
 */
inline struct interframe_params_t* updateIRQ_interframe(void) {
   int circbuf_size=get_globalParam (G_CIRCBUFSIZE)>>2;
   int alen = JPEG_wp-9; if (alen<0) alen+=circbuf_size;
   int jpeg_len=ccam_dma_buf_ptr[alen] & 0xffffff;
   set_globalParam(G_FRAME_SIZE,jpeg_len);
   int aframe_params=(alen & 0xfffffff8)-
                     (((jpeg_len + CCAM_MMAP_META + 3) & 0xffffffe0)>>2) /// multiple of 32-byte chunks to subtract
                     -8; /// size of the storage area to be filled before the frame
   if(aframe_params < 0) aframe_params += circbuf_size;
   struct interframe_params_t* interframe= (struct interframe_params_t*) &ccam_dma_buf_ptr[aframe_params];
/// should we use memcpy as before here?
   interframe->frame_length=jpeg_len;
   interframe->signffff=0xffff;
#if ELPHEL_DEBUG_THIS
    set_globalParam          (0x306,get_globalParam (0x306)+1);
#endif

   return interframe;
}

/**
 * @brief Fill exif data with the current frame data, save pointer to Exif page in the interframe area

    """

#    def read_mem (self,addr,quiet=1):
#        '''
#        Read 32-bit word from physical memory
#        @param addr  physical byte address
#        @param quiet - reduce output
#self.x393_mem

#0x27a00000
#    parameter CMPRS_AFIMUX_REG_ADDR0=     'h18,  // Uses 4 locations
#    parameter CMPRS_AFIMUX_REG_ADDR1=     'h1c,  // Uses 4 locations

    def afi_mux_reset (self,
                       port_afi,
                       rst_chn):
        """
        Reset selected input channels of selected AFI multiplexer               
        @param port_afi - number of AFI port (0 - afi 1, 1 - afi2)
        @param rst_chn  - bit mask of channels to reset (persistent, needs release)
        """
        self.x393_axi_tasks.write_control_register(
                    vrlg.CMPRS_GROUP_ADDR + (vrlg.CMPRS_AFIMUX_RADDR0,vrlg.CMPRS_AFIMUX_RADDR1)[port_afi] + vrlg.CMPRS_AFIMUX_RST,
                    rst_chn)
    def  afi_mux_enable_chn (self,
                             port_afi,
                             en_chn,
                             en):
        """
        Enable/disable selected input channel of the selecte AFI multiplexer 
        @param port_afi - number of AFI port (0 - afi 1, 1 - afi2)
        @param en_chn  -  number of afi input channel to enable/disable (0..3)
        @param en  -      number enable (True) or disable (False) selected AFI input
        """ 
        self.x393_axi_tasks.write_control_register(
                    vrlg.CMPRS_GROUP_ADDR + (vrlg.CMPRS_AFIMUX_RADDR0,vrlg.CMPRS_AFIMUX_RADDR1)[port_afi] + vrlg.CMPRS_AFIMUX_EN,
                    (2,3)[en] << (2 * en_chn))
              
    def  afi_mux_enable (self,
                         port_afi,
                         en):
        """
        Enable/disable selected AFI multiplexer
        @param port_afi - number of AFI port (0 - afi 1, 1 - afi2)
        @param en_chn  -  number of afi input channel to enable/disable (0..3)
        @param en  -      number enable (True) or disable (False) selected AFI input
        """ 
        self.x393_axi_tasks.write_control_register(
                    vrlg.CMPRS_GROUP_ADDR + (vrlg.CMPRS_AFIMUX_RADDR0,vrlg.CMPRS_AFIMUX_RADDR1)[port_afi] + vrlg.CMPRS_AFIMUX_EN,
                    (2,3)[en] << (2 * 4))

    def afi_mux_mode_chn (self,
                          port_afi,
                          chn,
                          mode):
        """
        Set mode of selected input channel of the selected AFI multiplexer
        @param port_afi - number of AFI port (0 - afi 1, 1 - afi2)
        @param chn  -     number of afi input channel to program
        @param mode  -    readback mode:
                            mode == 0 - show EOF pointer, internal
                            mode == 1 - show EOF pointer, confirmed written to the system memory
                            mode == 2 - show current pointer, internal
                            mode == 3 - show current pointer, confirmed written to the system memory
        """ 
        self.x393_axi_tasks.write_control_register(
                    vrlg.CMPRS_GROUP_ADDR + (vrlg.CMPRS_AFIMUX_RADDR0,vrlg.CMPRS_AFIMUX_RADDR1)[port_afi] + vrlg.CMPRS_AFIMUX_MODE,
                    (4 + (mode & 3)) << (4 * chn))

    def afi_mux_chn_start_length(self,
                                 port_afi,
                                 chn,
                                 sa,
                                 length):
        """
        Set mode of selected input channel of the selected AFI multiplexer
        @param port_afi - number of AFI port (0 - afi 1, 1 - afi2)
        @param chn  -     number of afi input channel to program
        @param sa  -      start address in 32-byte chunks
        @param length  -  channel buffer length in 32-byte chunks
        """
        reg_addr =  vrlg.CMPRS_GROUP_ADDR + (vrlg.CMPRS_AFIMUX_RADDR0,vrlg.CMPRS_AFIMUX_RADDR1)[port_afi] + vrlg.CMPRS_AFIMUX_SA_LEN + chn
        self.x393_axi_tasks.write_control_register(
                    reg_addr,
                    sa)
        self.x393_axi_tasks.write_control_register(
                    reg_addr + 4,
                    length)
        
    def afi_mux_setup (self,
                       port_afi,
                       chn_mask,
                       status_mode, # = 3,
                       report_mode, # = 0,
                       afi_cmprs0_sa,
                       afi_cmprs0_len,
                       afi_cmprs1_sa,
                       afi_cmprs1_len,
                       afi_cmprs2_sa,
                       afi_cmprs2_len,
                       afi_cmprs3_sa,
                       afi_cmprs3_len,
                       verbose = 1):    

        """
        Set mode of selected input channel of the selected AFI multiplexer
        @param port_afi -       number of AFI port (0 - afi 1, 1 - afi2)
        @param chn  -           number of afi input channel to program
        @param status_mode -    status mode (3 for auto)
        @param report_mode  -    readback mode:
                            mode == 0 - show EOF pointer, internal
                            mode == 1 - show EOF pointer, confirmed written to the system memory
                            mode == 2 - show current pointer, internal
                            mode == 3 - show current pointer, confirmed written to the system memory
        @param afi_cmprs0_sa -  input channel 0 start address in 32-byte chunks
        @param afi_cmprs0_len - input channel 0 buffer length in 32-byte chunks
        @param afi_cmprs1_sa -  input channel 0 start address in 32-byte chunks
        @param afi_cmprs1_len - input channel 0 buffer length in 32-byte chunks
        @param afi_cmprs2_sa -  input channel 0 start address in 32-byte chunks
        @param afi_cmprs2_len - input channel 0 buffer length in 32-byte chunks
        @param afi_cmprs3_sa -  input channel 0 start address in 32-byte chunks
        @param afi_cmprs3_len - input channel 0 buffer length in 32-byte chunks
        @param verbose - verbose level
        """
        if verbose >0 :
            print ("afi_mux_setup:")
            print ("AFI port (0/1) =   ",port_afi)
            print ("AFI channel mask = 0x%x"%(chn_mask))
            print ("status mode =      ",status_mode)
            print ("report mode =      ",report_mode)
            
            print ("channel 0 :        0x%08x/0x%08x"%(afi_cmprs0_sa * 32, afi_cmprs0_len * 32))
            print ("channel 1 :        0x%08x/0x%08x"%(afi_cmprs1_sa * 32, afi_cmprs1_len * 32))
            print ("channel 2 :        0x%08x/0x%08x"%(afi_cmprs2_sa * 32, afi_cmprs2_len * 32))
            print ("channel 3 :        0x%08x/0x%08x"%(afi_cmprs3_sa * 32, afi_cmprs3_len * 32))
        
        sa =     (afi_cmprs0_sa,  afi_cmprs1_sa,  afi_cmprs2_sa,  afi_cmprs3_sa)
        length = (afi_cmprs0_len, afi_cmprs1_len, afi_cmprs2_len, afi_cmprs3_len)
        for i in range(4):
            if (chn_mask >> i) & 1 :
                self.afi_mux_program_status (port_afi = port_afi,
                                             chn_afi = i,
                                             mode =status_mode,
                                             seq_num = 0)
                
        # reset all channels    
        self.afi_mux_reset( port_afi = port_afi,
                            rst_chn = 0xf) # reset all channels
        # release resets
        self.afi_mux_reset( port_afi = port_afi,
                            rst_chn =  0) # release reset on all channels
            
        # set report mode (pointer type) - per status    
        for i in range(4):
            if (chn_mask >> i) & 1 :
                self.afi_mux_mode_chn (port_afi = port_afi,
                                       chn = i,
                                       mode = report_mode)
        for i in range(4):
            if (not sa[i] is None) and (not length[i] is None):
                self.afi_mux_chn_start_length (port_afi = port_afi,
                                               chn =      i,
                                               sa =       sa[i],
                                               length =   length[i])

        # enable selected channels        
        for i in range(4):
            if (chn_mask >> i) & 1 :
                self.afi_mux_enable_chn (port_afi = port_afi,
                                         en_chn =   i,
                                         en =       True)

        # enable the whole afi_mux module
    
        self.afi_mux_enable (port_afi = port_afi,
                             en =       True)

