#!/usr/bin/python3

import configparser
import argparse
import os
import re
import sys
import subprocess

#---------------------------------------------------------------------------
#---------------------------------------------------------------------------

def make_ast_all_h(namespace):
  decls = incls = ""
  nodes = subprocess.getoutput("find ast -name \\*.h -print | sed -e 's=ast/==g' | sort")

  for node in nodes.split("\n"):
    #print(f"[{node}]")
    if node == "all.h" or node == "visitor_decls.h":
        continue

    decl = incl = node
    #
    # Handle declaration
    #
    if namespace == "cdk" and decl == "literal_node.h":
        decl = "template <typename StoredType> class literal_node;"
    else:
        decl = re.sub(r'(([\w\d]|_)+)/', r'', decl)
        decl = re.sub(r'(([\w\d]|_)+?)\.h', r'class \1;', decl)
    decls = decls + f"{decl}\n"
    #
    # Handle include
    #
    if namespace == "cdk":
        incl = re.sub(r'(([\w\d]|_)+?)\.h', r'#include <cdk/ast/\1.h>', incl)
    else:
        incl = re.sub(r'^', r'#include "ast/', incl)
        incl = re.sub(r'$', r'"', incl)
    
    incls = incls + f"{incl}\n"
    
  # HACKS!!!

  cdkallinclude = ''
  if namespace != "cdk":
      cdkallinclude = "#include <cdk/ast/all.h>\n"

  ###########################################################################
  #
  # File "nodes/all.h" will now be produced.
  #
  return f"""
// AUTOMATICALLY GENERATED BY CDK -- DO NOT EDIT
//
{cdkallinclude}
#ifdef __NODE_DECLARATIONS_ONLY__

namespace {namespace} {{

{decls}

}} // namespace {namespace}

#else /* !defined(__NODE_DECLARATIONS_ONLY__) */

#ifndef __AUTOMATIC_{namespace}_NODE_ALLNODES_H__
#define __AUTOMATIC_{namespace}_NODE_ALLNODES_H__

{incls}

#endif /* !defined(__AUTOMATIC_{namespace}_NODE_ALLNODES_H__) */

#endif /* !defined(__NODE_DECLARATIONS_ONLY__) */
"""

###########################################################################

def make_ast_visitor_decls(namespace):
  decls = incls = ''

  nodes = subprocess.getoutput("find ast -name \\*.h -exec grep -H 'accept' {} \\; | grep -v basic_node | cut -d: -f1 | sed -e 's=^.*/==g' | sort")

  for node in nodes.split("\n"):
      if node == "all.h" or node == "visitor_decls.h":
          continue

      decl = incl = node

      # Handle declaration
      #
      decl = re.sub(r'(([\w\d]|_)+?)\.h', r'  virtual void do_\1(@@NAMESPACE::\1 *const node, int lvl) = 0;', decl)
      decls = decls + f"{decl}\n"

      # Handle include
      #
      incl = re.sub(r'(([\w\d]|_)+?)\.h', r'  void do_\1(@@NAMESPACE::\1 *const node, int lvl);', incl)
      incls = incls + f"{incl}\n"

  decls = re.sub(r'@@NAMESPACE', namespace, decls)
  incls = re.sub(r'@@NAMESPACE', namespace, incls)
  # HACKS!
  #
  cdkvisitorinclude = ''
  if namespace != "cdk":
      cdkvisitorinclude = "#include <cdk/ast/visitor_decls.h>\n"

  # File "nodes/all_visitor_decls.h" will now be produced.
  #
  return f"""
// AUTOMATICALLY GENERATED BY CDK -- DO NOT EDIT
{cdkvisitorinclude}
#ifdef __IN_VISITOR_HEADER__
#ifdef __PURE_VIRTUAL_DECLARATIONS_ONLY__
{decls}
#else
{incls}
#endif /* !defined(__PURE_VIRTUAL_DECLARATIONS_ONLY__) */
#endif /* __IN_VISITOR_HEADER__ */
"""

