#   Copyright (c) 2009,2010 Axel Wachtler
#   All rights reserved.
#
#   Redistribution and use in source and binary forms, with or without
#   modification, are permitted provided that the following conditions
#   are met:
#
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of the authors nor the names of its contributors
#     may be used to endorse or promote products derived from this software
#     without specific prior written permission.
#
#   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
#   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
#   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
#   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
#   POSSIBILITY OF SUCH DAMAGE.

# $Id$

# === import ==================================================================
import ConfigParser
import sys, pprint, copy, os

pp = pprint.pprint

# === globals =================================================================
BOARDS = []
CFGP = None
# === functions ===============================================================

##
# read the board config file and return
# a list of sections
def get_boards(fname):
    global BOARDS
    BOARDS = read_config(fname)
    board_dict = generate_board_dictionary(BOARDS)
    return board_dict

##
# Generate the file board_cfg.h that is included from
# boards.h
#
# @param allboards
#        A dictionary of board classes.
# @param fname
#        Name of the include file to be generated.
# @param boardnames
#        List of boardnames that should be used
#        if None is given, all boards given in the allboards
#        dicitonary are used.
# @param preselectedboard
#        predefines a board (lowercase name) in the header file.
#        This is needed for the Arduino stuff, since they have no unique boards ID.
def write_board_header_file(allboards, fname, boardnames = None, preselectedboard = None):
    # preselect boards or take all if None is given
    if boardnames == None:
        boards = allboards
    else:
        boards = {}
        for b in boardnames:
            boards[b] = allboards[b]
    hfile = open(fname, "w")
    frag = "/* This file is autogenerated, do not edit, changes will be lost! */\n"\
            "#ifndef BOARD_CFG_H\n"\
            "#define BOARD_CFG_H (1)\n"
    if preselectedboard != None:

        frag += "\n"\
                "/* === PRESEECTED BOARD === */\n"\
                "/* ... so this must be an arduino include file */\n"\
                "#include \"pins_arduino.h\"\n"
    frag += generate_include_fragment(boards)
    frag += "\n#endif /*BOARD_CFG_H*/\n"
    hfile.write(frag)
    hfile.close()

def show_boards(boards):
    if isinstance(boards, dict):
        bl = boards.values()
    else:
        bl = boards

    board_list = [(b.name, b) for b in bl]
    board_list.sort()
    tmp = ""
    for bn,b in board_list:
        nlist = [b.name]
        nlist.extend(getattr(b,'aliases','').split())
        tmp += "    "
        tmp += "|".join(nlist) + ":\n      "
        tmp += "%s\n" % getattr(b,'comment','-?-').replace("\n","\n      ")
    return tmp

##
# Read a config file and returns a list of BoardCfg instances.
#
# @param  fname win-ini style config file.
# @return list of BoardCfg instances
#
def read_config(fname):
    global CFGP
    ret = []
    cfgp = ConfigParser.ConfigParser()
    cfgp.read(fname)
    for brdname in cfgp.sections():
        brd = BoardCfg(brdname)
        for opt in cfgp.options(brdname):
            val = cfgp.get(brdname, opt)
            setattr(brd, opt, val)
        brd.postinit()
        ret.append(brd)
    CFGP = cfgp
    return ret

##
# Write a include file fragment.
#
# @param boards list of BoardCfg instances
#
def generate_include_fragment(boards):
    inclst = [ ]
    if isinstance(boards, dict):
        board_list = boards.values()
    else:
        board_list = boards
    board_list.sort()

    board_id = 0
    board_types = []
    for b in board_list:
        nlist = [b.name]
        nlist.extend(getattr(b,'aliases','').split())
        for n in nlist:
            board_types.append("#define BOARD_%s (%d)\n" % (n.upper(), board_id))
            board_id += 1
    for b in board_list:
        nlist = [b.name]
        nlist.extend(getattr(b,'aliases','').split())
        tmp = " || ".join(["defined(%s)" % n for n in nlist]) + "\n"
        tmp += "# define BOOT_LOADER_ADDRESS (0x%x)\n" % int(b.blstart/2)
        if b.baudrate:
            tmp += "# if !defined(HIF_DEFAULT_BAUDRATE)\n"\
                   "#  define HIF_DEFAULT_BAUDRATE (%d)\n" \
                   "# endif\n" % int(b.baudrate)
        tmp += "# include \"%s\"\n\n" % (b.include)
        inclst.append(tmp)

    ret = '\n/* === BOARD TYPES === */\n' +\
          ''.join(board_types) +\
          '\n/* === BOARD INCLUDES === */\n'\
          '#if %s'\
          '#else\n'\
          '# error "BOARD_TYPE is not defined or wrong"\n'\
          '#endif\n' % \
            "#elif ".join(inclst)
    return ret


