# These are the different types of code generation output for protobuf.
# Each one defines a number of attributes:
#   'outputs': keys into protobuf_outpath_exts for the files that are generated by the recipe.
#   'deps': list of Make dependencies of the list of output files
#   'recipe': actual Make recipe to generate the output files
# 'deps' and 'recipe' can contain named parameters that will be substituted.  See fmt_params dict below.
protobuf_output_recipes = [
    {
        'outputs': ['hpp_out', 'cpp_out'],
        'deps': '$(PROTOC)',
        'recipe':('\tmkdir -p $(PBROOT)/%(path)s%(name)s\n'
                  '\t$(PROTOC) -I%(input_path)s %(incs)s --cpp_out=$(PBROOT)/%(path)s%(name)s %(input_path)s/%(protofile)s\n'
                  '\tmkdir -p %(hpp_out)s %(cpp_out)s\n'
                  '\tcp -f $(PBROOT)/%(path)s%(name)s/%(protobase)s.pb.h %(hpp_out)s/\n'
                  '\tcp -f $(PBROOT)/%(path)s%(name)s/%(protobase)s.pb.cc %(cpp_out)s/\n'
                  '\tcd %(hpp_out)s && \\\n'
                  '  files="%(protobase)s.pb.h"; $(CVSIGNORE)\n'
                  '\tcd %(cpp_out)s && \\\n'
                  '  files="%(protobase)s.pb.cc"; $(CVSIGNORE)\n'
                 ),
    },
    {
        'outputs': ['java_out'],
        'deps': '$(PROTOC)',
        'recipe':('\tmkdir -p $(PBROOT)/%(path)s%(name)s\n'
                  '\tcat $(SRCROOT)/make/pb-lite.txt %(input_path)s/%(protofile)s > $(PBROOT)/%(path)s%(name)s/%(protobase)s.lite.proto\n'
                  '\tmkdir -p %(java_out)s\n'
                  '\t$(PROTOC) -I%(input_path)s -I$(PBROOT)/%(path)s%(name)s %(incs)s --java_out=%(java_out)s $(PBROOT)/%(path)s%(name)s/%(protobase)s.lite.proto\n'
                  '\tcd %(java_out)s && \\\n'
                  '  files="%(protobase)s.pb.java.timestamp"; $(CVSIGNORE) && \\\n'
                  '  touch  %(protobase)s.pb.java.timestamp\n'
                  # Similarly, it's too complicated to auto-generate all of the CVSIGNOREs; someone can
                  # just manually create the other .cvsignore files once and check them in.
                 )
    },
    {
        'outputs': ['python_out'],
        'deps': '$(PROTOC)',
        'recipe':('\tmkdir -p %(python_out)s\n'
                  '\t$(PROTOC) -I%(input_path)s %(incs)s --python_out=%(python_out)s %(input_path)s/%(protofile)s\n'
                  '\tcd %(python_out)s && \\\n'
                  '  files="%(protobase)s__pb2.py"; $(CVSIGNORE)\n'
                 )
    },
    {
        'outputs': ['csharp_out'],
        'deps': '$(PROTOC_DOTNET)',
        'recipe':('\tmkdir -p $(PBROOT)/%(path)s%(name)s\n'
                  '\t$(PROTOC) -I%(input_path)s %(incs)s %(input_path)s/%(protofile)s --include_imports --descriptor_set_out=$(PBROOT)/%(path)s%(name)s/%(protobase)s.pb.bin\n'
                  '\tmkdir -p %(csharp_out)s\n'
                  '\t$(PROTOC_DOTNET) -q -i:$(shell cygpath -m "$(PBROOT)/%(path)s%(name)s/%(protobase)s.pb.bin") -o:$(shell cygpath -m "%(csharp_out)s/%(protobase)s.pb.cs")\n'
                  '\tcd %(csharp_out)s && \\\n'
                  '  files="%(protobase)s.pb.cs"; $(CVSIGNORE)\n'
                 )
    },
    {
        'outputs': ['rpc_client_out'],
        'deps': '$(PROTORPCGEN)',
        'recipe':('\tmkdir -p %(rpc_client_out)s\n'
                  '\t$(PROTORPCGEN) -I%(input_path)s %(incs)s --client_out=%(rpc_client_out)s %(input_path)s/%(protofile)s\n'
                  '\tcd %(rpc_client_out)s && \\\n'
                  '  files="%(protobase)s-client.pb.h %(protobase)s-client.pb.cc"; $(CVSIGNORE)\n'
                 )
    },
    {
        'outputs': ['rpc_async_client_out'],
        'deps': '$(PROTORPCGEN)',
        'recipe':('\tmkdir -p %(rpc_async_client_out)s\n'
                  '\t$(PROTORPCGEN) -I%(input_path)s %(incs)s --async_client_out=%(rpc_async_client_out)s %(input_path)s/%(protofile)s\n'
                  '\tcd %(rpc_async_client_out)s && \\\n'
                  '  files="%(protobase)s-client-async.pb.h %(protobase)s-client-async.pb.cc"; $(CVSIGNORE)\n'
                 )
    },
    {
        'outputs': ['rpc_server_out'],
        'deps': '$(PROTORPCGEN)',
        'recipe':('\tmkdir -p %(rpc_server_out)s\n'
                  '\t$(PROTORPCGEN) -I%(input_path)s %(incs)s --server_out=%(rpc_server_out)s %(input_path)s/%(protofile)s\n'
                  '\tcd %(rpc_server_out)s && \\\n'
                  '  files="%(protobase)s-server.pb.h %(protobase)s-server.pb.cc"; $(CVSIGNORE)\n'
                 )
    },
    {
        'outputs': ['rpc_client_java_out'],
        'deps': '$(PROTORPCGEN)',
        'recipe':('\tmkdir -p %(rpc_client_java_out)s\n'
                  '\t$(PROTORPCGEN) -I%(input_path)s %(incs)s --client_java_out=%(rpc_client_java_out)s %(input_path)s/%(protofile)s\n'
                  '\tcd %(rpc_client_java_out)s && \\\n'
                  '  files="%(protobase)s-client.pb.java.timestamp"; $(CVSIGNORE) && \\\n'
                  '  touch  %(protobase)s-client.pb.java.timestamp\n'
                  # Similarly, it's too complicated to auto-generate all of the CVSIGNOREs; someone can
                  # just manually create the other .cvsignore files once and check them in.
                 )
    },
    {
        'outputs': ['rpc_client_csharp_out'],
        'deps': '$(PROTORPCGEN)',
        'recipe':('\tmkdir -p %(rpc_client_csharp_out)s\n'
                  '\t$(PROTORPCGEN) -I%(input_path)s %(incs)s --client_csharp_out=%(rpc_client_csharp_out)s %(input_path)s/%(protofile)s\n'
                  '\tcd %(rpc_client_csharp_out)s && \\\n'
                  '  files="%(protobase)s-client.pb.cs"; $(CVSIGNORE)\n'
                 )
    },
    {
        'outputs': ['xmlu_out'],
        'deps': '$(PROTORPCGEN)',
        'recipe':('\tmkdir -p %(xmlu_out)s\n'
                  '\t$(PROTORPCGEN) -I%(input_path)s %(incs)s --xmlu_out=%(xmlu_out)s %(input_path)s/%(protofile)s\n'
                  '\tcd %(xmlu_out)s && \\\n'
                  '  files="%(protobase)s-xml.pb.h %(protobase)s-xml.pb.cc"; $(CVSIGNORE)\n'
                 )
    }
]

