#!/usr/bin/env python
# *************************************************************
#  
#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you under the Apache License, Version 2.0 (the
#  "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#  
#    http://www.apache.org/licenses/LICENSE-2.0
#  
#  Unless required by applicable law or agreed to in writing,
#  software distributed under the License is distributed on an
#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#  KIND, either express or implied.  See the License for the
#  specific language governing permissions and limitations
#  under the License.
#  
# *************************************************************


# ------------------------------------------------------------------------------
# Hacky little delta debug tool to figure out the proper includes for a pch file
#
# Usage:
#
# pchdelta.py <pch_target> <dir1> [<dir2> <dir3> ...]
#
# <pch_target>      File to perform delta debugging on. The section to test
#                   is delimeted by '//---MARKER---' lines.
# <dir1> .. <dirn>  Sequence of directories to run dmake in to test if the
#                   modification works
#
# Examples:
#
# pchdelta.py inc/pch/precompiled_sfx2.hxx inc source/dialog
#
#  Run pchdelta inside sfx2 first building the pch files and then files in
# source/dialog
#
# ------------------------------------------------------------------------------

import os
import os.path
import sys

# C++
MARKER="//---MARKER---\n"

# dmake
#MARKER="#---MARKER---\n"

# ------------------------------------------------------------------------------
# Sequentially build all argument directories from scratch

def testSequenceBuild(dirlist):
    cwd = os.path.abspath(os.getcwd())
    for path in dirlist:
        os.chdir(path)
        buildcommand = "dmake -u"
        buildcommand += " >>" + cwd + "/buildlog.txt 2>&1"
        buildresult = os.system(buildcommand)
        os.chdir(cwd)
        if buildresult != 0:
            return False
    return True

# ------------------------------------------------------------------------------
# Dump out the delta file with corresponding markers

def writePch(pchname, header, footer, acceptedlines, testlines):
    outputfile = file(pchname, "w")
    outputfile.write(header)
    outputfile.write(MARKER)
    outputfile.write("\n".join(acceptedlines))
    if len(testlines) > 0:
        outputfile.write("\n\n//---Candidate marker---\n")
        outputfile.write("\n".join(testlines) + "\n")
        outputfile.write("//---Candidate marker end---\n")
    outputfile.write(MARKER)
    outputfile.write(footer)
    outputfile.close()


# ------------------------------------------------------------------------------
# Recursive tester routine. Test the segment given and if an error is
# encountered splits the segment into <fanout> subsegment and recurses. Failing
# one liners are rejected. The set of accepted lines are built sequentially from
# the beginning.

def binaryTest(dirlist, lines, pchname, header, footer, acceptedlines, indent, startpoint):
    linecount = len(lines)
    if linecount == 0:
        return
    # Test if this slice passes the buildtest
    writePch(pchname, header, footer, acceptedlines, lines)
    if testSequenceBuild(dirlist):
        return acceptedlines + lines

    # Reject one liners
    if linecount == 1:
        print indent + "Rejected: " + lines[0]
        return acceptedlines

    # Recurse with multiline slices
    fanout = 4
    splits = []
    for i in range(3):
        splits.append(linecount * (i + 1) / fanout)
    splits.append(linecount)

    splitstart = 0
    for splitend in splits:
        # avoid splitting in case we have no resulting lines
        if (splitend - splitstart) == 0:
            continue
        splitslice = lines[splitstart:splitend]
        print indent + "[" + str(startpoint + splitstart) + ":" + str(startpoint + splitend) + "] (" + str(splitend - splitstart) + ")"
        acceptedlines = binaryTest(dirlist, splitslice, pchname, header, footer, acceptedlines, indent + " ", startpoint + splitstart)
        splitstart = splitend

    return acceptedlines

# ------------------------------------------------------------------------------
# Main entry point

if len(sys.argv) < 3:
    print "Usage: " + sys.argv[0] + " <pch_target> <dir1> [<dir2> <dir3> ...]"
    sys.exit(1)

pchname = os.path.abspath(sys.argv[1])
dirlist = sys.argv[2:]

# remove old build log file
if os.path.exists("buildlog.txt"):
    os.remove("buildlog.txt")

# test for corner case of everything working from the start
if testSequenceBuild(dirlist):
    print "pch working, nothing to do."
    sys.exit(0)

# Open the header file for reading
inputfile = file(pchname, "r+")
inputdata = inputfile.read()
inputfile.close()

segments = inputdata.split(MARKER)
header = segments[0]
footer = segments[2]
lines = segments[1].split("\n")

writePch(pchname + "_backup", header, footer, lines, [])

# test for corner case of no convergence possible
writePch(pchname, header, footer, [], [])
if not testSequenceBuild(dirlist):
    writePch(pchname, header, footer, lines, [])
    print "Building with no candidate lines failed. Convergence questionable, aborting."
    sys.exit(0)

# Starting pruning
print "Starting evaluation of " + str(len(lines)) + " lines"
acceptedlines = binaryTest(dirlist, lines, pchname, header, footer, [], "", 0)
writePch(pchname, header, footer, acceptedlines, [])
