# -*- coding: utf-8 -*-
#
#
# TheVirtualBrain-Framework Package. This package holds all Data Management, and
# Web-UI helpful to run brain-simulations. To use it, you also need do download
# TheVirtualBrain-Scientific Package (for simulators). See content of the
# documentation-folder for more details. See also http://www.thevirtualbrain.org
#
# (c) 2012-2020, Baycrest Centre for Geriatric Care ("Baycrest") and others
#
# This program is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
# This program 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 General Public License for more details.
# You should have received a copy of the GNU General Public License along with this
# program.  If not, see <http://www.gnu.org/licenses/>.
#
#
#   CITATION:
# When using The Virtual Brain for scientific publications, please cite it as follows:
#
#   Paula Sanz Leon, Stuart A. Knock, M. Marmaduke Woodman, Lia Domide,
#   Jochen Mersmann, Anthony R. McIntosh, Viktor Jirsa (2013)
#       The Virtual Brain: a simulator of primate brain network dynamics.
#   Frontiers in Neuroinformatics (7:10. doi: 10.3389/fninf.2013.00010)
#
#

"""
This is a diagnostic tool that is used to find file leaks
Usage :

import from this module the  monkey_patch_file_to_trace function and call it very early in the program
Once the trace is collected run  python diagnose_file_trace.py [filter_path_prefix]> analysed_trace then study the analysed_trace file
"""

import __builtin__
import traceback
from datetime import datetime


def timestamp():
    return datetime.now().strftime("%H:%M:%S.%f")


def monkey_patch_file_to_trace(trace_file='file_open_trace'):
    _file_open_trace = open(trace_file, 'w', buffering=0)

    # openfiles = set()
    global oldfile
    global oldopen

    oldfile = __builtin__.file

    class newfile(oldfile):
        def __init__(self, *args):
            self.x = args[0]
            _file_open_trace.write("$$ %s OPENING %s\n" % (timestamp(), str(self.x)))
            # traceback module will open python files to get the trace. We must exclude them from tracing otherwise stackoverflow
            if not self.x.endswith('py'):
                _file_open_trace.write(''.join(traceback.format_stack()))
            oldfile.__init__(self, *args)
            # openfiles.add(self)

        def close(self):
            _file_open_trace.write("$$ %s CLOSING %s\n" % (timestamp(), str(self.x)))
            if not self.x.endswith('py'):
                _file_open_trace.write(''.join(traceback.format_stack()))
            oldfile.close(self)
            # openfiles.remove(self)

    oldopen = __builtin__.open

    def newopen(*args):
        return newfile(*args)

    __builtin__.file = newfile
    __builtin__.open = newopen


def restore_monkey_patch():
    __builtin__.file = oldfile
    __builtin__.open = oldopen


def parse_trace():
    result = []

    with open('file_open_trace') as f:
        for line in f:
            if line.startswith("$$"):
                idx1 = line.find(' ')
                idx2 = line.find(' ', idx1 + 1)
                idx3 = line.find(' ', idx2 + 1)

                time = line[idx1 + 1:idx2]
                op = line[idx2 + 1:idx3]
                pth = line[idx3 + 1:-1]

                result.append((pth, op, time, []))
            else:
                result[-1][3].append(line)

    return result


def analyse_trace(filter_path_prefix=None):
    trace = parse_trace()

    if filter_path_prefix is not None:
        trace = [a for a in trace if a[0].startswith(filter_path_prefix)]

    result = {}

    for pth, op, time, stack in trace:
        if pth not in result:
            result[pth] = 0
        if op == "OPENING":
            result[pth] += 1
        else:
            result[pth] -= 1

    print()
    print('  List of files with unbalanced open close calls ')
    print('=================================================')
    print()

    for guilty_pth in result:
        if result[guilty_pth] != 0:
            print('%s %d' % (guilty_pth, result[guilty_pth]))
    print()
    print(' TRACES ')
    print('========')
    print()

    for guilty_pth in result:
        if result[guilty_pth] != 0:
            print()
            print('=' * 80)
            print('file: %s ' % guilty_pth)
            print('open-close: %d' % result[guilty_pth])
            print('printing access log')
            print()

            count = 0
            for pth, op, time, stack in trace:
                if pth == guilty_pth:
                    if op == "OPENING":
                        count += 1
                    else:
                        count -= 1

                    print('%s  %s %d -----------' % (time, op, count))
                    for st in stack:
                        print st,


if __name__ == '__main__':
    import sys

    if len(sys.argv) == 2:
        analyse_trace(sys.argv[1])
    else:
        analyse_trace()