#!/usr/bin/env python3
"""Export the Slipnet structure to a JSON file.

This script is self-contained to avoid Python version compatibility issues
with the main copycat package.
"""

import json


class Slipnode:
    """Minimal Slipnode for export purposes."""

    def __init__(self, slipnet, name, depth, length=0.0):
        self.slipnet = slipnet
        self.name = name
        self.conceptualDepth = depth
        self.intrinsicLinkLength = length
        self.shrunkLinkLength = length * 0.4
        self.codelets = []
        self.categoryLinks = []
        self.instanceLinks = []
        self.propertyLinks = []
        self.lateralSlipLinks = []
        self.lateralNonSlipLinks = []
        self.incomingLinks = []
        self.outgoingLinks = []


class Sliplink:
    """Minimal Sliplink for export purposes."""

    def __init__(self, source, destination, label=None, length=0.0):
        self.source = source
        self.destination = destination
        self.label = label
        self.fixedLength = length
        source.outgoingLinks.append(self)
        destination.incomingLinks.append(self)


class SlipnetExporter:
    """Recreates the Slipnet structure for export."""

    def __init__(self):
        self.slipnodes = []
        self.sliplinks = []
        self._addInitialNodes()
        self._addInitialLinks()

    def _addNode(self, name, depth, length=0):
        slipnode = Slipnode(self, name, depth, length)
        self.slipnodes.append(slipnode)
        return slipnode

    def _addLink(self, source, destination, label=None, length=0.0):
        link = Sliplink(source, destination, label=label, length=length)
        self.sliplinks.append(link)
        return link

    def _addSlipLink(self, source, destination, label=None, length=0.0):
        link = self._addLink(source, destination, label, length)
        source.lateralSlipLinks.append(link)

    def _addNonSlipLink(self, source, destination, label=None, length=0.0):
        link = self._addLink(source, destination, label, length)
        source.lateralNonSlipLinks.append(link)

    def _addBidirectionalLink(self, source, destination, length):
        self._addNonSlipLink(source, destination, length=length)
        self._addNonSlipLink(destination, source, length=length)

    def _addCategoryLink(self, source, destination, length):
        link = self._addLink(source, destination, None, length)
        source.categoryLinks.append(link)

    def _addInstanceLink(self, source, destination, length=100.0):
        categoryLength = source.conceptualDepth - destination.conceptualDepth
        self._addCategoryLink(destination, source, categoryLength)
        link = self._addLink(source, destination, None, length)
        source.instanceLinks.append(link)

    def _addPropertyLink(self, source, destination, length):
        link = self._addLink(source, destination, None, length)
        source.propertyLinks.append(link)

    def _addOppositeLink(self, source, destination):
        self._addSlipLink(source, destination, label=self.opposite)
        self._addSlipLink(destination, source, label=self.opposite)

    def _link_items_to_their_neighbors(self, items):
        previous = items[0]
        for item in items[1:]:
            self._addNonSlipLink(previous, item, label=self.successor)
            self._addNonSlipLink(item, previous, label=self.predecessor)
            previous = item

    def _addInitialNodes(self):
        self.letters = []
        for c in 'abcdefghijklmnopqrstuvwxyz':
            slipnode = self._addNode(c, 10.0)
            self.letters.append(slipnode)

        self.numbers = []
        for c in '12345':
            slipnode = self._addNode(c, 30.0)
            self.numbers.append(slipnode)

        # string positions
        self.leftmost = self._addNode('leftmost', 40.0)
        self.rightmost = self._addNode('rightmost', 40.0)
        self.middle = self._addNode('middle', 40.0)
        self.single = self._addNode('single', 40.0)
        self.whole = self._addNode('whole', 40.0)

        # alphabetic positions
        self.first = self._addNode('first', 60.0)
        self.last = self._addNode('last', 60.0)

        # directions
        self.left = self._addNode('left', 40.0)
        self.left.codelets += ['top-down-bond-scout--direction']
        self.left.codelets += ['top-down-group-scout--direction']
        self.right = self._addNode('right', 40.0)
        self.right.codelets += ['top-down-bond-scout--direction']
        self.right.codelets += ['top-down-group-scout--direction']

        # bond types
        self.predecessor = self._addNode('predecessor', 50.0, 60.0)
        self.predecessor.codelets += ['top-down-bond-scout--category']
        self.successor = self._addNode('successor', 50.0, 60.0)
        self.successor.codelets += ['top-down-bond-scout--category']
        self.sameness = self._addNode('sameness', 80.0)
        self.sameness.codelets += ['top-down-bond-scout--category']

        # group types
        self.predecessorGroup = self._addNode('predecessorGroup', 50.0)
        self.predecessorGroup.codelets += ['top-down-group-scout--category']
        self.successorGroup = self._addNode('successorGroup', 50.0)
        self.successorGroup.codelets += ['top-down-group-scout--category']
        self.samenessGroup = self._addNode('samenessGroup', 80.0)
        self.samenessGroup.codelets += ['top-down-group-scout--category']

        # other relations
        self.identity = self._addNode('identity', 90.0)
        self.opposite = self._addNode('opposite', 90.0, 80.0)

        # objects
        self.letter = self._addNode('letter', 20.0)
        self.group = self._addNode('group', 80.0)

        # categories
        self.letterCategory = self._addNode('letterCategory', 30.0)
        self.stringPositionCategory = self._addNode('stringPositionCategory', 70.0)
        self.stringPositionCategory.codelets += ['top-down-description-scout']
        self.alphabeticPositionCategory = self._addNode('alphabeticPositionCategory', 80.0)
        self.alphabeticPositionCategory.codelets += ['top-down-description-scout']
        self.directionCategory = self._addNode('directionCategory', 70.0)
        self.bondCategory = self._addNode('bondCategory', 80.0)
        self.groupCategory = self._addNode('groupCategory', 80.0)
        self.length = self._addNode('length', 60.0)
        self.objectCategory = self._addNode('objectCategory', 90.0)
        self.bond_edge_type = self._addNode('bond_edge_type', 90.0)

        self.initiallyClampedSlipnodes = [
            self.letterCategory,
            self.stringPositionCategory,
        ]

    def _addInitialLinks(self):
        self._link_items_to_their_neighbors(self.letters)
        self._link_items_to_their_neighbors(self.numbers)

        # letter categories
        for letter in self.letters:
            self._addInstanceLink(self.letterCategory, letter, 97.0)
        self._addCategoryLink(self.samenessGroup, self.letterCategory, 50.0)

        # lengths
        for number in self.numbers:
            self._addInstanceLink(self.length, number)

        groups = [self.predecessorGroup, self.successorGroup, self.samenessGroup]
        for group in groups:
            self._addNonSlipLink(group, self.length, length=95.0)

        opposites = [
            (self.first, self.last),
            (self.leftmost, self.rightmost),
            (self.left, self.right),
            (self.successor, self.predecessor),
            (self.successorGroup, self.predecessorGroup),
        ]
        for a, b in opposites:
            self._addOppositeLink(a, b)

        # properties
        self._addPropertyLink(self.letters[0], self.first, 75.0)
        self._addPropertyLink(self.letters[-1], self.last, 75.0)

        links = [
            # object categories
            (self.objectCategory, self.letter),
            (self.objectCategory, self.group),
            # string positions
            (self.stringPositionCategory, self.leftmost),
            (self.stringPositionCategory, self.rightmost),
            (self.stringPositionCategory, self.middle),
            (self.stringPositionCategory, self.single),
            (self.stringPositionCategory, self.whole),
            # alphabetic positions
            (self.alphabeticPositionCategory, self.first),
            (self.alphabeticPositionCategory, self.last),
            # direction categories
            (self.directionCategory, self.left),
            (self.directionCategory, self.right),
            # bond categories
            (self.bondCategory, self.predecessor),
            (self.bondCategory, self.successor),
            (self.bondCategory, self.sameness),
            # group categories
            (self.groupCategory, self.predecessorGroup),
            (self.groupCategory, self.successorGroup),
            (self.groupCategory, self.samenessGroup),
            # bond edge types
            (self.bond_edge_type, self.letterCategory),
            (self.bond_edge_type, self.length),
        ]
        for a, b in links:
            self._addInstanceLink(a, b)

        # link bonds to their groups
        self._addNonSlipLink(self.sameness, self.samenessGroup, label=self.groupCategory, length=30.0)
        self._addNonSlipLink(self.successor, self.successorGroup, label=self.groupCategory, length=60.0)
        self._addNonSlipLink(self.predecessor, self.predecessorGroup, label=self.groupCategory, length=60.0)

        # link bond groups to their bonds
        self._addNonSlipLink(self.samenessGroup, self.sameness, label=self.bondCategory, length=90.0)
        self._addNonSlipLink(self.successorGroup, self.successor, label=self.bondCategory, length=90.0)
        self._addNonSlipLink(self.predecessorGroup, self.predecessor, label=self.bondCategory, length=90.0)

        # letter category to length
        self._addSlipLink(self.letterCategory, self.length, length=95.0)
        self._addSlipLink(self.length, self.letterCategory, length=95.0)

        # letter to group
        self._addSlipLink(self.letter, self.group, length=90.0)
        self._addSlipLink(self.group, self.letter, length=90.0)

        # direction-position, direction-neighbor, position-neighbor
        self._addBidirectionalLink(self.left, self.leftmost, 90.0)
        self._addBidirectionalLink(self.right, self.rightmost, 90.0)
        self._addBidirectionalLink(self.right, self.leftmost, 100.0)
        self._addBidirectionalLink(self.left, self.rightmost, 100.0)
        self._addBidirectionalLink(self.leftmost, self.first, 100.0)
        self._addBidirectionalLink(self.rightmost, self.first, 100.0)
        self._addBidirectionalLink(self.leftmost, self.last, 100.0)
        self._addBidirectionalLink(self.rightmost, self.last, 100.0)

        # other
        self._addSlipLink(self.single, self.whole, length=90.0)
        self._addSlipLink(self.whole, self.single, length=90.0)