###########################################################################
#---------------------------------------------------------------------------
#---------------------------------------------------------------------------

config_file_name = os.path.expanduser('~/.config/cdk.ini')

def ensure_config_exists(config_file_name):
    confdir = os.path.expanduser("~/.config")
    if not os.path.isdir(confdir):
        os.makedirs(confdir)

    config = configparser.ConfigParser()
    if not os.path.isfile(config_file_name):
        config['defaults'] = {
            'cdk': os.path.expanduser('~/compiladores/root'),
            'system': 'no'
        }
        config['cvs'] = {'CVSROOT':'none'}
        with open(config_file_name, 'w') as configfile:
            config.write(configfile)
    else:
        with open(config_file_name) as configfile:
            config.read_file(configfile)
    return config

def config_load(filename):
    return ensure_config_exists(filename)

def config_save(filename):
    config = ensure_config_exists(filename)
    with open(filename, 'w') as configfile:
          config.write(configfile)

#---------------------------------------------------------------------------

config = config_load(config_file_name)

#---------------------------------------------------------------------------

def cdk_do_ast_cdk(config, args):
    whereami = os.path.dirname(os.path.realpath(__file__))
    for decl in args.decls:
        if decl == 'ast':
            #print(f"running {whereami}/mk-node-decls.pl cdk ooo")
            #os.system(f"{whereami}/mk-node-decls.pl cdk")
            print(make_ast_all_h("cdk"))
        else:
            #print(f"running {whereami}/mk-visitor-decls.pl cdk")
            #os.system(f"{whereami}/mk-visitor-decls.pl cdk")
            print(make_ast_visitor_decls("cdk"))

def cdk_do_ast_language(config, args):
    whereami = os.path.dirname(os.path.realpath(__file__))
    for decl in args.decls:
        if decl == 'ast':
            #print(f"running {whereami}/mk-node-decls.pl {args.language}")
            #os.system(f"{whereami}/mk-node-decls.pl {args.language}")
            print(make_ast_all_h(args.language))
        else:
            #print(f"running {whereami}/mk-visitor-decls.pl {args.language}")
            #os.system(f"{whereami}/mk-visitor-decls.pl {args.language}")
            print(make_ast_visitor_decls(args.language))

def cdk_do_target(config, args):
    whereami = os.path.dirname(os.path.realpath(__file__))
    for target in args.target:
        #print(f"running {whereami}/mk-visitor-skel.pl {args.language} {target}")
        os.system(f"{whereami}/mk-visitor-skel.pl {args.language} {target}")

#---------------------------------------------------------------------------

parser = argparse.ArgumentParser(prog="cdk", description='CDK command line interface.')

subparsers = parser.add_subparsers(help='CDK sub-commands.', dest="subcommand")

# create the parser for the "ast" command
parser_ast = subparsers.add_parser('ast', help='AST commands.')

required_a = parser_ast.add_argument_group('required arguments')
required_a.add_argument('--decls', action='append', choices=['ast', 'target'], help='generate declarations (multiple choice).', required=True)
group_ast = required_a.add_mutually_exclusive_group(required=True)
group_ast.add_argument('--cdk',      dest='in_cdk',   action='store_true', help="generate code for the CDK")
group_ast.add_argument('--language', dest='language', action='store',      help="generate code for given language")

# create the parser for the "target" command
parser_t = subparsers.add_parser('target', help='Generation target (visitor) commands.')
required_t = parser_t.add_argument_group('required arguments')
required_t.add_argument('--language', type=str, dest='language', action="store", help='Check LANGUAGE target', required=True)
required_t.add_argument('--target', type=str, dest='target', action="append", help='Which TARGET(s) to check (may be repeated)', required=True)

#---------------------------------------------------------------------------
### DO THINGS

args = parser.parse_args()

if args.subcommand == 'ast':
    if args.in_cdk:
        cdk_do_ast_cdk(config, args)
    else:
        cdk_do_ast_language(config, args)
elif args.subcommand == 'target':
    cdk_do_target(config, args)
else:
    parser.print_help()