def generate_board_dictionary(boards):
    ret = {}
    for b in boards:
        if ret.has_key(b.name):
            print "duplicate board ID %s" % b.name
        else:
            ret[b.name] = b
    return ret


def generate_board_dictionary_expanded(boards):
    ret = {}
    for b in boards.values():
        board_names = [b.name]
        board_names.extend(getattr(b,'aliases','').split())
        for name in board_names:
            if ret.has_key(name):
                print "duplicate board ID %s" % name
                raw_input("press enter to continue")
            ret[name] = copy.copy(b)
            ret[name].name = name
            ret[name].aliases = ""
    return ret

def boards_to_dox():
    global CFGP
    import pprint
    pp=pprint.pprint
    ret = ""
    # read the configparser Values and convert it
    # to a html overview.
    ret = "/**\n@page pgBoards Boards and Modules\n"
    boards = CFGP.sections()
    boards.sort()
    ret += " - ".join(["@ref secBoard%s \"%s\"" %(i,i) for i in boards])
    ret += "\n"
    for i in boards:
        b = dict(CFGP.items(i))
        ret += "\n@section secBoard%s Board %s" %(i,i)
        aliases = b.get('aliases', None)
        if aliases:
            ret += " (%s)" % ", ".join(aliases.split())
        ret += "\n"
        ret += "<em>%s</em>\n" %b.get('comment')
        img = b.get("image",None)
        if img != None:
            ret += "@image html %s \"%s\" \n" % (img, i)
        ret += " - Include file: %s\n" % os.path.basename(b.get("include"))
        ret += " - MMCU: %s\n" % b.get("cpu")
        ret += " - F_CPU: %s\n" % b.get("f_cpu")
        fuses = ""
        if b.get("lfuse",None):
            fuses += "-U lfuse:w:%s:m " % b.get("lfuse")
        if b.get("hfuse",None):
            fuses += "-U hfuse:w:%s:m " % b.get("hfuse")
        if b.get("efuse",None):
            fuses += "-U efuse:w:%s:m " % b.get("efuse")
        if len(fuses):
            ret += " - Fuses: %s\n" % fuses
    ret +="*/\n"

    ret += "/**\n    @addtogroup grpBoard \n  @{\n"
    # assign board includes to a group
    for f in [os.path.basename(dict(CFGP.items(b)).get("include")) for b in boards]:
        ret += " @file %s\n\n" % f
    ret += "  @} */"
    return ret

def boards_to_asciidoc():
    global CFGP
    rv = ":imgpath: manual/\n\n== Board Overview ==\n\n"

    TAB_START = "[cols=\"1,1,4\",options=\"header\"]\n"\
                "|====================================\n"\
                "| Board              | Scons Targets | Comment"
    TAB_END = "|===================================="
    TAB_FMT = "| xref:%(boardref)s[%(board)s] | %(board)s %(aliases)s | %(comment)s"
    EOL = "\n"
    boards = sorted(CFGP.sections())

    rv += TAB_START + EOL
    for i in boards:
        b = dict(CFGP.items(i))
        b["board"] = i
        b["boardref"] = "_" + i
        b["aliases"] = b.get('aliases', "")
        b["image"] = b.get("image","")
        if len(b["image"].strip()) == 0:
            b["image"] = "antenna.png"
        rv+= TAB_FMT % b  + EOL

    rv += TAB_END  + EOL

    for i in boards:
        b = dict(CFGP.items(i))
        b["board"] = i
        b["aliases"] = b.get("aliases","")
        b["baudrate"] = b.get("baudrate", "9600")
        b["image"] = b.get("image","")
        if len(b["image"].strip()) == 0:
            b["image"] = "antenna.png"
        rv += "\n== %(board)s ==\n\n" \
              "image::{imgpath}/%(image)s[height=\"200\", float=\"right\"]\n"\
              "[horizontal]\n"\
              "Board::\n   %(comment)s\n"\
              "Targets::\n   %(board)s %(aliases)s\n" \
              "Include::\n   +%(include)s+\n"\
              "CPU::\n %(cpu)s\nF_CPU:: %(f_cpu)s\nBaudrate:: %(baudrate)s bit/s\n"\
              "\n\n"% b
    return rv


