# Allen Institute Software License - This software license is the 2-clause BSD
# license plus a third clause that prohibits redistribution for commercial
# purposes without further permission.
#
# Copyright 2017. Allen Institute. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Redistributions for commercial purposes are not permitted without the
# Allen Institute's written permission.
# For purposes of this license, commercial purposes is the incorporation of the
# Allen Institute's software into anything for which you will charge fees or
# other compensation. Contact terms@alleninstitute.org for commercial licensing
# opportunities.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
import argparse
import allensdk.core.swc as swc
try:
    xrange
except:
    from past.builtins import xrange


def validate_swc(swc_file):
    """
    To be compatible with NEURON, SWC files must have the following properties:
        1) a single root node with parent ID '-1'
        2) sequentially increasing ID numbers
        3) immediate children of the soma cannot branch
    """
    soma_id = swc.Morphology.SOMA
    morphology = swc.read_swc(swc_file)
    # verify that there is a single root node
    num_soma_nodes = sum([(int(c['type']) == soma_id)
                          for c in morphology.compartment_list])
    if num_soma_nodes != 1:
        raise Exception(
            "SWC must have single soma compartment.  Found: %d" % num_soma_nodes)
    # sanity check
    root = morphology.root
    if root is None:
        raise Exception("Morphology has no root node")
    # verify that children of the root have max one child
    for root_child_id in root['children']:
        root_child = morphology.compartment_index[root_child_id]
        num_grand_children = len(root_child['children'])
        if num_grand_children > 1:
            raise Exception("Child of root (%s) has more than one child (%d)" % (
                root_child_id, num_grand_children))
    # get a list of all of the ids, make sure they are unique while we're at it
    all_ids = set()
    for compartment in morphology.compartment_list:
        iid = int(compartment["id"])
        if iid in all_ids:
            raise Exception("Compartment ID %s is not unique." %
                            compartment["id"])
        pid = int(compartment["parent"])
        if iid < pid:
            raise Exception(
                "Compartment (%d) has a smaller ID that its parent (%d)" % (iid, pid))
        all_ids.add(iid)

    # sort the ids and make sure there are no gaps
    sorted_ids = sorted(all_ids)
    for i in xrange(1, len(sorted_ids)):
        if sorted_ids[i] - sorted_ids[i - 1] != 1:
            raise Exception("Compartment IDs are not sequential")
    return True


def main():
    try:
        parser = argparse.ArgumentParser(
            "validate an SWC file for use with NEURON")
        parser.add_argument('swc_file')
        args = parser.parse_args()
        validate_swc(args.swc_file)
    except Exception as e:
        print(str(e))
        exit(1)
if __name__ == "__main__":
    main()
