# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import optparse, os, re, sys
from cStringIO import StringIO
from mozbuild.pythonutil import iter_modules_in_path
import mozpack.path as mozpath
import itertools

import ipdl

def log(minv, fmt, *args):
    if _verbosity >= minv:
        print fmt % args

# process command line

op = optparse.OptionParser(usage='ipdl.py [options] IPDLfiles...')
op.add_option('-I', '--include', dest='includedirs', default=[ ],
              action='append',
              help='Additional directory to search for included protocol specifications')
op.add_option('-v', '--verbose', dest='verbosity', default=1, action='count',
              help='Verbose logging (specify -vv or -vvv for very verbose logging)')
op.add_option('-q', '--quiet', dest='verbosity', action='store_const', const=0,
              help="Suppress logging output")
op.add_option('-d', '--outheaders-dir', dest='headersdir', default='.',
              help="""Directory into which C++ headers will be generated.
A protocol Foo in the namespace bar will cause the headers
  dir/bar/Foo.h, dir/bar/FooParent.h, and dir/bar/FooParent.h
to be generated""")
op.add_option('-o', '--outcpp-dir', dest='cppdir', default='.',
              help="""Directory into which C++ sources will be generated
A protocol Foo in the namespace bar will cause the sources
  cppdir/FooParent.cpp, cppdir/FooChild.cpp
to be generated""")


options, files = op.parse_args()
_verbosity = options.verbosity
headersdir = options.headersdir
cppdir = options.cppdir
includedirs = [ os.path.abspath(incdir) for incdir in options.includedirs ]

if not len(files):
    op.error("No IPDL files specified")

ipcmessagestartpath = os.path.join(headersdir, 'IPCMessageStart.h')
ipc_msgtype_name_path = os.path.join(cppdir, 'IPCMessageTypeName.cpp')

# Compiling the IPDL files can take a long time, even on a fast machine.
# Check to see whether we need to do any work.
latestipdlmod = max(os.stat(f).st_mtime
                    for f in itertools.chain(files,
                                             iter_modules_in_path(mozpath.dirname(__file__))))

def outputModTime(f):
    # A non-existant file is newer than everything.
    if not os.path.exists(f):
        return 0
    return os.stat(f).st_mtime

# Because the IPDL headers are placed into directories reflecting their
# namespace, collect a list here so we can easily map output names without
# parsing the actual IPDL files themselves.
headersmap = {}
for (path, dirs, headers) in os.walk(headersdir):
    for h in headers:
        base = os.path.basename(h)
        if base in headersmap:
            root, ext = os.path.splitext(base)
            print >>sys.stderr, 'A protocol named', root, 'exists in multiple namespaces'
            sys.exit(1)
        headersmap[base] = os.path.join(path, h)

def outputfiles(f):
    base = os.path.basename(f)
    root, ext = os.path.splitext(base)

    suffixes = ['']
    if ext == '.ipdl':
        suffixes += ['Child', 'Parent']

    for suffix in suffixes:
        yield os.path.join(cppdir, "%s%s.cpp" % (root, suffix))
        header = "%s%s.h" % (root, suffix)
        # If the header already exists on disk, use that.  Otherwise,
        # just claim that the header is found in headersdir.
        if header in headersmap:
            yield headersmap[header]
        else:
            yield os.path.join(headersdir, header)

def alloutputfiles():
    for f in files:
        for s in outputfiles(f):
            yield s
    yield ipcmessagestartpath

earliestoutputmod = min(outputModTime(f) for f in alloutputfiles())

if latestipdlmod < earliestoutputmod:
    sys.exit(0)

log(2, 'Generated C++ headers will be generated relative to "%s"', headersdir)
log(2, 'Generated C++ sources will be generated in "%s"', cppdir)

allmessages = {}
allprotocols = []

def normalizedFilename(f):
    if f == '-':
        return '<stdin>'
    return f

# First pass: parse and type-check all protocols
for f in files:
    log(2, os.path.basename(f))
    filename = normalizedFilename(f)
    if f == '-':
        fd = sys.stdin
    else:
        fd = open(f)

    specstring = fd.read()
    fd.close()

    ast = ipdl.parse(specstring, filename, includedirs=includedirs)
    if ast is None:
        print >>sys.stderr, 'Specification could not be parsed.'
        sys.exit(1)

    log(2, 'checking types')
    if not ipdl.typecheck(ast):
        print >>sys.stderr, 'Specification is not well typed.'
        sys.exit(1)

    if _verbosity > 2:
        log(3, '  pretty printed code:')
        ipdl.genipdl(ast, codedir)

# Second pass: generate code
for f in files:
    # Read from parser cache
    filename = normalizedFilename(f)
    ast = ipdl.parse(None, filename, includedirs=includedirs)
    ipdl.gencxx(filename, ast, headersdir, cppdir)

    if ast.protocol:
        allmessages[ast.protocol.name] = ipdl.genmsgenum(ast)
        allprotocols.append('%sMsgStart' % ast.protocol.name)

allprotocols.sort()

ipcmsgstart = StringIO()

print >>ipcmsgstart, """
// CODE GENERATED by ipdl.py. Do not edit.

#ifndef IPCMessageStart_h
#define IPCMessageStart_h

enum IPCMessageStart {
"""

for name in allprotocols:
    print >>ipcmsgstart, "  %s," % name
    print >>ipcmsgstart, "  %sChild," % name

print >>ipcmsgstart, """
  LastMsgIndex
};

static_assert(LastMsgIndex <= 65536, "need to update IPC_MESSAGE_MACRO");

#endif // ifndef IPCMessageStart_h
"""

ipc_msgtype_name = StringIO()
print >>ipc_msgtype_name, """
// CODE GENERATED by ipdl.py. Do not edit.
#include <cstdint>

#include "IPCMessageStart.h"

using std::uint32_t;

namespace {

enum IPCMessages {
"""

for protocol in sorted(allmessages.keys()):
    for (msg, num) in allmessages[protocol].idnums:
        if num:
            print >>ipc_msgtype_name, "  %s = %s," % (msg, num)
        elif not msg.endswith('End'):
            print >>ipc_msgtype_name,  "  %s__%s," % (protocol, msg)

print >>ipc_msgtype_name, """
};

} // anonymous namespace

namespace mozilla {
namespace ipc {

const char* StringFromIPCMessageType(uint32_t aMessageType)
{
  switch (aMessageType) {
"""

for protocol in sorted(allmessages.keys()):
    for (msg, num) in allmessages[protocol].idnums:
        if num or msg.endswith('End'):
            continue
        print >>ipc_msgtype_name, """
  case %s__%s:
    return "%s::%s";""" % (protocol, msg, protocol, msg)

print >>ipc_msgtype_name, """
  default:
    return "???";
  }
}

} // namespace ipc
} // namespace mozilla
"""

ipdl.writeifmodified(ipcmsgstart.getvalue(), ipcmessagestartpath)
ipdl.writeifmodified(ipc_msgtype_name.getvalue(), ipc_msgtype_name_path)