#!/usr/bin/env python3

import argparse
import os
import glob
import csv
import sys
import re
from shutil import which
import datetime

def is_tool(name):
    return which(name) is not None

def check_path(path):
    paths = glob.glob(path)
    if len(paths) == 0:
        exit("file not found: %s" % path)
    if len(paths) > 1:
        print("warning: glob pattern found too many files, using first one: %s" % paths[0])
    
    return paths[0]

def openlane_date_sort(e):
    datestamp = os.path.basename(e)
    if re.match(r'^\d+\-\d+\_\d+\-\d+$',datestamp):
        timestamp = datetime.datetime.strptime(datestamp, '%d-%m_%H-%M')
        return timestamp.timestamp()
    return datestamp

def summary_report(summary_file):
    # print short summary of the csv file
    status = None
    with open(summary_file) as fh:
        summary = csv.DictReader(fh)
        for row in summary:
            for key, value in row.items():
                if "violation" in key or "error" in key:
                    print("%30s : %20s" % (key, value))
                if "AREA" in key:
                    area = float(value)
                if "flow_status" in key:
                    status = value           

    print("area %d um^2" % (1e6 * area))
    if status is not None: # newer OpenLANE has status, older ones don't
        print("flow status: %s" % status)

def full_summary_report(summary_file):
    # print short summary of the csv file
    with open(summary_file) as fh:
        summary = csv.DictReader(fh)
        for row in summary:
            for key, value in row.items():
                print("%30s : %20s" % (key, value))
                    
