#####################################################################################
#
#  Copyright (c) Microsoft Corporation. All rights reserved.
#
# This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
# copy of the license can be found in the License.html file at the root of this distribution. If 
# you cannot locate the  Apache License, Version 2.0, please send an email to 
# ironpy@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
# by the terms of the Apache License, Version 2.0.
#
# You must not remove this notice, or any other, from this software.
#
#
#####################################################################################

import re
import sys
import nt
import os.path


def get_root_dir():
    script = os.path.realpath(__file__)
    return  os.path.normpath(os.path.join(os.path.dirname(script), "../../.."))

root_dir = get_root_dir()


source_directories = [
    root_dir + "\\Languages\\IronPython",
    root_dir + "\\Runtime",
]

exclude_directories = [
    root_dir + "\\runtime\\tests\\linqdlrtests",
]

START = "#region Generated %s"
END =   "#endregion"
PREFIX = r"^([ \t]*)"

#TODO: does it make sense to run this under CPython?
#try:
#    import os
#    listdir = os.listdir
#    pathjoin = os.path.join
#    isdir = os.path.isdir
#except Exception, e:
if sys.platform=="cli":
    import nt
    import System.IO        
        
    def pathjoin(dir, file):
        if(dir[-1] == '\\'):
            return dir+file
        return dir + "\\" + file
    
    def isdir(dir):
        return System.IO.Directory.Exists(dir)
        
    listdir = nt.listdir        

class ConditionWriter:
    def __init__(self, cw):
        self.cw = cw
        self.first = True

    def condition(self, text=None, **kw):
        if self.first:
            self.first = False
            self.cw.enter_block(text, **kw)
        else:
            self.cw.else_block(text, **kw)

    def close(self):
        if not self.first:
            self.cw.exit_block()

class CodeWriter:
    def __init__(self, indent=0):
        self.lines = []
        self.__indent = indent
        self.kws = {}

    def begin_generated(self, generator):
        self.writeline()
        self.writeline("// *** BEGIN GENERATED CODE ***")
        filename = System.IO.Path.GetFileName(generator.func_code.co_filename)
        self.writeline("// generated by function: " + generator.__name__ + " from: " + filename)

        self.writeline()

    def end_generated(self):
        self.writeline()
        self.writeline("// *** END GENERATED CODE ***")
        self.writeline()

    def indent(self): self.__indent += 1
    def dedent(self): self.__indent -= 1

    def writeline(self, text=None):
        if text is None or text.strip() == "":
            self.lines.append("")
        else:
            self.lines.append("    "*self.__indent + text)

    def write(self, template, **kw):
        if kw or self.kws:
            kw1 = self.kws.copy()
            kw1.update(kw)
            #print kw
            template = template % kw1
        for l in template.split('\n'):
            self.writeline(l)

    def enter_block(self, text=None, **kw):
        if text is not None:
            self.write(text + " {", **kw)
        self.indent()

    def else_block(self, text=None, **kw):
        self.dedent()
        if text:
            self.writeline("} else " + (text % kw) + " {")
        else:
            self.writeline("} else {")
        self.indent()
        
    def case_block(self, text=None, **kw):
        self.enter_block(text, **kw)
        self.indent()
        
    def case_label(self, text=None, **kw):
        self.write(text, **kw)
        self.indent()
        
    def exit_case_block(self):
        self.exit_block()
        self.dedent()

    def catch_block(self, text=None, **kw):
        self.dedent()
        if text:
            self.writeline("} catch " + (text % kw) + " {")
        else:
            self.writeline("} catch {")
        self.indent()

    def finally_block(self):
        self.dedent()
        self.writeline("} finally {")
        self.indent()

    def exit_block(self, text=None, **kw):
        self.dedent()
        if text:
            self.writeline("} " + text, **kw)
        else:
            self.writeline('}')

    def text(self):
        return '\n'.join(self.lines)

    def conditions(self):
        return ConditionWriter(self)

class CodeGenerator:
    def __init__(self, name, generator):
        self.generator = generator
        self.generators = []
        self.replacer = BlockReplacer(name)

    def do_file(self, filename):
        g = FileGenerator(filename, self.generator, self.replacer)
        if g.has_match:
            self.generators.append(g)

    def do_generate(self):
        if not self.generators:
            raise Exception("didn't find a match for %s" % self.replacer.name)
            
        result = []
        for g in self.generators:
            result.append(g.generate())
        return result

    def do_dir(self, dirname):
        if dirname.lower() in exclude_directories:
            return
        for file in listdir(dirname):            
            filename = pathjoin(dirname, file)
            if isdir(filename):
                self.do_dir(filename)
            elif filename.endswith(".cs"):
                self.do_file(filename)

    def doit(self):
        for src_dir in source_directories:
            self.do_dir(src_dir)
        for g in self.generators:
            g.collect_info()
        return self.do_generate()