# Output paths and extensions for the files that can be generated.
#   key: corresponds to the protobuf makegen target constructor param that enables this output type
#   value: suffixes of output files generated, appended to the input file without the '.proto' extension
protobuf_outpath_exts = {
    'hpp_out': ['.pb.h'],
    'cpp_out': ['.pb.cc'],
    # It's tough to determine the actual output file, so we use a timestamp file instead.
    # (The output Java file can end up in a nested package subdirectory and the name is modified
    # to match Java conventions.)
    'java_out': ['.pb.java.timestamp'], 
    'python_out': ['__pb2.py'],
    'csharp_out': ['.pb.cs'],
    'rpc_client_out':['-client.pb.h','-client.pb.cc'],
    'rpc_async_client_out':['-client-async.pb.h','-client-async.pb.cc'],
    'rpc_server_out':['-server.pb.h','-server.pb.cc'],
    # See comments for "java_out" above.
    'rpc_client_java_out':['-client.pb.java.timestamp'],
    'rpc_client_csharp_out':['-client.pb.cs'],
    'xmlu_out':['-xml.pb.h','-xml.pb.cc'],
}


class protobuf(build_target):
    def __init__(self, name, protofile, input_path='.', deps=[],
            hpp_out=None, cpp_out=None, java_out=None,
            python_out=None, csharp_out=None,
            rpc_client_out=None, rpc_async_client_out=None,
            rpc_server_out=None,
            rpc_client_java_out=None,
            rpc_client_csharp_out=None,
            xmlu_out=None):
        super(protobuf, self).__init__(
                ttype = 'protobuf', name = name, tdeps = deps,
                outs = [], builddirs = [], conflicts_with = [])
        assert_string(protofile, 'protofile', self.fullname)
        assert_string(input_path, 'input_path', self.fullname)
        if (hpp_out != None) and (cpp_out == None):
            raise Exception('(In ' + self.fullname + ') If you specify hpp_out, you must specify cpp_out')
        self.exported_cpp_includes = []
        
        if hpp_out == None:
            # Default to support original behavior.
            hpp_out = cpp_out
        
        self.outpaths = {
                'hpp_out':hpp_out,
                'cpp_out':cpp_out,
                'java_out':java_out,
                'python_out':python_out,
                'csharp_out':csharp_out,
                'rpc_client_out':rpc_client_out,
                'rpc_async_client_out':rpc_async_client_out,
                'rpc_server_out':rpc_server_out,
                'rpc_client_java_out':rpc_client_java_out,
                'rpc_client_csharp_out':rpc_client_csharp_out,
                'xmlu_out':xmlu_out
            }
        
        self.protofile = protofile
        if not protofile.endswith('.proto'):
            raise Exception('(In ' + self.fullname + ') Proto file ' + protofile + ' does not end in .proto')
        self.protobase = protofile[:-6]
        self.raw_input_path = input_path
        self.protoc_includes = [self.srcPathToMakefilePath(self.raw_input_path)]
        atLeastOne = False
        for currPathKey in sorted(protobuf_outpath_exts.keys()):
            currOutpath = self.outpaths[currPathKey]
            if currOutpath != None:
                atLeastOne = True
                currOutpath = self.srcPathToMakefilePath(currOutpath)
                self.outpaths[currPathKey] = currOutpath
                for ext in protobuf_outpath_exts[currPathKey]:
                    self.outs.append(currOutpath + '/' + self.protobase + ext)
        if not atLeastOne:
            raise Exception('(In ' + self.fullname + ') protobuf target must contain at least one output type')
        if self.outpaths['hpp_out'] != None:
            self.exported_cpp_includes.append(self.outpaths['hpp_out'])
    def get_exported_cpp_includes(self):
        return self.exported_cpp_includes
    def process_recipe(self, currRecipe):
        outfiles_short = []
        outfiles = []
        for currPathKey in currRecipe['outputs']:
            currOutpath = self.outpaths[currPathKey]
            if currOutpath == None:
                return ''
            exts = protobuf_outpath_exts[currPathKey]
            outfiles_short += [(self.protobase + ext) for ext in exts]
            outfiles += [(currOutpath + '/' + self.protobase + ext) for ext in exts]
        return str.join(' ', outfiles) + ': ' +\
                    self.srcPathToMakefilePath(self.raw_input_path + '/' + self.protofile) +\
                    ' $(DEFS_FILES) $(SRCROOT)/make/makegen/protobuf.py $(SRCROOT)/%(path)sBUILD ' +\
                    currRecipe['deps'] + '\n' +\
                    '\t###\n' +\
                    '\t# %(protofile)s => ' + str.join(', ', outfiles_short) + '\n' +\
                    '\t###\n' +\
                    currRecipe['recipe']
    def gen_make(self, makefile):
        prereqs = getPrereqs(self)
        fmt_str = '.PHONY: %(path)s%(name)s\n' \
                  '%(path)s%(name)s: %(outs)s\n' \
                  '%(outs)s:| %(prereqs)s\n'
        for currRecipe in protobuf_output_recipes:
            fmt_str += self.process_recipe(currRecipe)
            
        all_protoc_incs = []
        sorted_targets = sortDeps(self, True, include_types=['protobuf'])
        for curr in sorted_targets:
            all_protoc_incs.extend(curr.protoc_includes)
        all_protoc_incs = remove_duplicates(all_protoc_incs)
        fmt_params = {
                'token': self.token,
                'prereqs': str.join(' ', prereqs),
                'outs': str.join(' ', self.outs),
                'path': self.path,
                'name': self.name,
                'protofile': self.protofile,
                'protobase': self.protobase,
                'input_path': self.srcPathToMakefilePath(self.raw_input_path),
                'incs': str.join(' ', ['-I' + ipath for ipath in all_protoc_incs]),
            }
        fmt_params.update(self.outpaths)
        makefile.write(fmt_str % fmt_params)