def drc_report(drc_file):
    last_drc = None
    drc_count = 0
    with open(drc_file) as drc:
        for line in drc.readlines():
            drc_count += 1
            if '(' in line:
                if last_drc is not None:
                    print("* %s (%d)" % (last_drc, drc_count/4))
                last_drc = line.strip()
                drc_count = 0

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="OpenLANE summary tool")
    group = parser.add_mutually_exclusive_group(required=True)

    # either choose the design and interation
    group.add_argument('--design', help="only run checks on specific design", action='store')
    # or show standard cells
    group.add_argument('--show-sky130', help='show all standard cells', action='store_const', const=True)

    # optionally choose different name for top module and which run to use (default latest)
    parser.add_argument('--top', help="name of top module if not same as design", action='store')
    parser.add_argument('--run', help="choose a specific run. If not given use latest. If not arg, show a menu", action='store', default=-1, nargs='?', type=int)

    # what to show
    parser.add_argument('--drc', help='show DRC report', action='store_const', const=True)
    parser.add_argument('--summary', help='show violations, area & status from summary report', action='store_const', const=True)
    parser.add_argument('--full-summary', help='show the full summary report csv file', action='store_const', const=True)
    parser.add_argument('--synth', help='show post techmap synth', action='store_const', const=True)
    parser.add_argument('--yosys-report', help='show cell usage after yosys synth', action='store_const', const=True)

    # klayout for intermediate files
    parser.add_argument('--floorplan', help='show floorplan', action='store_const', const=True)
    parser.add_argument('--pdn', help='show PDN', action='store_const', const=True)
    parser.add_argument('--global-placement', help='show global placement PDN', action='store_const', const=True)
    parser.add_argument('--detailed-placement', help='show detailed placement', action='store_const', const=True)
    parser.add_argument('--gds', help='show final GDS', action='store_const', const=True)

    # GDS3D for 3d view
    parser.add_argument('--gds-3d', help='show final GDS in 3D', action='store_const', const=True)
 
    parser.add_argument('--caravel', help='use caravel directory structure instead of standard openlane', action='store_const', const=True)
   
    args = parser.parse_args()

    if not args.top:
        args.top = args.design 

    if not 'OPENLANE_ROOT' in os.environ:
        exit("pls set OPENLANE_ROOT to where your OpenLANE is installed")

    klayout_def = os.path.join(os.path.dirname(sys.argv[0]), 'klayout_def.xml')
    klayout_gds = os.path.join(os.path.dirname(sys.argv[0]), 'klayout_gds.xml')
    gds3d_tech  = os.path.join(os.path.dirname(sys.argv[0]), 'sky130.txt')

    # if showing off the sky130 cells
    if args.show_sky130:
        if not os.environ['PDK_ROOT']:
            exit("pls set PDK_ROOT to where your PDK is installed")
        path = check_path(os.path.join(os.environ['PDK_ROOT'], "sky130A", "libs.ref", "sky130_fd_sc_hd", "gds", "sky130_fd_sc_hd.gds"))
        os.system("klayout -l %s %s" % (klayout_gds, path))
        exit()

    # otherwise need to know where openlane and the designs are
    openlane_designs = ''
    if args.caravel:
        if os.path.exists('openlane'):
            openlane_designs = 'openlane'
        else:
            openlane_designs = '.'
        run_dir = os.path.join(openlane_designs, args.design, 'runs/*')
    else:
        openlane_designs = os.path.join(os.environ['OPENLANE_ROOT'], 'designs')
        run_dir = os.path.join(openlane_designs, args.design, 'runs/*-*')

    list_of_files = glob.glob(run_dir)
    if len(list_of_files) == 0:
        exit("couldn't find that design")
    list_of_files.sort(key=openlane_date_sort)

    # what run to show?
    if args.run == -1:
        # default is to use the latest
        print("using latest run:")
        run_path = max(list_of_files, key=os.path.getctime)

    elif args.run is None:
        # UI for asking for which run to use
        for run_index, run in enumerate(list_of_files):
            print("\n%2d: %s" % (run_index, os.path.basename(run)), end='')
        print(" <default>\n")
        
        n = input("which run? <enter for default>: ") or run_index
        run_path = list_of_files[int(n)]

    else:
        # use the given run
        print("using run %d:" % args.run)
        run_path = list_of_files[args.run]

    print(run_path)

    if args.summary:
        path = check_path(os.path.join(run_path, 'reports', 'final_summary_report.csv'))
        summary_report(path)

    if args.full_summary:
        path = check_path(os.path.join(run_path, 'reports', 'final_summary_report.csv'))
        full_summary_report(path)

    if args.drc:
        path = os.path.join(run_path, 'logs', 'magic', 'magic.drc') # don't check path because if DRC is clean, don't get the file
        if os.path.exists(path):
            drc_report(path)
        else:
            print("no DRC file, DRC clean?")

    if args.synth:
        path = check_path(os.path.join(run_path, "tmp", "synthesis", "post_techmap.dot")) # post_techmap is created by https://github.com/efabless/openlane/pull/282
        os.system("xdot %s" % path)

    if args.yosys_report:
        filename = "*yosys_*.stat.rpt"
        path = check_path(os.path.join(run_path, "reports", "synthesis", filename))
        os.system("cat %s" % path)

    if args.floorplan:
        path = check_path(os.path.join(run_path, "results", "floorplan", args.top + ".floorplan.def"))
        os.system("klayout -l %s %s" % (klayout_def, path))

    if args.pdn:
        filename = "*pdn.def"
        path = check_path(os.path.join(run_path, "tmp", "floorplan", filename))
        os.system("klayout -l %s %s" % (klayout_def, path))

    if args.global_placement:
        filename = "*replace.def"
        path = check_path(os.path.join(run_path, "tmp", "placement", filename))
        os.system("klayout -l %s %s" % (klayout_def, path))

    if args.detailed_placement:
        path = check_path(os.path.join(run_path, "results", "placement", args.top + ".placement.def"))
        os.system("klayout -l %s %s" % (klayout_def, path))

    if args.gds:
        path = check_path(os.path.join(run_path, "results", "magic", args.top + ".gds"))
        os.system("klayout -l %s %s" % (klayout_gds, path))

    if args.gds_3d:
        if not is_tool('GDS3D'):
            exit("pls install GDS3D from https://github.com/trilomix/GDS3D")
        path = check_path(os.path.join(run_path, "results", "magic", args.top + ".gds"))
        os.system("GDS3D -p %s -i %s" % (gds3d_tech, path))
        