def get_lib_make_rules():
    boarddict = generate_board_dictionary(BOARDS)
    boards = generate_board_dictionary_expanded(boarddict)
    boardnames = boards.keys()
    boardnames.sort()
    all_rule = "all: " + " ".join(boardnames)
    all_rule += "\n\nlist:\n"
    for bn in boardnames:
        all_rule += "\t @echo '  %-16s : %s'\n" % (bn, boards[bn].comment.split("\n")[0])

    rules = []
    for bn in boardnames:
        board = boards[bn]
        r = "%(name)s:\n\t$(MAKE) BOARD=%(name)s MCU=%(cpu)s F_CPU=%(F_CPU)s "\
            "$(TARGETS)\n" % boards[bn]
        rules.append(r)
    board_rules = "\n".join(rules)
    return all_rule, board_rules

def get_app_make_rules(app):
    boarddict = generate_board_dictionary(BOARDS)
    boards = generate_board_dictionary_expanded(boarddict)
    boardnames = []
    for bn in boards.keys():
        exclude = boards[bn].no_app.split() + boards[bn].no_xmpl.split()
        if app not in exclude:
            boardnames.append(bn)
    boardnames.sort()
    all_rule = "all: " + " ".join(boardnames)
    all_rule += "\n\nlist:\n"
    for bn in boardnames:
        all_rule += "\t @echo '  %-16s : %s'\n" % (bn, boards[bn].comment.split("\n")[0])

    rules = []
    for bn in boardnames:
        board = boards[bn]
        r = "%(name)s:\n\t$(MAKE) -f $(CURRENT_MAKEFILE)"\
            " BOARD=%(name)s MCU=%(cpu)s F_CPU=%(F_CPU)s"\
            " BOOTOFFSET=%(bootoffset)s"\
            " $(TARGETS)\n" % boards[bn]
        rules.append(r)
    board_rules = "\n".join(rules)
    return all_rule, board_rules

def get_xmpl_aps_rules(xmpl):
    boarddict = generate_board_dictionary(BOARDS)
    boards = generate_board_dictionary_expanded(boarddict)
    boardnames = []
    for bn in boards.keys():
        if boards[bn].no_xmpl.find(xmpl) < 0:
            boardnames.append(bn)
    boardnames.sort()
    # this the text snippet to paste in the APS file.
    config_template = """\
      <CONFIG>
        <NAME>%(name)s</NAME>
        <USESEXTERNALMAKEFILE>NO</USESEXTERNALMAKEFILE>
        <EXTERNALMAKEFILE/>
        <PART>%(cpu)s</PART>
        <HEX>1</HEX>
        <LIST>1</LIST>
        <MAP>1</MAP>
        <OUTPUTFILENAME>%(xmpl)s_%(name)s.elf</OUTPUTFILENAME>
        <OUTPUTDIR>build_%(name)s_%(xmpl)s/</OUTPUTDIR>
        <ISDIRTY>1</ISDIRTY>
        <OPTIONS>
        </OPTIONS>
        <INCDIRS>
          <INCLUDE>..\inc\</INCLUDE>
          <INCLUDE>.\</INCLUDE>
        </INCDIRS>
        <LIBDIRS>
          <LIBDIR>..\lib</LIBDIR>
        </LIBDIRS>
        <LIBS>
          <LIB>liburacoli_%(name)s.a</LIB>
        </LIBS>
        <LINKOBJECTS/>
        <OPTIONSFORALL>-Wall -Wundef -gdwarf-2 -mmcu=%(cpu)s -D%(name)s -DF_CPU=%(F_CPU)s -Os -fsigned-char </OPTIONSFORALL>
        <LINKEROPTIONS/>
        <SEGMENTS/>
      </CONFIG>"""
    configs = []
    for bn in boardnames:
        board = boards[bn]
        board['xmpl'] = xmpl
        r =  config_template % board
        configs.append(r)
    aps_configs = "\n".join(configs)
    return aps_configs, boardnames[0]


# === classes =================================================================
class BoardCfg:
    def __init__(self,name):
        self.name = name
        self.baudrate = None

    def postinit(self):
        self.blstart = eval(self.bootoffset)
        self.F_CPU = self.f_cpu
        self.NO_APP = self.no_app.split()
        self.NO_XMPL = self.no_xmpl.split()
        me = getattr(self,'more_xmpls','')
        self.MORE_XMPLS = me.split()

    def get_ccopts(self):
        ccflags = "-D%s " % self.name
        ccflags += "-DF_CPU=%s " % self.f_cpu
        try:
            x = eval(self.ccflags)
            ccflags += x
        except:
            pass
        return ccflags

    def __str__(self):
        return "<%s>" % self.name

    def __getitem__(self, key):
        return getattr(self,key)

    def __setitem__(self, key, val):
        setattr(self, key, val)

# === init ====================================================================

if __name__ == "__main__":
    for fname in sys.argv[1:]:
        bd = get_boards(fname)
        #print "xxxx:",fname,bd
        #print show_boards(bd)
        #print get_make_rules()

        print boards_to_asciidoc()