##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2013-2016 Uwe Hermann <uwe@hermann-uwe.de>
##
## 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 2 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/>.
##

###Always imported
import sigrokdecode as srd

###Maybe needed. Not really clear what common.srdhelper provides, but bitpacking is used
from common.srdhelper import bitpack

###Not at all needed, but provides logging capability for debugging purposes
import logging


'''
OUTPUT_PYTHON format:

Packet:
[<ptype>, <pdata>]

<ptype>, <pdata>
 - 'ITEM', [<item>, <itembitsize>]
 - 'WORD', [<word>, <wordbitsize>, <worditemcount>]

<item>:
 - A single item (a number). It can be of arbitrary size. The max. number
   of bits in this item is specified in <itembitsize>.

<itembitsize>:
 - The size of an item (in bits). For a 4-bit parallel bus this is 4,
   for a 16-bit parallel bus this is 16, and so on.

<word>:
 - A single word (a number). It can be of arbitrary size. The max. number
   of bits in this word is specified in <wordbitsize>. The (exact) number
   of items in this word is specified in <worditemcount>.

<wordbitsize>:
 - The size of a word (in bits). For a 2-item word with 8-bit items
   <wordbitsize> is 16, for a 3-item word with 4-bit items <wordbitsize>
   is 12, and so on.

<worditemcount>:
 - The size of a word (in number of items). For a 4-item word (no matter
   how many bits each item consists of) <worditemcount> is 4, for a 7-item
   word <worditemcount> is 7, and so on.
'''


def crash_course(nada):

    ###alternative ways of for loop to fill result with [1, 3, 5, ..., 999]
    result = [i for i in range(1,999,2)]
    ###or
    result = []
    for i in range(1e5)[1:999:2]:
        result.append(i)
    ## range(1e5) <=> 0, 1, 2, 3, ... 100000. By adding [1, 999, 2], we select items 1 to 999 with a step of 2
    ## thus we select only 1, 3, 5, 7, ..., 999 out of the (huge) list...

    # this will print 0, 1, 2:
    for i in range(3):
        print(i)
        

    return -1


### Helper function to define the channels. 
# num_data_channels - number of pins for data (Address/Data) to be decoded
# num_enable_channels - number of pins that shall provide the enable state
def channel_list(num_data_channels, num_enable_channels):
    l = []
    for i in range(num_data_channels):
        a = {'id': 'da%d' % i, 'name': 'DA%d' % i, 'desc': 'Data/Address line %d' % i}
        l.append(a)
    for i in range(num_enable_channels):
        a = {'id': 'e%d' % i, 'name': 'E%d' % i, 'desc': 'Enable line %d' % i}
        l.append(a)
    return tuple(l)

### Helper function to define the options. 
# num_enable_channels - number of pins that shall provide the enable state 
def options_list(num_enable_channels):
    l = []
    for i in range(num_enable_channels):
        a = {'id': 'e%d' % i, 'desc': 'Enable state for line %d' % i, 'default': 'h', 'values': ('h', 'l')}
        l.append(a)
    l.append({'id': 'repeat_count_enable', 'desc': 'Repeat count Enable', 'default': 0})
    l.append({'id': 'repeat_count_DataAddress', 'desc': 'Repeat count Data/Address', 'default': 0})
    l.append({'id': 'wordsize', 'desc': 'Word size - >1 will accumulate', 'default': 0})
    l.append({'id': 'endianness', 'desc': 'Endianness', 'default': 'Big', 'values': ('Big', 'Little')})
    l.append({'id': 'logging', 'desc': 'Log debug messages', 'default': 'No', 'values': ('Yes', 'No')})
    l.append({'id': 'log_path', 'desc': 'Log file path', 'default': 'C:/tmp/EPROM_log.txt'})
    return tuple(l)

class ChannelError(Exception):
    pass
    
    
##Global variables 
# This is where number of Data/Address pins (max number)
# and enable pins are defined
NUM_DATA_CHANNELS = 16
NUM_ENABLE_CHANNELS = 4