[(16, 'glob.glob', 'glob.glob', (['path'], {}), False, 'import glob\n'), (25, 'os.path.basename', 'os.path.basename', (['e'], {}), False, 'import os\n'), (26, 're.match', 're.match', (['"""^\\\\d+\\\\-\\\\d+\\\\_\\\\d+\\\\-\\\\d+$"""', 'datestamp'], {}), False, 'import re\n'), (70, 'argparse.ArgumentParser', 'argparse.ArgumentParser', ([], {'description': '"""OpenLANE summary tool"""'}), False, 'import argparse\n'), (133, 'glob.glob', 'glob.glob', (['run_dir'], {}), False, 'import glob\n'), (13, 'shutil.which', 'which', (['name'], {}), False, 'from shutil import which\n'), (27, 'datetime.datetime.strptime', 'datetime.datetime.strptime', (['datestamp', '"""%d-%m_%H-%M"""'], {}), False, 'import datetime\n'), (35, 'csv.DictReader', 'csv.DictReader', (['fh'], {}), False, 'import csv\n'), (52, 'csv.DictReader', 'csv.DictReader', (['fh'], {}), False, 'import csv\n'), (109, 'os.path.dirname', 'os.path.dirname', (['sys.argv[0]'], {}), False, 'import os\n'), (110, 'os.path.dirname', 'os.path.dirname', (['sys.argv[0]'], {}), False, 'import os\n'), (111, 'os.path.dirname', 'os.path.dirname', (['sys.argv[0]'], {}), False, 'import os\n'), (118, 'os.system', 'os.system', (["('klayout -l %s %s' % (klayout_gds, path))"], {}), False, 'import os\n'), (124, 'os.path.exists', 'os.path.exists', (['"""openlane"""'], {}), False, 'import os\n'), (128, 'os.path.join', 'os.path.join', (['openlane_designs', 'args.design', '"""runs/*"""'], {}), False, 'import os\n'), (130, 'os.path.join', 'os.path.join', (["os.environ['OPENLANE_ROOT']", '"""designs"""'], {}), False, 'import os\n'), (131, 'os.path.join', 'os.path.join', (['openlane_designs', 'args.design', '"""runs/*-*"""'], {}), False, 'import os\n'), (169, 'os.path.join', 'os.path.join', (['run_path', '"""logs"""', '"""magic"""', '"""magic.drc"""'], {}), False, 'import os\n'), (170, 'os.path.exists', 'os.path.exists', (['path'], {}), False, 'import os\n'), (177, 'os.system', 'os.system', (["('xdot %s' % path)"], {}), False, 'import os\n'), (182, 'os.system', 'os.system', (["('cat %s' % path)"], {}), False, 'import os\n'), (186, 'os.system', 'os.system', (["('klayout -l %s %s' % (klayout_def, path))"], {}), False, 'import os\n'), (191, 'os.system', 'os.system', (["('klayout -l %s %s' % (klayout_def, path))"], {}), False, 'import os\n'), (196, 'os.system', 'os.system', (["('klayout -l %s %s' % (klayout_def, path))"], {}), False, 'import os\n'), (200, 'os.system', 'os.system', (["('klayout -l %s %s' % (klayout_def, path))"], {}), False, 'import os\n'), (204, 'os.system', 'os.system', (["('klayout -l %s %s' % (klayout_gds, path))"], {}), False, 'import os\n'), (210, 'os.system', 'os.system', (["('GDS3D -p %s -i %s' % (gds3d_tech, path))"], {}), False, 'import os\n'), (117, 'os.path.join', 'os.path.join', (["os.environ['PDK_ROOT']", '"""sky130A"""', '"""libs.ref"""', '"""sky130_fd_sc_hd"""', '"""gds"""', '"""sky130_fd_sc_hd.gds"""'], {}), False, 'import os\n'), (161, 'os.path.join', 'os.path.join', (['run_path', '"""reports"""', '"""final_summary_report.csv"""'], {}), False, 'import os\n'), (165, 'os.path.join', 'os.path.join', (['run_path', '"""reports"""', '"""final_summary_report.csv"""'], {}), False, 'import os\n'), (176, 'os.path.join', 'os.path.join', (['run_path', '"""tmp"""', '"""synthesis"""', '"""post_techmap.dot"""'], {}), False, 'import os\n'), (181, 'os.path.join', 'os.path.join', (['run_path', '"""reports"""', '"""synthesis"""', 'filename'], {}), False, 'import os\n'), (185, 'os.path.join', 'os.path.join', (['run_path', '"""results"""', '"""floorplan"""', "(args.top + '.floorplan.def')"], {}), False, 'import os\n'), (190, 'os.path.join', 'os.path.join', (['run_path', '"""tmp"""', '"""floorplan"""', 'filename'], {}), False, 'import os\n'), (195, 'os.path.join', 'os.path.join', (['run_path', '"""tmp"""', '"""placement"""', 'filename'], {}), False, 'import os\n'), (199, 'os.path.join', 'os.path.join', (['run_path', '"""results"""', '"""placement"""', "(args.top + '.placement.def')"], {}), False, 'import os\n'), (203, 'os.path.join', 'os.path.join', (['run_path', '"""results"""', '"""magic"""', "(args.top + '.gds')"], {}), False, 'import os\n'), (209, 'os.path.join', 'os.path.join', (['run_path', '"""results"""', '"""magic"""', "(args.top + '.gds')"], {}), False, 'import os\n'), (147, 'os.path.basename', 'os.path.basename', (['run'], {}), False, 'import os\n')]