#!/usr/bin/python
# vim: set fileencoding=utf-8:

from __future__ import print_function, unicode_literals, division

from clang.cindex import Index, CursorKind
from collections import namedtuple, OrderedDict, defaultdict
import sys
import os


DECL_KINDS = {
  CursorKind.FUNCTION_DECL,
}


Strip = namedtuple('Strip', 'start_line start_column end_line end_column')


def main(progname, cfname, only_static, move_all):
  only_static = False

  cfname = os.path.abspath(os.path.normpath(cfname))

  hfname1 = os.path.splitext(cfname)[0] + os.extsep + 'h'
  hfname2 = os.path.splitext(cfname)[0] + '_defs' + os.extsep + 'h'

  files_to_modify = (cfname, hfname1, hfname2)

  index = Index.create()
  src_dirname = os.path.join(os.path.dirname(__file__), '..', 'src')
  src_dirname = os.path.abspath(os.path.normpath(src_dirname))
  relname = os.path.join(src_dirname, 'nvim')
  unit = index.parse(cfname, args=('-I' + src_dirname,
                                   '-DUNIX',
                                   '-DEXITFREE',
                                   '-DFEAT_USR_CMDS',
                                   '-DFEAT_CMDL_COMPL',
                                   '-DFEAT_COMPL_FUNC',
                                   '-DPROTO',
                                   '-DUSE_MCH_ERRMSG'))
  cursor = unit.cursor

  tostrip = defaultdict(OrderedDict)
  definitions = set()

  for child in cursor.get_children():
    if not (child.location and child.location.file):
      continue
    fname = os.path.abspath(os.path.normpath(child.location.file.name))
    if fname not in files_to_modify:
      continue
    if child.kind not in DECL_KINDS:
      continue
    if only_static and next(child.get_tokens()).spelling == 'static':
      continue

    if child.is_definition() and fname == cfname:
      definitions.add(child.spelling)
    else:
      stripdict = tostrip[fname]
      assert(child.spelling not in stripdict)
      stripdict[child.spelling] = Strip(
        child.extent.start.line,
        child.extent.start.column,
        child.extent.end.line,
        child.extent.end.column,
      )

  for (fname, stripdict) in tostrip.items():
    if not move_all:
      for name in set(stripdict) - definitions:
        stripdict.pop(name)

    if not stripdict:
      continue

    if fname.endswith('.h'):
      is_h_file = True
      include_line = next(reversed(stripdict.values())).start_line + 1
    else:
      is_h_file = False
      include_line = next(iter(stripdict.values())).start_line

    lines = None
    generated_existed = os.path.exists(fname + '.generated.h')
    with open(fname, 'rb') as F:
      lines = list(F)

    stripped = []

    for name, position in reversed(stripdict.items()):
      sl = slice(position.start_line - 1, position.end_line)
      if is_h_file:
        include_line -= sl.stop - sl.start
      stripped += lines[sl]
      lines[sl] = ()

    if not generated_existed:
      lines[include_line:include_line] = [
        '#ifdef INCLUDE_GENERATED_DECLARATIONS\n',
        '# include "{0}.generated.h"\n'.format(os.path.relpath(fname, relname)),
        '#endif\n',
      ]

    with open(fname, 'wb') as F:
      F.writelines(lines)


if __name__ == '__main__':
  progname = sys.argv[0]
  args = sys.argv[1:]
  if not args or '--help' in args:
    print('Usage:')
    print('')
    print('  {0} [--static [--all]] file.c...'.format(progname))
    print('')
    print('Stripts all declarations from file.c, file.h and file_defs.h.')
    print('If --static argument is given then only static declarations are')
    print('stripped. Declarations are stripped only if corresponding')
    print('definition is found unless --all argument was given.')
    print('')
    print('Note: it is assumed that static declarations starts with "static"')
    print('      keyword.')
    sys.exit(0 if args else 1)

  if args[0] == '--static':
    only_static = True
    args = args[1:]
  else:
    only_static = False

  if args[0] == '--all':
    move_all = True
    args = args[1:]
  else:
    move_all = False

  for cfname in args:
    print('Processing {0}'.format(cfname))
    main(progname, cfname, only_static, move_all)
