#!/usr/bin/env python3
#
#  Copyright (C) 2015  Clifford Wolf <clifford@clifford.at>
#
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#
#  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
#  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
#  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
#  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
#  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
#  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

import icebox
from icebox import re_match_cached
import getopt, sys, re, os

check_mode = False
fixup_mode = False

def usage():
    print("""
Usage: %s [options] [input.asc [output.asc]]

    -c
        check colbuf bits

    -f
        fix colbuf bits
""" % os.path.basename(sys.argv[0]))
    sys.exit(1)

try:
    opts, args = getopt.getopt(sys.argv[1:], "cf")
except:
    usage()

for o, a in opts:
    if o == "-c":
        check_mode = True
    elif o == "-f":
        fixup_mode = True
    else:
        usage()

if len(args) == 0:
    args.append("/dev/stdin")

if len(args) not in [1, 2]:
    usage()

if check_mode == fixup_mode:
    print("Error: Use either -c or -f!")
    sys.exit(1)

print("Reading file '%s'.." % args[0])
ic = icebox.iceconfig()
ic.read_file(args[0])

def make_cache(stmt, raw_db):
    cache = list()
    for entry in raw_db:
        if entry[1] == stmt and entry[2].startswith("glb_netwk_"):
            cache_entry = [int(entry[2][-1]), []]
            for bit in entry[0]:
                value = "1"
                if bit.startswith("!"):
                    value = "0"
                    bit = bit[1:]
                match = re_match_cached("B([0-9]+)\[([0-9]+)\]", bit)
                cache_entry[1].append((int(match.group(1)), int(match.group(2)), value))
            cache.append(cache_entry)
    return cache

def match_cache_entry(cache_entry, tile_dat):
    for entry in cache_entry[1]:
        if tile_dat[entry[0]][entry[1]] != entry[2]:
            return False
    return True

def analyze_tile(ic, cache, tile_pos):
    glbs = set()
    tile_dat = ic.tile(tile_pos[0], tile_pos[1])
    for cache_entry in cache:
        if match_cache_entry(cache_entry, tile_dat):
            glbs.add(cache_entry[0])
    return glbs

colbuf_map = dict()
used_glbs_map = dict()
driven_glbs_map = dict()

for entry in ic.colbuf_db():
    colbuf_map[(entry[2], entry[3])] = (entry[0], entry[1])

for tiles in [ic.io_tiles, ic.logic_tiles, ic.ramb_tiles, ic.ramt_tiles]:
    cache = None
    for tile in tiles:
        if cache is None:
            cache = make_cache("buffer", ic.tile_db(tile[0], tile[1]))
        glbs = analyze_tile(ic, cache, tile)
        if len(glbs):
            assert tile in colbuf_map
            s = used_glbs_map.setdefault(colbuf_map[tile], set())
            used_glbs_map[colbuf_map[tile]] = s.union(glbs)

    cache = None
    for tile in tiles:
        if cache is None:
            cache = make_cache("ColBufCtrl", ic.tile_db(tile[0], tile[1]))
        glbs = analyze_tile(ic, cache, tile)
        if len(glbs):
            driven_glbs_map[tile] = glbs

def set_colbuf(ic, tile, bit, value):
    tile_dat = ic.tile(tile[0], tile[1])
    tile_db = ic.tile_db(tile[0], tile[1])
    for entry in tile_db:
        if entry[1] == "ColBufCtrl" and entry[2] == "glb_netwk_%d" % bit:
            match = re_match_cached("B([0-9]+)\[([0-9]+)\]", entry[0][0])
            l = tile_dat[int(match.group(1))]
            n = int(match.group(2))
            l = l[:n] + value + l[n+1:]
            tile_dat[int(match.group(1))] = l
            return
    assert False

error_count = 0
correct_count = 0
for tile, bits in sorted(used_glbs_map.items()):
    for bit in bits:
        if tile not in driven_glbs_map or bit not in driven_glbs_map[tile]:
            print("Missing driver for glb_netwk_%d in tile %s" % (bit, tile))
            set_colbuf(ic, tile, bit, "1")
            error_count += 1
for tile, bits in sorted(driven_glbs_map.items()):
    for bit in bits:
        if tile not in used_glbs_map or bit not in used_glbs_map[tile]:
            print("Unused driver for glb_netwk_%d in tile %s" % (bit, tile))
            set_colbuf(ic, tile, bit, "0")
            error_count += 1
        else:
            # print("Correct driver for glb_netwk_%d in tile %s" % (bit, tile))
            correct_count += 1
print("Found %d correct driver bits." % correct_count)
if error_count != 0:
    if not fixup_mode:
        print("Found %d errors!" % error_count)
        sys.exit(1)
    ic.write_file(args[0] if len(args) == 1 else args[1])
    print("Corrected %d errors." % error_count)
else:
    print("No errors found.")