class BlockReplacer:
    def __init__(self, name):
        self.start = START % name
        self.end = END# % name
        #self.block_pat = re.compile(PREFIX+self.start+".*?"+self.end,
        #                            re.DOTALL|re.MULTILINE)
        self.name = name

    def match(self, text):
        #m = self.block_pat.search(text)
        #if m is None: return None
        #indent = m.group(1)
        #return indent
        
        startIndex = text.find(self.start)
        if startIndex != -1:
            origStart = startIndex
            # go to the beginning of the line on which self.start appears
            startIndex = text.rfind('\n', 0, startIndex) + 1
            
            # Some simple parsing logic that allows us to use regions within
            # our generated code
            if self.end == '#endregion':
                start = origStart + len(self.start)
                regionIndex = -1
                endregionIndex = -1
                count = 0 # number of '#region' encountered minus '#endregion'
                while True:
                    regionIndex = text.find('#region', start)
                    endregionIndex = text.find('#endregion', start)
                    if (endregionIndex >= 0 and
                        (endregionIndex < regionIndex or regionIndex == -1)):
                        if count == 0:
                            endIndex = endregionIndex
                            break
                        else:
                            count -= 1
                            start = endregionIndex + len("#endregion")
                            continue
                    if (regionIndex >= 0 and
                        (regionIndex < endregionIndex or endregionIndex == -1)):
                        count += 1
                        start = regionIndex + len("#region")
                        continue
                    if regionIndex == -1 and endregionIndex == -1:
                        # occurrences of '#region' outnumber #endregion"
                        endIndex = -1
                        break
            else:
                endIndex = text.find(self.end, startIndex)
                
            if endIndex != -1:
                indent = text[startIndex:origStart]
                return (indent, startIndex, endIndex+len(self.end))
        
        return None
    
    def replace(self, cw, text, indent):
        code = cw.lines
        code.insert(0, self.start)
        code.append(self.end)
        
        def should_indent(line):
            if not line: return False
            if line.startswith("#region"): return True
            if line.startswith("#endregion"): return True
            if line.startswith("#"): return False
            return True
        
       #code_text = '\n' + indent
        code_text = indent[0]
        delim = False
        for line in code:
            if delim:
                code_text += "\n"
                if should_indent(line):
                    code_text += indent[0]
            code_text += line
            delim = True
        
        #return self.block_pat.sub(code_text, text)
        #indicies = self.match(text)
        
        res = text[0:indent[1]] + code_text + text[indent[2]:len(text)]
        return res

def save_file(name, text):
    f = open(name, 'w')
    f.write(text)
    f.close()

def texts_are_equivalent(texta, textb):
    """Compares two program texts by removing all identation and 
    blank lines first."""
    
    def normalized_lines(text):
        for l in text.splitlines():
            l = l.strip()
            if l:
                yield l
                
    texta = "\n".join(normalized_lines(texta))
    textb = "\n".join(normalized_lines(textb))
    return texta == textb
    
class FileGenerator:
    def __init__(self, filename, generator, replacer):
        self.filename = filename
        self.generator = generator
        self.replacer = replacer

        thefile = open(filename)
        self.text = thefile.read()
        thefile.close()
        self.indent = self.replacer.match(self.text)
        self.has_match = self.indent is not None

    def collect_info(self):
        pass
       
    def generate(self):
        print "generate",
        if sys.argv.count('checkonly') > 0:
            print "(check-only)",
        print self.filename, "...",

        cw = CodeWriter()
        cw.text = self.replacer.replace(CodeWriter(), self.text, self.indent)
        cw.begin_generated(self.generator)
        self.generator(cw)
        cw.end_generated()
        new_text = self.replacer.replace(cw, self.text, self.indent)
        if not texts_are_equivalent(self.text, new_text):
            if sys.argv.count('checkonly') > 0:
                print "different!"
                name = self.filename + ".diff"
                print "    generated file saved as: " + name
                save_file(name, new_text)
                return False
            else:
                print "updated"
                if sys.argv.count('checkout') > 0:
                    nt.spawnl(0, "tf.exe", "tf.exe", "edit", self.filename)
                save_file(self.filename, new_text)
        else:
            print "ok"

        return True

def generate(*g):
    result = []
    for name, func in g:
        run = CodeGenerator(name, func).doit()
        result.extend(run)
    return result
