#!/usr/bin/env python
# -*- mode: python -*-

from __future__ import print_function
import re
import sys
import os
import argparse

try:
    from cStringIO import StringIO
except Exception:
    from io import StringIO

base = os.path.join(os.path.dirname(__file__), "..")
sys.path.insert(0, base)
import links_and_nodes_base as ln_base

def main(args):
    parser = argparse.ArgumentParser(description="generate description of links_and_nodes message_definitions for different programming languages.")

    parser.add_argument(
        "mdname",
        nargs="*", # nargs=argparse.REMAINDER,
        help="name's of message definitions to generate a description for"
    )
    parser.add_argument(
        "--mds-from-file",
        action="append", default=[], metavar="FILE",
        help="read message definition names from given filename"
    )

    # all md's found below specified folder
    parser.add_argument(
        "--recursive",
        action="append", default=[], metavar="DIR",
        help="add DIR to search path and assume that each recursively found file below that directory is a message definition to generate"
    )

    # md search path modification
    parser.add_argument(
        "--md-dir", "-md_dir",
        dest="md_dir", metavar="DIR",
        action="append", default=[],
        help="add additional message-definition base directory to search"
    )
    parser.add_argument(
        "--md-dirs-from-env-var",
        metavar="VARNAME",
        action="append", default=[],
        help="add additional message-definition base directories to search from given environment variable"
    )

    # output to filesystem
    parser.add_argument(
        "--output-format",
        choices=["c++", "jna", "java"], default="c++",
        help="generate descriptions for specified programming language (default: c++)"
    )
    parser.add_argument(
        "--output", "-o",
        default="ln_messages.h", metavar="FILENAME", dest="output_fn",
        help="filename to write description to (default: ln_messages.h)"
    )
    parser.add_argument(
        "--force", "-f",
        action="store_true",
        help="force writing to output file, even if no change is detected"
    )
    parser.add_argument(
        "--jna-skip-builtin",
        action="store_true",
        help="do not generate jna descriptions for message-definitions delivered by libln"
    )
    parser.add_argument(
        "--java-package",
        metavar="PACKAGE_NAME", default="",
        help="java package-name to use when generating jna & java descriptions"
    )
    
    # modifiers
    parser.add_argument(
        "--map-service-base",
        dest="map_service_base", metavar="X=Y",
        action="append", default=[],
        help="rename to be generates service-base-class of message definition X to Y when generating for c++"
    )
    parser.add_argument(
        "--wrapping-ns",
        action="append", default=[], metavar="NAMESPACE",
        help="wrap generated c++ service-base-classes with an additional namespace"
    )
    parser.add_argument(
        "--prefix-ns-define",
        default="LN_MD_PREFIX_NS", metavar="DEFINE",
        help="name of c++ define that when set its value is used for an additional namespace to wrap generated descriptions (default: LN_MD_PREFIX_NS)"
    )

    # output to stdout
    parser.add_argument(
        "--sfunction-parameter", "-sfunction_parameter",
        dest="sfunction_parameter",
        action="store_true",
        help="output description needed for the ln_publish_subscribe simulink sfunction"
    )
    parser.add_argument(
        "--sfunction-parameter-with-hash", "-sfunction_parameter-with-hash",
        dest="sfunction_parameter_with_hash",
        action="store_true",
        help="output description needed for the ln_publish_subscribe simulink sfunction with has of msg def"
    )
    parser.add_argument(
        "--idf", "-idf",
        action="store_true", dest="output_idf",
        help="output idf description of given message definitions"
    )    
    args = parser.parse_args()
    service_base_map = {}
    for maps in args.map_service_base:
        m = maps.split("=", 1)
        service_base_map[m[0]] = m[1]

    additional_message_definition_dirs = []
    recheck_md_dirs = False
    for dn in args.recursive + args.md_dir:
        additional_message_definition_dirs.append(dn)
        recheck_md_dirs = True

    def add_md_dirs_from_var(varname):
        msgdef_dirs = os.getenv(varname, "").split(os.path.pathsep)
        additional_message_definition_dirs.extend(msgdef_dirs)
        recheck_md_dirs = True        
    for varname in args.md_dirs_from_env_var:
        add_md_dirs_from_var(varname)        

    recursive_files = []
    def add_files(dn, base=None):
        if base is None:
            base = dn
        for e in os.listdir(dn):
            if e[0] == ".":
                continue
            p = os.path.join(dn, e)
            if os.path.isdir(p):
                add_files(p, base)
            elif os.path.isfile(p):
                recursive_files.append(p[len(base)+1:])
    for dn in args.recursive:
        add_files(dn.rstrip(os.sep))
    # sources: message definition names
    mds = args.mdname + recursive_files
    for md_fn in args.mds_from_file:
        with open(md_fn, "rb") as fp:
            data = fp.read()
        mds.extend(filter(lambda l: l and l[0] != '#', map(lambda s: s.strip(), data.split("\n"))))
    
    if recheck_md_dirs:
        ln_base.additional_message_definition_dirs = additional_message_definition_dirs
        ln_base.rescan_message_definition_dirs()

    hdr = ln_base.message_definition_header(service_base_map=service_base_map)
    hdr.wrapping_namespaces = args.wrapping_ns
    hdr.md_prefix_ns_define = args.prefix_ns_define
    
    for md in mds:
        hdr.add_message_definition(md.strip("./" + os.sep))
        
    if mds:
        if args.sfunction_parameter or args.sfunction_parameter_with_hash:
            print(hdr.generate_sfunction_parameter(with_hash=args.sfunction_parameter_with_hash))
        if args.output_idf:
            print(hdr.generate_idf())
        if (args.sfunction_parameter or args.sfunction_parameter_with_hash) or args.output_idf:
            return

    if args.output_format == "java":
        # java always goes into multiple files
        # -o specifies target directory
        return hdr.generate_java(args.output_fn, args.java_package)

    if args.output_format == "jna":
        # java always goes into multiple files
        # -o specifies target directory
        return hdr.generate_jna(args.output_fn, args.java_package, args.jna_skip_builtin)

    if args.output_format != "c++":
        raise Exception("invalid/unknown output format: %r" % args.output_format)

    # c++
    comment = """/* DO NOT EDIT! -- this file was autogenerated by
  cd %s
  %s
*/

""" % (
    os.getcwd(),
    " ".join(sys.argv))
    if args.output_fn == "-":
        return hdr.generate(sys.stdout, comment=comment)
    
    fp = StringIO()
    hdr.generate(fp, output_fn=args.output_fn, comment=comment)
    new_text = fp.getvalue()
    
    output_fn = os.path.realpath(args.output_fn)
    if not args.force and os.path.isfile(output_fn):
        with ln_base.open_text(output_fn, "rb") as fp:
            data = fp.read()
        write = data != new_text
        if data == new_text:
            print("%s: %s is up to date." % (sys.argv[0], output_fn))
            return
    
    if not os.path.exists(os.path.dirname(output_fn)):
        os.makedirs(os.path.dirname(output_fn))
    with ln_base.open_text(output_fn, "wb") as fp:
        fp.write(new_text)

if __name__ == "__main__":
    main(sys.argv[1:])
