#!/usr/bin/env node

// Do not modify this casually!  The loccount script uses this for
// analyzing the output of a run on the Make-A-Lisp repository.

// In the makefile, this is run in its normal mode under "make check"
// to list files that are identified in a way that does not match their
// mal implementation directory.
//
// "make check-mal-missing" runs the script in VERBOSE mode, gathers up the
// GOOD results, counts the number of hits in each mal implementation
// directory, and reports the ones with 5 or less hits. This is a good
// indication that loccount doesn't know how to count that
// implementation/language yet.

const { promisify } = require('util')
const fs = require('fs')
const exec = promisify(require('child_process').exec)
const VERBOSE = process.env['VERBOSE']
const LOCCOUNT = process.env['LOCCOUNT'] || 'loccount'
const re_extras = /\.sh shell|\.css CSS|\.h C-header|\.md Markdown|\.json JSON|\/tests\/.*\.mal mal /
const re_loc = /([^\/]+)\/([^ ]+) ([^ ]+) ([0-9]+) ([0-9]+)/

// implementation directory to file type mapping
const impl_kind = {
    'bash':          'shell',
    'coffee':        'CoffeeScript',
    'common-lisp':   'Lisp',
    'cs':            'C#',
    'cpp':           'C++',
    'es6':           'JavaScript',
    'fsharp':        'F#',
    'gnu-smalltalk': 'Smalltalk',
    'guile':         'Scheme',
    'js':            'JavaScript',
    'mal':           'Lisp',
    'make':          'makefile',
    'miniMAL':       'JSON',
    'nasm':          'asm',
    'objc':          'Objective-C',
    'objpascal':     'Pascal',
    'ocaml':         'ML',
    'perl6':         'Perl',
    'picolisp':      'Lisp',
    'plpgsql':       'SQL',
    'plsql':         'SQL',
    'ps':            'PostScript',
    'r':             'R',
    'rpython':       'Python',
    'swift3':        'Swift',
    'swift4':        'Swift',
    'ts':            'Typescript',
    'vb':            'VisualBasic',
    'wasm':          'WebAssembly',
}

// files that occur in multiple implementations and are a different
// type from the implementation directory
const file_kind = {
    'Makefile':            'makefile',
    'run':                 'shell',
    'node_readline.js':    'JavaScript',
    'tests/step5_tco.mal': 'mal',
    'tests/stepA_mal.mal': 'mal',
}

// specific files paths that are a different type from the
// implementation directory
const path_kind = {
    'basic/basicpp.py':                 'Python',
    'clojure/src/mal/node_readline.js': 'JavaScript',
    'clojure/src/mal/readline.cljs':    'ClojureScript',
    'dart/pubspec.yaml':                'YAML',
    'elm/bootstrap.js':                 'JavaScript',
    'fsharp/terminal.cs':               'C#',
    'java/pom.xml':                     'XML',
    'logo/examples/tree.mal':           'mal',
    'objc/mal_readline.c':              'C',
    'rust/Cargo.toml':                  'TOML',
    'vb/getline.cs':                    'C#',
    'vimscript/vimextras.c':            'C',
    'wasm/run.js':                      'JavaScript',
}


const vlog = (...a) => VERBOSE ? console.log(...a) : null
const loccount = fs.realpathSync(LOCCOUNT)

// Process arguments
if (process.argv.length < 3) {
    console.error(`Usage: ${process.argv[1]} MAL_PATH`)
    process.exit(2)
}
const malPath = process.argv[2]
if (!fs.existsSync(`${malPath}/mal/stepA_mal.mal`)) {
    console.error(`No mal directory at ${malPath}`)
    process.exit(2)
}

process.chdir(malPath)

async function main() {
    const implSet = new Set((await exec(`make -s print-IMPLS`)).stdout
            .split(/\s/)
            .filter(x => x))
    const lines = (await exec(`${loccount} -i ./`)).stdout
        .split('\n')
        .filter(x => x)

    let unknowns = 0
    for (let line of lines) {
        if (!(implSet.has(line.split('/')[0]))) {
            vlog(`IGNORE            : ${line}`)
            continue
        }
        const res = re_loc.exec(line)
        if (!res) {
            console.error(`Could not parse line: '${line}'`)
            process.exit(1)
        }
        const [match, impl, file, kind, sloc, lloc] = res
        if (impl === kind.toLowerCase()) {
            vlog(`GOOD (impl dir)   : ${impl}/${file} (${kind})`)
        } else if (impl_kind[impl] === kind) {
            vlog(`GOOD (impl map)   : ${impl}/${file} (${kind})`)
        } else if (file_kind[file] === kind) {
            vlog(`GOOD (file map)   : ${impl}/${file} (${kind})`)
        } else if (path_kind[impl+'/'+file] === kind) {
            vlog(`GOOD (path map)   : ${impl}/${file} (${kind})`)
        } else if (re_extras.exec(line)) {
            vlog(`GOOD (path regex) : ${line}`)
        } else {
            console.warn(`UNKNOWN           : ${line}`)
            unknowns += 1
        }
    }
    if (unknowns) {
        console.error(`Found ${unknowns} unknown files`)
        process.exit(1)
    }
}

Main()