class Decoder(srd.Decoder):
    api_version = 3
    id = 'EPROM'
    # name and longname are what enters "Decoder selector"
    # name is also the 'tag' in front of the channel 
    name = 'EPROM'
    longname = 'EPROM access'
    #Desc is short form description under "Decoder selector"
    desc = 'EPROM, ROM, RAM parallel bus Memory'
    license = 'gplv2+'
    inputs = ['logic']
    outputs = ['outEprom']
    tags = ['Util']
    optional_channels = channel_list(NUM_DATA_CHANNELS, NUM_ENABLE_CHANNELS)
    options = options_list(NUM_ENABLE_CHANNELS)
    
    ### Items - row always presented (regardless of selected wordsize)
    ###         Will be updated for each selected trigger/wait-event
    ### Words - row presented iff wordsize > 0
    ###         Will be updated each time Items have accumulated to wordsize
    annotations = (
        ('items', 'Items'),
        ('words', 'Words'),
    )
    annotation_rows = (
        ('items', 'Items', (0,)),
        ('words', 'Words', (1,)),
    )


    def __init__(self):
        self.reset()
        

    ### Define the object start variables ###
    def reset(self):
        self.items_count_for_word = 0
        self.saved_item = None
        self.ss_item = self.es_item = None
        self.ss_word = self.es_word = None
        self.first = True
        self.idx_data_channels = []
        self.idx_enable_channels = []
        self.items = []
        
    def start(self):
        self.out_python = self.register(srd.OUTPUT_PYTHON)
        self.out_ann = self.register(srd.OUTPUT_ANN)
        
        if self.options['logging'] == 'Yes':
            logging.basicConfig(filename=self.options['log_path'], level=logging.DEBUG)
            self.log_output('starting logging within EPROM pd.py')
        
    def log_output(self, message):
        if self.options['logging'] == 'Yes':
            logging.debug(message)
        
    def putpb(self, data):
        self.put(self.ss_item, self.es_item, self.out_python, data)

    def putb(self, data):
        self.put(self.ss_item, self.es_item, self.out_ann, data)

    def putpw(self, data):
        self.put(self.ss_word, self.es_word, self.out_python, data)

    def putw(self, data):
        self.log_output('putw, Start:' + str(self.ss_word) + ' End:' + str(self.es_word) + ' Data:' + str(data))
        self.put(self.ss_word, self.es_word, self.out_ann, data)
    
    # handle_bits - This is where an 'item' - value composed of the lines that are to be decoded - is processed further
    # self - self, item - value of combined data bits (trigger pins removed), used_pins - number of bits
    # effectively, input is a value of the data/address bus and the number of bits used
    def handle_bits(self, item, used_pins):
        # At this point we have an 'item' consisting 'used_pins' number of bits. 
        # This item shall be output, but potentially also added
        # to form an accumulated word. 

        # First, output the item - this is always done.
        self.putpb(['ITEM', self.saved_item])
        self.putb([0, [self.fmt_item.format(self.saved_item)]])

        # Next, check if we have something to add to a word.
        # If option wordsize > 0 then time to check
        
        # If this is first time, set counter. This will later be updated each
        # time a word is indeed output.
        if self.first == True:
            self.first = False
            self.items_count_for_word = self.options['wordsize']

        if self.options['wordsize'] > 0:
            if self.items_count_for_word > 0:
                self.items.append(item)
            self.items_count_for_word -= 1
            # Check if we accumulated enough items for a complete word.
            # If so, reset counter and emit word!
            if self.items_count_for_word == 0:
                self.items_count_for_word = self.options['wordsize']
                # Collect items and prepare annotation details
                if self.options['endianness'] == 'Big':
                    self.items.reverse()
                word = 0
                for i in range(self.options['wordsize']):
                    word += self.items[i] << (i * used_pins)
                self.log_output('Output word: ' + self.fmt_word.format(word))
                self.es_word = self.es_item
                self.putw([1, [self.fmt_word.format(word)]])
                self.putpw(['WORD', word])
                self.items = []

 
    # outputIsEnabled - Check if assigned pins for enable meet the criteria
    # Assume that result is True, but if any enable pin fails to meet criteria, return is False
    def outputIsEnabled(self, pins):
        return_value = True
        for i in range(0, NUM_ENABLE_CHANNELS):
            if self.idx_enable_channels[i] is not None:
                #This channel is assigned so check it 
                if (self.options['e%d' % i] != 'h' and pins[self.idx_enable_channels[i]] == 1) or ((self.options['e%d' % i] != 'l' and pins[self.idx_enable_channels[i]] == 0)):
                    return_value = False
        return return_value


    def decode(self):
        debug_message = 'decode(self) called. rep_count:' + str(self.options['repeat_count_enable'])
        self.log_output(debug_message)
    ##### PREPARATION, run once ############
        # Determine which (optional) channels have input data. 
        
        # Figure which data channels that actually are assigned and store these indices
        # in self.idx_data_channels as a list. Not assigned channels will get the id NONE
        # number of (at all) available data channels - they start at 0: NUM_DATA_CHANNELS
        self.idx_data_channels = [
            idx if self.has_channel(idx) else None
            for idx in range(0, NUM_DATA_CHANNELS)
        ]
        
        # idx_assigned_data_channels - list of indices of the channels that are actually assigned by user.
        idx_assigned_data_channels = [idx for idx in self.idx_data_channels if idx is not None]
                
        if not idx_assigned_data_channels:
            raise ChannelError('At least one channel has to be supplied.') #like this would work? ChannelError is empty!!!
        # Get highest index of assigned data/address channel 
        max_connected = max(idx_assigned_data_channels)
        # max_connected - max value in the list (not referring to indices, that would be len-1)
        # Likely highest number since otherwise use 'len'?

        # Figure which enable channels that actually are assigned and store
        # these in self.idx_enable_channels as a list. Not assigned channels will get the id NONE
        # number of (at all) available enable channels - they start at NUM_DATA_CHANNELS 
        # and end at NUM_DATA_CHANNELS+NUM_ENABLE_CHANNELS-1 so...
        self.idx_enable_channels = [
            idx if self.has_channel(idx) else None
            for idx in range(NUM_DATA_CHANNELS, NUM_DATA_CHANNELS+NUM_ENABLE_CHANNELS)
        ]
        
        ### idx_assigned_data_channels - list of indices of the channels that are actually assigned by user.
        idx_assigned_enable_channels = [idx for idx in self.idx_enable_channels if idx is not None]

        if not idx_assigned_enable_channels:
            raise ChannelError('At least one enable pin has to be supplied.') #like this would work? ChannelError is empty!!!




        #### Determine .wait() conditions.####
        # Alternatives:
        # 'l' - low pin value (0)
        # 'h' - high pin value (1)
        # 'r' - rising edge
        # 'f' - falling edge
        # 'e' - either edge (change since previous)
        # 's' - stable, i.e., no edge
        # pins = self.wait({0: 'l', 1: 'h', 2:'l'}) #wait for first three pins entering low high low
        # pins = self.wait({0: 'l', 1: 'h'}, {2:'l'}) #wait for first two pins low-high OR third pin low
        # -OR-
        #
        # skip - enter number of samples to skip, but don't care about pins
        # pins = self.wait({'skip': aCoupleOfSamples})
        #
        ###Can it be that a single 'l' or 'h' is not acceptable for wait???
        ###conds = {0: 'l'} ...doesn't seem to work!
        ###Single transition works
        ###conds = {0: 'r'}
        ###Try more complex alternatives where ___one of the inputs toggles___
        ###conds = {0: 'l', 1: 'h', 2: 'f'} ...this works!
        #Next level, several alternative triggers:
 
        #create combinations of the enable channels where all but
        # one is static at its enable state and one is defined as 
        # transitioning _to_ its enable state ('h' -> 'r', 'l' -> 'f')
        # Channels that aren't assigned should not be part of the condition
        # so skip them
        conds_OUTPUT_ENABLE = []
        for i in range(0, NUM_ENABLE_CHANNELS):
            if self.idx_enable_channels[i] != None:
                #This channel is assigned so create a dict for it 
                d = {}
                for j in range(0, NUM_ENABLE_CHANNELS):
                    if self.idx_enable_channels[j] != None:
                        # This channel is assigned so it's part of the dict
                        if i == j:
                            if self.options['e%d' % j] == 'h':
                                d[self.idx_enable_channels[j]] = 'r'
                            else:
                                d[self.idx_enable_channels[j]] = 'f'
                        else:
                            d[self.idx_enable_channels[j]] = self.options['e%d' % j]
                conds_OUTPUT_ENABLE.append(d)
            
        conds_step1 = conds_OUTPUT_ENABLE
        debug_message = 'START OF ITEM condition: ' + str(conds_OUTPUT_ENABLE)
        self.log_output(debug_message)
    
        #Create a combination to detect where OUTPUT_ENABLE no longer is valid
        #This happens is on or more of the enable pins change state away from the
        #defined enable state. Again, only set this up for the pins that are assigned.
        conds_OUTPUT_DISABLE = []
        for i in range(0, NUM_ENABLE_CHANNELS):
            if self.idx_enable_channels[i] is not None:
                #This channel is assigned so create a dict for it 
                d = {}
                if self.options['e%d' % i] == 'h':
                    d[self.idx_enable_channels[i]] = 'f'
                else:
                    d[self.idx_enable_channels[i]] = 'r'
                conds_OUTPUT_DISABLE.append(d)
            
        #In addition to OUTPUT_ENABLE going invalid, there can also be a change in
        #the data/address bus, indicating new data. Just check for any change 
        #within this set of pins. Again only for the assigned pins.
        conds_ADDRESS_CHANGE = []
        for i in range(0, NUM_DATA_CHANNELS):
            if self.idx_data_channels[i] != None:
                conds_ADDRESS_CHANGE.append({(self.idx_data_channels[i]) : 'e'})
        
        #Combine the two above conditions into a complete condition to define
        #end of the current data/address data (step 2)
        conds_step2 = conds_OUTPUT_DISABLE
        for i in range(len(conds_ADDRESS_CHANGE)):
            conds_step2.append(conds_ADDRESS_CHANGE[i])

        debug_message = 'END OF ITEM condition: ' + str(conds_OUTPUT_DISABLE)
        self.log_output(debug_message)
        
        
        # Pre-determine which input data to strip off, the width of
        # individual items and multiplexed words, as well as format
        # strings here. This simplifies call sites which run in tight
        # loops later.
        idx_strip = max_connected + 1
        num_item_bits = idx_strip - 1
        num_item_bits = max_connected + 1
        self.log_output('Number of active bits in Address/Data: ' + str(num_item_bits))
        # Method to round num_digits_item to correct based on number of bits
        # Each hex number corresponds to four bits...
        # Find the number of full nibbles:
        num_digits_item = num_item_bits // 4
        # Perhaps there are additional bits left?
        if num_item_bits % 4 > 0:
            num_digits_item += 1
        self.fmt_item = "{{:0{}x}}".format(num_digits_item)
        # same calculation for words:
        num_word_items = self.options['wordsize']
        num_word_bits = num_item_bits * num_word_items
        num_digits_word = num_word_bits // 4
        if num_word_bits % 4 > 0:
            num_digits_word += 1
        self.fmt_word = "{{:0{}x}}".format(num_digits_word)

    
    ##### END OF PREPARATION, run once ############



        # Keep processing the input stream. Assume "always zero" for
        # not-connected input lines. 
        while True:
            #######Wait condition and how to handle data ###############
            ##
            ## The condition for relevant data to be output is: 
            ## OUTPUT_ENABLE <=> enable pins in defined mode for output...
            ##  (this is checked in self.outputIsEnabled(self, pins)
            ##
            ## 1.   Wait for condition that OUTPUT_ENABLE == true 
            ##      To prevent fault readings, OUTPUT_ENABLE shall remain valid for repeat_count_enable samples!
            ##      Read bus at this instant and back trace ss_item (start sample), don't output anything
            ##      yet, just set ss_item and store item as self.saved_item
            ## 2.   At this point, two different thing may happen:
            ##          2.1     Address may actually change during OUTPUT_ENABLE so check for any change of address
            ##                  pins during OUTPUT_ENABLE. If so, the Address/Data bus value shall remain stable for  
            ##                  repeat_count_DataAddress samples while still OUTPUT_ENABLE. If this occurs:
            ##                      Read bus as new sample, output self.stored_item, ss_item, es_item (current) 
            ##                      Store current item as self.stored_item and set ss_item.
            ##          2.2     OUTPUT_ENABLE goes invalid. If so, output self.stored_item and es_item.
            ##                  DO NOT set ss_item. This will be done at next OUTPUT_ENABLE
            ##      How to tell difference between 2.1 and 2.2? 
            ##          If OUTPUT_ENABLE is not true, it is 2.2 and no need to wait further, but
            ##          if OUTPUT_ENABLE _is_ true, pins shall stay constant for repeat_count_DataAddress
            
            ## step 1, look for stable OUTPUT_ENABLE
            step1 = True
            while step1 == True:
                pins = self.wait(conds_step1)
                debug_message = 'at step1 after wait. sample ' + str(self.samplenum)
                self.log_output(debug_message)
                counter = self.options['repeat_count_enable']
                found_start = True
                while counter > 0:
                    counter -= 1
                    pins = self.wait()
                    if self.outputIsEnabled(pins) == False: #check if condition fails
                        found_start = False                      # if so, do not continue
                        counter = 0;
                if found_start == True:  
                    debug_message = 'found_start at sample ' + str(self.samplenum)
                    self.log_output(debug_message)
                    # here we have a stable output enable situation. Store item here and store ss_item, but nothing
                    # more to do. Output will be handled in step2
                    # Create a bit list of the pins. Not assigned pins will correspond to 0
                    bits = [0 if idx is None else pins[idx] for idx in self.idx_data_channels]
                    ###we need the bits from the channels 0..max 
                    item = bitpack(bits[0:num_item_bits])
                    self.saved_item = item
                    # save start sample for the item.
                    self.ss_item = self.samplenum - self.options['repeat_count_enable']
                    # Possibly save start sample for the word, if we are at the beginning of a word.
                    # This is the case if count equals wordsize or we are at the first instance in the sequence
                    if self.items_count_for_word == self.options['wordsize'] or self.first == True:
                        self.ss_word = self.samplenum - self.options['repeat_count_enable']
                    # OK, we're done with step1 now
                    step1 = False
                    
            ### Step1 completed, now wait for output enable stop or address change    
             
            ## step 2, loop this while OUTPUT_ENABLE, but report different items if address bus changes   
            step2 = True
            while step2 == True:
                pins = self.wait(conds_step2)
                debug_message = 'at step2 after wait. sample ' + str(self.samplenum)
                self.log_output(debug_message)
                # Something changed. Store final sample and output item
                # If still output enable, we need to continue looping. 
                # If continue looping, make sure we have the same address for a couple of loops 
                # before updating the new value
                # Set end sample for Item here, but let handle_bits take care of end sample for word - easier.
                self.es_item = self.samplenum
                self.handle_bits(self.saved_item, num_item_bits)
                # Did a disable of output trigger? If so, we're done here. If not, verify that there is a stable change in Data/Address
                if self.outputIsEnabled(pins) == False:
                    #no output enable anymore - step 2 completed and return to step 1 - look for start of new Data/Address
                    step2 = False 
                else:    
                    # OK, still output enabled, then Data/Address did change, make sure that same Data/Address is around for 
                    # a couple of samples or wait for new change (restart step 2)
                    ref_data = bitpack(bits[0:num_item_bits])    
                    counter = self.options['repeat_count_DataAddress']
                    step2_1 = True
                    while counter > 0:
                        counter -= 1
                        pins = self.wait()
                        item = bitpack(bits[0:num_item_bits])
                        #check if bus is not stable
                        if item != ref_data: 
                            # if not, do not continue, but repeat step 2
                            step2_1 = False                      
                            counter = 0;
                        if self.outputIsEnabled(pins) == False: #if not OUTPUT_ENABLE anymore, then step 2 is also over
                            step2 = False
                            step2_1 = False
                            counter = 0
                            
                    if step2_1 == True:  
                        # Here we have a stable change of data. Store item here and store ss_item, but nothing
                        # more to do. Output will be handled in step2 (not step21)
                        bits = [0 if idx is None else pins[idx] for idx in self.idx_data_channels]
                        #we need the bits from the channels 0..num_item_bits-1, i.e., as indicated below
                        item = bitpack(bits[0:num_item_bits])
                        self.saved_item = item
                        self.ss_item = self.samplenum - self.options['repeat_count_DataAddress']
                        # Possibly save start sample for the word, if we are at the beginning of a word.
                        # This is the case if count equals wordsize 
                        if self.items_count_for_word == self.options['wordsize'] or self.first == True:
                            self.ss_word = self.samplenum - self.options['repeat_count_DataAddress']

             
