#!/usr/bin/env python
#
# Copyright (C) 2009 Anders Logg
#
# This file is part of DOLFIN.
#
# DOLFIN is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DOLFIN is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DOLFIN. If not, see <http://www.gnu.org/licenses/>.
#
# Check and fix coding style in all files (currently very limited)
#
# Modified by Chris Richardson, 2013
#
# Last changed: 2013-05-10
#

import os, sys, re, getopt

# Camelcaps exceptions
camelcaps_exceptions = ["cGq", "dGq",                                                         # ODE solver
                        "addListener", "addTest", "getRegistry", "makeTest", "wasSuccessful", # cppunit
                        "tagName", "tagNames", "documentElement", "childNodes",               # doscstrings
                        "nodeType", "firstChild", "parentNode",                               # docstrings
                        "doubleArray", "intArray",                                            # swig 
                        "fatalError", "xmlChar", "xmlNode", "xmlStrcasecmp",                  # libxml2
                        "startDocument", "endDocument", "startElement", "endElement"]         # libxml2


def camelcaps2underscore(word):
    "Convert fooBar to foo_bar."
    new_word = ""
    for c in word:
        if c.isupper() and len(new_word) > 0:
            new_word += "_" + c.lower()
        else:
            new_word += c
    return new_word

def check_camelcaps(code):
    "Check for fooBar (should be foo_bar)"
    problems=[]
    for i,line in enumerate(code.split("\n")):
        if(re.search(r"//",line)):
            continue
        words = re.findall(r"\b([a-z]+[A-Z][a-z]+)\b", line)
        for word in words:
            if word in camelcaps_exceptions:
                continue
            if re.match("vtk",word):
                continue
            problems.append("[camelCaps          ] Line %d: %s"%(i+1,line))
    return code, problems

def check_trailing_whitespace(code):
    "Remove trailing spaces at end of lines."
    problems = []
    lines = []
    for i,line in enumerate(code.split("\n")):
        new_line = line.rstrip()
        lines.append(new_line)
        if new_line == line:
            continue
        problems.append("[trailing whitespace] Line %d: %s"%(i+1,line.replace(" ","_").replace("\r","^M")))
    new_code = "\n".join(lines)
    if len(new_code) > 0 and not new_code[-1] == "\n":
        new_code += "\n"
    return new_code, problems

def check_tabs(code):
    "Check for TAB character"
    problems = []
    lines =[]
    for i,line in enumerate(code.split("\n")):
        if(re.search("\t",line)):
            problems.append("[tab                ] Line %d: %s"%(i+1,line.replace("\t",r"\t      ")))
            lines.append(re.sub("\t","        ",line))
        else:
            lines.append(line)

    new_code = "\n".join(lines)
    if len(new_code) > 0 and not new_code[-1] == "\n":
        new_code += "\n"
    return new_code, problems

def check_symbol_spacing(code):
    "Add spaces around +,-,= etc."
    problems = []
    for i,line in enumerate(code.split("\n")):
        if(re.search(r"//",line)):
            continue
        words = re.findall(r"\b[=|+|-|==]\b", line)
        if(len(words)>0):
            problems.append("[symbol spacing     ] Line %d: %s"%(i,line))
        words = re.findall(r"\b,\b", line)
        if(len(words)>0):
            problems.append("[comma spacing      ] Line %d: %s"%(i,line))
    return code, problems

def part_of_dolfin(code):
    "Look for DOLFIN in file"
    if re.search("DOLFIN",code):
        return True
    else:
        return False

def detect_bad_formatting(filename, opts):
    """Detect where code formatting appears to be wrong.
    opts Flags:
    -c for checking camelCaps
    -w for checking whitespace
    -s for checking symbol spacing, i.e. =, +, - etc.
    -wf to fix whitespace errors
    """

    # Open file
    f = open(filename, "r")
    code = f.read()
    f.close()
    
    if(not part_of_dolfin(code)):
        problems = ['[part of dolfin     ] Does not contain "DOLFIN" - skipping']
    else:
        # Checks
        problems = []
        if("-c" in opts):
            code, p = check_camelcaps(code)
            problems.extend(p)
        if("-w" in opts):
            code, p1 = check_trailing_whitespace(code)
            problems.extend(p1)
            code, p2 = check_tabs(code)
            problems.extend(p2)
            # Rewrite corrected file (only corrects whitespace errors)
            if("-f" in opts and (len(p1)!=0 or len(p2)!=0)):
                problems.append('Rewriting file %s'%filename)
                f = open(filename, "w")
                f.write(code)
                f.close()
        if("-s" in opts):
            code, p = check_symbol_spacing(code)
            problems.extend(p)

    if(len(problems) != 0):
        print "================================================================================"
        print filename
        print "================================================================================"
        for p in problems:
            print p


def usage(name):
    print
    print name," [-c|-w|-s|-f] path"
    print "\n Check formatting of code in the folders and subfolders of path\n"
    print " -c check camelCaps"
    print " -w check white space and tabs"
    print " -s check symbol spacing"
    print " -wf fix whitespace errors"
    print " default: check all"

if __name__ == "__main__":
    try:
        opts, args = getopt.getopt(sys.argv[1:], "cwsf")
    except getopt.GetoptError as err:
        print str(err) # will print something like "option -a not recognized"
        usage(sys.argv[0])
        sys.exit(0)

    if(len(args)==0):
        usage(sys.argv[0])
        sys.exit(0)
    filepath = args[0]

    options=[]
    for opt, arg in opts:
        options.append(opt)

    if len(options)==0:
        options=['-c','-w','-s']

    # Iterate over all source files
    for root, dirs, files in os.walk(filepath):
        for file in files:
            if not file.endswith((".cpp", ".h", ".py", ".i")):
                continue
            filename = os.path.join(root, file)
            detect_bad_formatting(filename, options)