def export_slipnet():
    """Export slipnet nodes and links to a JSON structure."""
    slipnet = SlipnetExporter()

    # Build node data
    nodes = []
    node_to_id = {}

    for node in slipnet.slipnodes:
        node_to_id[node] = node.name
        node_data = {
            "name": node.name,
            "conceptualDepth": node.conceptualDepth,
            "intrinsicLinkLength": node.intrinsicLinkLength,
            "shrunkLinkLength": node.shrunkLinkLength,
        }
        if node.codelets:
            node_data["codelets"] = node.codelets
        nodes.append(node_data)

    # Build link data with type classification
    links = []

    for link in slipnet.sliplinks:
        link_data = {
            "source": node_to_id[link.source],
            "destination": node_to_id[link.destination],
            "fixedLength": link.fixedLength,
        }
        if link.label:
            link_data["label"] = node_to_id[link.label]

        # Determine link type
        if link in link.source.lateralSlipLinks:
            link_data["type"] = "slip"
        elif link in link.source.lateralNonSlipLinks:
            link_data["type"] = "nonSlip"
        elif link in link.source.categoryLinks:
            link_data["type"] = "category"
        elif link in link.source.instanceLinks:
            link_data["type"] = "instance"
        elif link in link.source.propertyLinks:
            link_data["type"] = "property"

        links.append(link_data)

    # Identify initially clamped nodes
    initially_clamped = [node_to_id[n] for n in slipnet.initiallyClampedSlipnodes]

    # Create the full structure
    data = {
        "description": "Copycat Slipnet - A semantic network for analogical reasoning",
        "nodeCount": len(nodes),
        "linkCount": len(links),
        "initiallyClampedNodes": initially_clamped,
        "nodes": nodes,
        "links": links,
    }

    return data


if __name__ == '__main__':
    data = export_slipnet()

    output_file = 'slipnet.json'
    with open(output_file, 'w') as f:
        json.dump(data, f, indent=2)

    print(f"Exported {data['nodeCount']} nodes and {data['linkCount']} links to {output_file}")
