# Copyright (c) 2014 Christopher Felton
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


from __future__ import division
from __future__ import print_function

import sys
import os
from time import gmtime, strftime
import subprocess
from pprint import pprint

from .._toolflow import _toolflow
from .._convert import convert
from ..._fpga import _fpga
from ...extintf import Clock

from ._quartus_parse_reports import get_utilization
from ._quartus_parse_reports import get_fmax

_default_pin_attr = {
    '': None,
}

class Quartus(_toolflow):
    """
    """


    def __init__(self, brd, top=None, path='./altera/'):
        """
        Given a board definition and a top-level module 
        create an instance of the Quartus toolchain.
        """
        _toolflow.__init__(self, brd, top=top, path=path)
        self.sdc_file = ''
        self._core_file_list = set()
        self._default_project_file = None
        

    def add_cores(self, filename):
        self._core_file_list.update(set(filename))


    def create_project(self, use='verilog', **pattr):
        """ Generate the Quartus .qsf project file
        """
        self.qsf_file = os.path.join(self.path, self.name+".qsf")
        qsf  = '# -------------------------------------------------------------------------- #\n'
        qsf += '# Autogenerated by gizflo \n'
        qsf += '# -------------------------------------------------------------------------- #\n\n'
        # File list
        type_file = {'verilog': 'VERILOG_FILE',
                     'vhdl': 'VHDL_FILE'}
        for f in self._core_file_list:
            qsf += "set_global_assignment -name QIP_FILE %s\n" % (f,)
        for f in self._hdl_file_list:
            qsf += "set_global_assignment -name %s %s\n" % (type_file[use], f,)
        qsf += "set_global_assignment -name FAMILY \"%s\"\n" % (self.brd.family,)
        qsf += "set_global_assignment -name DEVICE %s\n" % (self.brd.device,)
        # there is an issue here that needs to be resolved, with a version or 
        # platform dependent the myhdl converters will rename the module to 
        # the name specified with toV*.name and on another version/platform
        # it does not (self.brd.top.func_name,)
        qsf += "set_global_assignment -name TOP_LEVEL_ENTITY %s\n" % (self.name,)
        qsf += "set_global_assignment -name STRATIX_DEVICE_IO_STANDARD \"2.5 V\"\n"

        for k,v in pattr.items():
            qsf += "set_global_assignment -name %s  %s\n" %(k, v)

        # Add pin constraints/assigments
        for port_name,port in self.brd.ports.items():
            if port.inuse:
                _pins = port.pins

                for ii,pn in enumerate(_pins):
                    qsf += "set_location_assignment PIN_%s -to " % (str(pn),)
                    if len(_pins) == 1:
                        qsf += "\"%s\" " % (port_name)
                    else:
                        qsf += "\"%s\[%d\]\" " % (port_name,ii)
                    qsf += "\n"

        qsf += "#\n"

        f = open(self.qsf_file, 'w')
        try:
            d_qsf = open(self._default_project_file)
            f.write(d_qsf.read()) # Including if it exists...
            d_qsf.close()
        except:
            print("Did not include default .qsf")

        f.write(qsf)
        f.close()
        # @todo: log setup information
        #print(qsf)
        return

        
    def create_constraints(self):
        self.sdc_file = os.path.join(self.path, self.name+'.sdc')
        sdc  = '# -------------------------------------------------------------------------- #\n'
        sdc += '# clock contraints given clock def in design \n'
        for port_name, port in self.brd.ports.items():
            if port.inuse and isinstance(port.sig, Clock):
                period = 1 / (port.sig.frequency / 1e9)
                sdc += "create_clock -period %.7f [get_ports %s]\n\n" % (period, port_name)
        #sdc += 'create_clock -period 20 [get_ports clock]\n\n'
        sdc += '# Automatically apply a generate clock on the output of phase-locked loops (PLLs)\n'
        sdc += '# This command can be safely left in the SDC even if no PLLs exist in the design\n'
        sdc += 'derive_pll_clocks\n\n'
        # @todo: determine IO contraints for IO defitions
        sdc += '# Constrain the input I/O path\n'
        sdc += 'set_input_delay -clock clock -max 3 [all_inputs]\n'
        sdc += 'set_input_delay -clock clock -min 2 [all_inputs]\n\n'
        sdc += '# Constrain the output I/O path\n'
        sdc += 'set_output_delay -clock clock 2 [all_outputs]\n\n'
        sdc += '# -------------------------------------------------------------------------- #\n'

        f = open(self.sdc_file, 'w')
        f.write(sdc)
        f.close()
        # @todo: log setup information
        #print(sdc)
        return                
    

    def create_flow_script(self):
        fn = os.path.join(self.path, self.name+'.tcl')
        tcl_script = fn

        tcl = ""
        tcl = "#\n#\n# Quartus II implementation script\n"
        date_time = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
        tcl += "# created: %s\n" % (date_time,)
        tcl += "# by: %s\n" % (os.path.basename(sys.argv[0]),)
        tcl += "#\n#\n"
        tcl += "# See also: http://www.altera.com/support/excamples/tcl/open_project.html\n"
        tcl += "cd %s\n" % (self.path,)
        tcl += "load_package flow\n"
        tcl += "project_open %s\n" % (self.name,)
        tcl += "#set_global_assignment -name PROJECT_OUTPUT_DIRECTORY .\n"
        tcl += "# Synopsys design constraints, .sdc must exist\n"
        tcl += "# Quartus settings, .qsf, must exist (created from .qdf?)\n"
        tcl += "# Extra pin assignments\n"
        tcl += "#set_location_assignment -to clk PIN_BLA\n"
        tcl += "# You can define multiple clocks with a fixed relation, (not here)\n"
        tcl += "execute_flow -compile\n"
        #tcl += "execute_flow -early_timing_estimate\n"
        tcl += "project_close\n"
        
        fid = open(fn, 'w')
        fid.write(tcl)
        fid.close() 

        return tcl_script
        

    def run(self, use='verilog', name=None):
        """ Execute the tool-flow """

        self.pathexist(self.path)

        # convert the top-level
        cfiles = convert(self.brd, name=self.name, 
                         use=use, path=self.path)
        self.add_files(cfiles)

        # create the ISE files to run the toolflow
        self.create_project(use=use)
        self.create_constraints()
        tcl_name = self.create_flow_script()

        cmd = ['quartus_sh', '-t', tcl_name, '-project', self.name]
        self.logfn = 'build_quartus.log'
        try:
            logfile = open(self.logfn, 'w')
            subprocess.check_call(cmd,  #shell=True,
                                  stderr=subprocess.STDOUT,
                                  stdout=logfile)
            logfile.close()
        except Exception as err:
            print(err)
            raise err

        return self.logfn


    def get_utilization(self):
        fitlog = os.path.join(self.path, self.name+'.fit.rpt')
        info = get_utilization(fitlog)
        stalog = os.path.join(self.path, self.name+'.sta.rpt')
        info = get_fmax(stalog, info)
        return info

