"""
This module provides a structure to define the fuzz environment
"""
import os
from os import path
from string import Template
import diamond


class FuzzConfig:
    def __init__(self, job, family, device, tiles, ncl, mapargs=None):
        """
        :param job: user-friendly job name, used for folder naming etc
        :param family: Target family name
        :param device: Target device name
        :param tiles: List of tiles to consider during fuzzing
        :param ncl: Minimal NCL file to use as a base for interconnect fuzzing
        """
        self.job = job
        self.family = family
        self.device = device
        self.tiles = tiles
        self.ncl = ncl
        self.ncd_specimen = None
        self.mapargs = mapargs

    @property
    def workdir(self):
        return path.join(".", "work", self.job)

    def make_workdir(self):
        """Create the working directory for this job, if it doesn't exist already"""
        os.makedirs(self.workdir, exist_ok=True)

    def setup(self, skip_specimen=False):
        """
        Create a working directory, and run Diamond on a minimal ncl file to create a ncd/prf for Tcl usage
        """
        self.make_workdir()
        if not skip_specimen:
            self.build_design(self.ncl, {})

    def build_design(self, des_template, substitutions, prefix="", no_trce=True, backanno=False, substitute=True):
        """
        Run Diamond on a given design template, applying a map of substitutions, plus some standard substitutions
        if not overriden.

        :param des_template: path to template NCL/Verilog file
        :param substitutions: dictionary containing template subsitutions to apply to NCL/Verilog file
        :param prefix: prefix to append to filename, for running concurrent jobs without collisions

        Returns the path to the output bitstream
        """
        subst = dict(substitutions)
        if "route" not in subst:
            subst["route"] = ""
        if "sysconfig" not in subst:
            subst["sysconfig"] = ""
        ext = des_template.split(".")[-1]
        lpf_template = des_template.replace("." + ext, ".lpf")
        prf_template = des_template.replace("." + ext, ".prf")

        desfile = path.join(self.workdir, prefix + "design." + ext)
        bitfile = path.join(self.workdir, prefix + "design.bit")
        lpffile = path.join(self.workdir, prefix + "design.lpf")
        prffile = path.join(self.workdir, prefix + "design.prf")

        if path.exists(bitfile):
            os.remove(bitfile)
        with open(des_template, "r") as inf:
            with open(desfile, "w") as ouf:
                if substitute:
                    ouf.write(Template(inf.read()).substitute(**subst))
                else:
                    ouf.write(inf.read())
        if path.exists(lpf_template):
            with open(lpf_template, "r") as inf:
                with open(lpffile, "w") as ouf:
                    if substitute:
                        ouf.write(Template(inf.read()).substitute(**subst))
                    else:
                        ouf.write(inf.read())
        if path.exists(prf_template):
            with open(prf_template, "r") as inf:
                with open(prffile, "w") as ouf:
                    ouf.write(Template(inf.read()).substitute(**subst))
        diamond.run(self.device, desfile, no_trce=no_trce, backanno=backanno, mapargs=self.mapargs)
        if ext == "ncl" and self.ncd_specimen is None:
            self.ncd_specimen = path.join(self.workdir, prefix + "design.tmp", "par_impl.ncd")
        return bitfile


    @property
    def ncd_prf(self):
        """
        A (ncd, prf) file tuple for Tcl. build_design must have been run at least once prior to accessing this property
        """
        assert self.ncd_specimen is not None
        prf = self.ncd_specimen.replace("par_impl.ncd", "synth_impl.prf")
        return self.ncd_specimen, prf
