
import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino
import System
import math

REQUESTED_LAYER_ORDER = [
    'SURVEY',
    'GRAPHIC SURVEY',
    'G_SRV_ Major Contours',
    'G_SRV_ Minor Contours',
    'G_SRV_Hatch',
    'G_SRV_Improvements',
    'G_SRV_Text',
    'G_SRV_Spot Text',
    'G_SRV_Leaders',
    'G_SRV_Building Footprint',
    'G_SRV_Chain Link Fence',
    'G_SRV_Utilities',
    'G_SRV_Striping',
    'G_SRV_Blocks',
    'G_SRV_Street Center',
    'ATTACHMENTS',
    'PROPERTY LINE',
    'EASEMENT'
]

FIXED_TEXT_STYLE_NAME = 'Century Gothic Header - Bold'
DEFAULT_FONT_NAME = 'Century Gothic'

LEGEND_DOC_SECTION = 'LiveLinkedLayerLegend'
LEGEND_MASTER_ENTRY = 'MasterId'
SETTINGS_SECTION = 'LiveLinkedLayerLegendSettings'

SETTING_TEXT_HEIGHT = 'TextHeight'
SETTING_ROW_SPACING = 'RowSpacing'
SETTING_LINE_LENGTH = 'LineLength'
SETTING_TEXT_STYLE = 'TextStyleName'
SETTING_MODEL_SPACE_COEFFICIENT = 'ModelSpaceCoefficient'

LEGEND_TAG_KEY = 'GeneratedLayerLegend'
LEGEND_TAG_VALUE = 'LiveLinkedByLayerName'
LEGEND_TAG_VALUES = ['LiveLinkedByLayerName', 'LinkedByLayerName']
LEGEND_MASTER_KEY = 'LiveLegendMasterId'
LEGEND_ROLE_KEY = 'LinkedLegendRole'
LEGEND_ROW_KEY = 'LinkedLegendRow'
LEGEND_LAYER_ID_KEY = 'LinkedLayerId'
LEGEND_LAYER_PATH_KEY = 'LinkedLayerOriginalPath'
LEGEND_MATCH_KEY = 'UseAsMatchPropertiesSource'

HANDLER_STICKY_KEY = 'LiveLinkedLayerLegend_AllHandlers'
UPDATING_STICKY_KEY = 'LiveLinkedLayerLegend_IsUpdating'

DEFAULT_TEXT_HEIGHT = 2.5
DEFAULT_ROW_SPACING_FACTOR = 2.55
DEFAULT_LINE_LENGTH_FACTOR = 24.0
DEFAULT_LINE_GAP_FACTOR = 0.16
DEFAULT_MODEL_SPACE_COEFFICIENT = 1.0


def safe_float(value, default_value):
    try:
        if value is None or value == '':
            return default_value
        return float(value)
    except:
        return default_value


def get_document_string(section, entry):
    try:
        return sc.doc.Strings.GetValue(section, entry)
    except:
        return None


def set_document_string(section, entry, value):
    try:
        sc.doc.Strings.SetString(section, entry, str(value))
        return True
    except:
        return False


def setting_exists(key):
    value = get_document_string(SETTINGS_SECTION, key)
    return value is not None and value != ''


def get_setting_float(key, default_value):
    return safe_float(get_document_string(SETTINGS_SECTION, key), default_value)


def initialize_settings_if_needed():
    # Force this tool away from any prior A140 or user-selected style setting.
    set_document_string(SETTINGS_SECTION, SETTING_TEXT_STYLE, FIXED_TEXT_STYLE_NAME)
    set_document_string(SETTINGS_SECTION, SETTING_MODEL_SPACE_COEFFICIENT, DEFAULT_MODEL_SPACE_COEFFICIENT)

    if not setting_exists(SETTING_TEXT_HEIGHT):
        set_document_string(SETTINGS_SECTION, SETTING_TEXT_HEIGHT, DEFAULT_TEXT_HEIGHT)
    if not setting_exists(SETTING_ROW_SPACING):
        set_document_string(SETTINGS_SECTION, SETTING_ROW_SPACING, DEFAULT_TEXT_HEIGHT * DEFAULT_ROW_SPACING_FACTOR)
    if not setting_exists(SETTING_LINE_LENGTH):
        set_document_string(SETTINGS_SECTION, SETTING_LINE_LENGTH, DEFAULT_TEXT_HEIGHT * DEFAULT_LINE_LENGTH_FACTOR)


def get_or_create_master_id():
    master_id = get_document_string(LEGEND_DOC_SECTION, LEGEND_MASTER_ENTRY)
    if master_id is None or master_id == '':
        master_id = str(System.Guid.NewGuid())
        set_document_string(LEGEND_DOC_SECTION, LEGEND_MASTER_ENTRY, master_id)
    return master_id


def layer_is_deleted(layer):
    try:
        return layer.IsDeleted
    except:
        return False


def get_layer_full_path(layer):
    try:
        return layer.FullPath
    except:
        return layer.Name


def find_layer_index_by_name(layer_name):
    target_lower = layer_name.lower()
    fallback_index = None
    for i in range(sc.doc.Layers.Count):
        layer = sc.doc.Layers[i]
        if layer is None or layer_is_deleted(layer):
            continue
        full_path = get_layer_full_path(layer)
        simple_name = layer.Name
        if full_path == layer_name or simple_name == layer_name:
            return i
        if fallback_index is None:
            if full_path.lower() == target_lower or simple_name.lower() == target_lower:
                fallback_index = i
    return fallback_index


def get_layer_entries_in_order():
    entries = []
    used_indices = {}
    for requested_name in REQUESTED_LAYER_ORDER:
        layer_index = find_layer_index_by_name(requested_name)
        if layer_index is not None and not used_indices.has_key(layer_index):
            entries.append(layer_index)
            used_indices[layer_index] = True
    for i in range(sc.doc.Layers.Count):
        layer = sc.doc.Layers[i]
        if layer is None or layer_is_deleted(layer):
            continue
        if not used_indices.has_key(i):
            entries.append(i)
            used_indices[i] = True
    return entries


def get_layer_hierarchy_level(layer_index):
    layer = sc.doc.Layers[layer_index]
    full_path = get_layer_full_path(layer)
    if not full_path:
        return 0
    return full_path.count('::')


def get_layer_display_name(layer_index):
    layer = sc.doc.Layers[layer_index]
    full_path = get_layer_full_path(layer)
    if full_path and '::' in full_path:
        return full_path.split('::')[-1]
    return layer.Name


def object_is_any_layer_legend(obj_id):
    try:
        tag_value = rs.GetUserText(obj_id, LEGEND_TAG_KEY)
        if tag_value in LEGEND_TAG_VALUES:
            return True
    except:
        pass
    obj_name = rs.ObjectName(obj_id)
    if obj_name:
        if obj_name.startswith('Live linked layer legend'):
            return True
        if obj_name.startswith('Linked layer legend'):
            return True
        if obj_name.startswith('Layer legend'):
            return True
        if obj_name.startswith('MATCHPROPERTIES SOURCE'):
            return True
    return False


def find_fixed_dimstyle():
    try:
        for i in range(sc.doc.DimStyles.Count):
            style = sc.doc.DimStyles[i]
            if style is None:
                continue
            try:
                if style.IsDeleted:
                    continue
            except:
                pass
            if style.Name == FIXED_TEXT_STYLE_NAME:
                return style, i
        for i in range(sc.doc.DimStyles.Count):
            style = sc.doc.DimStyles[i]
            if style is None:
                continue
            try:
                if style.IsDeleted:
                    continue
            except:
                pass
            if style.Name.lower() == FIXED_TEXT_STYLE_NAME.lower():
                return style, i
    except:
        pass
    return None, -1


def force_fixed_style_model_space_coefficient_to_one():
    style, index = find_fixed_dimstyle()
    if style is None or index < 0:
        return False
    try:
        style_copy = style.Duplicate()
        changed = False
        try:
            style_copy.ModelSpaceScale = 1.0
            changed = True
        except:
            pass
        try:
            style_copy.DimScale = 1.0
            changed = True
        except:
            pass
        if changed:
            try:
                sc.doc.DimStyles.Modify(style_copy, index, False)
                return True
            except:
                return False
    except:
        pass
    return False


def get_fixed_dimstyle_name_status():
    style, index = find_fixed_dimstyle()
    if style is None:
        return 'WARNING: Text/Dim style not found: ' + FIXED_TEXT_STYLE_NAME + '. Using Century Gothic font fallback.'
    return 'Using fixed Text/Dim style: ' + FIXED_TEXT_STYLE_NAME


def get_existing_legend_objects_by_role():
    labels = []
    lines = []
    all_objects = rs.AllObjects()
    if not all_objects:
        return labels, lines
    for obj_id in all_objects:
        if not object_is_any_layer_legend(obj_id):
            continue
        role = None
        row = 999999
        try:
            role = rs.GetUserText(obj_id, LEGEND_ROLE_KEY)
            row_text = rs.GetUserText(obj_id, LEGEND_ROW_KEY)
            if row_text is not None and row_text != '':
                row = int(row_text)
        except:
            pass
        if role == 'Label':
            labels.append([row, obj_id])
        elif role == 'LineSample':
            lines.append([row, obj_id])
    labels.sort()
    lines.sort()
    return labels, lines


def get_text_origin(obj_id):
    rhino_object = sc.doc.Objects.Find(obj_id)
    if rhino_object:
        try:
            origin = rhino_object.Geometry.Plane.Origin
            return (origin.X, origin.Y, origin.Z)
        except:
            pass
    return None


def get_text_height_safe(obj_id):
    rhino_object = sc.doc.Objects.Find(obj_id)
    if rhino_object:
        try:
            h = rhino_object.Geometry.TextHeight
            if h is not None and h > 0:
                return h
        except:
            pass
    try:
        bbox = rs.BoundingBox(obj_id)
        if bbox and len(bbox) >= 8:
            height = max([pt[1] for pt in bbox]) - min([pt[1] for pt in bbox])
            if height > 0:
                return height
    except:
        pass
    return None


def get_curve_length_safe(obj_id):
    try:
        length = rs.CurveLength(obj_id)
        if length is not None and length > 0:
            return length
    except:
        pass
    return None


def capture_existing_legend_settings():
    initialize_settings_if_needed()
    labels, lines = get_existing_legend_objects_by_role()
    anchor = None

    if labels:
        anchor = get_text_origin(labels[0][1])

    if anchor is None:
        legend_objects = []
        all_objects = rs.AllObjects()
        if all_objects:
            for obj_id in all_objects:
                if object_is_any_layer_legend(obj_id):
                    legend_objects.append(obj_id)
        if legend_objects:
            bbox = rs.BoundingBox(legend_objects)
            if bbox and len(bbox) >= 8:
                min_x = min([pt[0] for pt in bbox])
                max_y = max([pt[1] for pt in bbox])
                min_z = min([pt[2] for pt in bbox])
                anchor = (min_x, max_y, min_z)

    if labels:
        existing_height = get_text_height_safe(labels[0][1])
        stored_height = get_setting_float(SETTING_TEXT_HEIGHT, DEFAULT_TEXT_HEIGHT)
        if existing_height is not None and existing_height > 0:
            if stored_height > 0 and abs(existing_height - stored_height) > max(0.0001, stored_height * 0.01):
                scale_factor = existing_height / stored_height
                old_row_spacing = get_setting_float(SETTING_ROW_SPACING, stored_height * DEFAULT_ROW_SPACING_FACTOR)
                old_line_length = get_setting_float(SETTING_LINE_LENGTH, stored_height * DEFAULT_LINE_LENGTH_FACTOR)
                set_document_string(SETTINGS_SECTION, SETTING_TEXT_HEIGHT, existing_height)
                set_document_string(SETTINGS_SECTION, SETTING_ROW_SPACING, old_row_spacing * scale_factor)
                set_document_string(SETTINGS_SECTION, SETTING_LINE_LENGTH, old_line_length * scale_factor)
            else:
                set_document_string(SETTINGS_SECTION, SETTING_TEXT_HEIGHT, existing_height)

    if len(labels) >= 2:
        p0 = get_text_origin(labels[0][1])
        p1 = get_text_origin(labels[1][1])
        if p0 and p1:
            spacing = abs(p0[1] - p1[1])
            if spacing > 0:
                set_document_string(SETTINGS_SECTION, SETTING_ROW_SPACING, spacing)

    if lines:
        length = get_curve_length_safe(lines[0][1])
        if length is not None and length > 0:
            set_document_string(SETTINGS_SECTION, SETTING_LINE_LENGTH, length)

    set_document_string(SETTINGS_SECTION, SETTING_TEXT_STYLE, FIXED_TEXT_STYLE_NAME)
    set_document_string(SETTINGS_SECTION, SETTING_MODEL_SPACE_COEFFICIENT, DEFAULT_MODEL_SPACE_COEFFICIENT)
    return anchor


def get_model_extents_excluding_legends():
    all_objects = rs.AllObjects()
    if not all_objects:
        return None
    usable_objects = []
    for obj_id in all_objects:
        if not object_is_any_layer_legend(obj_id):
            usable_objects.append(obj_id)
    if not usable_objects:
        return None
    bbox = rs.BoundingBox(usable_objects)
    if not bbox or len(bbox) < 8:
        return None
    min_x = min([pt[0] for pt in bbox])
    max_x = max([pt[0] for pt in bbox])
    min_y = min([pt[1] for pt in bbox])
    max_y = max([pt[1] for pt in bbox])
    min_z = min([pt[2] for pt in bbox])
    max_z = max([pt[2] for pt in bbox])
    return (min_x, max_x, min_y, max_y, min_z, max_z)


def collect_layer_lock_states_for_objects(object_ids):
    states = {}
    for obj_id in object_ids:
        try:
            layer_name = rs.ObjectLayer(obj_id)
            if layer_name and not states.has_key(layer_name):
                states[layer_name] = rs.IsLayerLocked(layer_name)
        except:
            pass
    return states


def restore_layer_lock_states(states):
    for layer_name in states:
        try:
            if states[layer_name]:
                rs.LayerLocked(layer_name, True)
            else:
                rs.LayerLocked(layer_name, False)
        except:
            pass


def delete_all_existing_copied_or_live_legends():
    ids_to_delete = []
    all_objects = rs.AllObjects()
    if all_objects:
        for obj_id in all_objects:
            if object_is_any_layer_legend(obj_id):
                ids_to_delete.append(obj_id)
    if ids_to_delete:
        lock_states = collect_layer_lock_states_for_objects(ids_to_delete)
        for layer_name in lock_states:
            try:
                rs.LayerLocked(layer_name, False)
            except:
                pass
        for obj_id in ids_to_delete:
            try:
                rs.UnlockObject(obj_id)
            except:
                pass
        try:
            rs.DeleteObjects(ids_to_delete)
        except:
            pass
        restore_layer_lock_states(lock_states)


def get_unique_group_name(base_name):
    group_name = base_name
    suffix = 1
    try:
        while rs.IsGroup(group_name):
            group_name = base_name + '_' + str(suffix)
            suffix += 1
    except:
        pass
    return group_name


def calculate_layout(layer_indices, existing_anchor):
    initialize_settings_if_needed()
    text_height = get_setting_float(SETTING_TEXT_HEIGHT, DEFAULT_TEXT_HEIGHT)
    if text_height <= 0:
        text_height = DEFAULT_TEXT_HEIGHT
    row_spacing = get_setting_float(SETTING_ROW_SPACING, text_height * DEFAULT_ROW_SPACING_FACTOR)
    if row_spacing <= 0:
        row_spacing = text_height * DEFAULT_ROW_SPACING_FACTOR
    line_gap_below_text = text_height * DEFAULT_LINE_GAP_FACTOR
    line_length = get_setting_float(SETTING_LINE_LENGTH, text_height * DEFAULT_LINE_LENGTH_FACTOR)
    if line_length <= 0:
        line_length = text_height * DEFAULT_LINE_LENGTH_FACTOR
    indent_step = text_height * 4.0

    max_label_length = 1
    for layer_index in layer_indices:
        label = get_layer_display_name(layer_index)
        if len(label) > max_label_length:
            max_label_length = len(label)
    min_line_length = max_label_length * text_height * 0.9
    if line_length < min_line_length:
        line_length = min_line_length

    if existing_anchor:
        start_x = existing_anchor[0]
        start_y = existing_anchor[1]
        start_z = existing_anchor[2]
    else:
        extents = get_model_extents_excluding_legends()
        if extents:
            min_x, max_x, min_y, max_y, min_z, max_z = extents
            dx = max_x - min_x
            dy = max_y - min_y
            dz = max_z - min_z
            diagonal = math.sqrt(dx * dx + dy * dy + dz * dz)
            margin = max(line_length * 0.75, diagonal * 0.03)
            start_x = max_x + margin
            start_y = max_y
            start_z = 0.0
        else:
            start_x = 0.0
            start_y = 0.0
            start_z = 0.0

    return text_height, row_spacing, line_gap_below_text, line_length, indent_step, start_x, start_y, start_z


def apply_match_source_attributes(object_id, layer_index, object_name):
    rhino_object = sc.doc.Objects.Find(object_id)
    if rhino_object is None:
        return False
    attributes = rhino_object.Attributes.Duplicate()
    attributes.LayerIndex = layer_index
    attributes.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromLayer
    attributes.LinetypeSource = Rhino.DocObjects.ObjectLinetypeSource.LinetypeFromLayer
    attributes.Name = object_name
    try:
        attributes.PlotColorSource = Rhino.DocObjects.ObjectPlotColorSource.PlotColorFromLayer
    except:
        pass
    try:
        attributes.PlotWeightSource = Rhino.DocObjects.ObjectPlotWeightSource.PlotWeightFromLayer
    except:
        pass
    try:
        attributes.Mode = Rhino.DocObjects.ObjectMode.Normal
    except:
        pass
    try:
        sc.doc.Objects.ModifyAttributes(object_id, attributes, True)
        return True
    except:
        return False


def apply_fixed_text_style_and_height(text_id, text_height):
    style, index = find_fixed_dimstyle()
    rhino_object = sc.doc.Objects.Find(text_id)
    if rhino_object is None:
        return False
    try:
        geom = rhino_object.Geometry.Duplicate()
        # Apply the fixed style first. Then force actual model-space text height afterward.
        if style is not None:
            try:
                geom.DimensionStyleId = style.Id
            except:
                pass
        try:
            geom.TextHeight = text_height
        except:
            pass
        sc.doc.Objects.Replace(text_id, geom)
        return True
    except:
        return False


def set_text_to_own_layer_field(text_id, fallback_layer_name):
    object_id_string = str(text_id)
    field_text = '%<ObjectLayer("' + object_id_string + '")>%'
    did_set = False
    try:
        rs.TextObjectText(text_id, field_text)
        did_set = True
    except:
        did_set = False
    if not did_set:
        try:
            rhino_object = sc.doc.Objects.Find(text_id)
            if rhino_object:
                text_geometry = rhino_object.Geometry.Duplicate()
                try:
                    text_geometry.TextFormula = field_text
                    did_set = sc.doc.Objects.Replace(text_id, text_geometry)
                except:
                    try:
                        text_geometry.RichText = field_text
                        did_set = sc.doc.Objects.Replace(text_id, text_geometry)
                    except:
                        did_set = False
        except:
            did_set = False
    if not did_set:
        try:
            rs.TextObjectText(text_id, fallback_layer_name)
        except:
            pass
    return did_set


def get_text_bottom_y(text_id, fallback_y, text_height):
    try:
        bbox = rs.BoundingBox(text_id)
        if bbox and len(bbox) >= 8:
            return min([pt[1] for pt in bbox])
    except:
        pass
    return fallback_y - (text_height * 0.25)


def tag_generated_object(object_id, master_id, layer_index, role, row_index):
    layer = sc.doc.Layers[layer_index]
    full_path = get_layer_full_path(layer)
    layer_id = str(layer.Id)
    try:
        rs.SetUserText(object_id, LEGEND_TAG_KEY, LEGEND_TAG_VALUE)
        rs.SetUserText(object_id, LEGEND_MASTER_KEY, master_id)
        rs.SetUserText(object_id, LEGEND_ROLE_KEY, role)
        rs.SetUserText(object_id, LEGEND_ROW_KEY, str(row_index))
        rs.SetUserText(object_id, LEGEND_LAYER_ID_KEY, layer_id)
        rs.SetUserText(object_id, LEGEND_LAYER_PATH_KEY, full_path)
        if role == 'LineSample':
            rs.SetUserText(object_id, LEGEND_MATCH_KEY, 'YES - select this line as the MatchProperties source for ' + full_path)
        else:
            rs.SetUserText(object_id, LEGEND_MATCH_KEY, 'Label only - use the line below as the MatchProperties source')
    except:
        pass


def sync_live_layer_legend():
    if sc.sticky.has_key(UPDATING_STICKY_KEY) and sc.sticky[UPDATING_STICKY_KEY]:
        return []
    sc.sticky[UPDATING_STICKY_KEY] = True
    try:
        initialize_settings_if_needed()
        force_fixed_style_model_space_coefficient_to_one()

        master_id = get_or_create_master_id()
        layer_indices = get_layer_entries_in_order()
        if not layer_indices:
            print('No layers were found in the current document.')
            sc.sticky[UPDATING_STICKY_KEY] = False
            return []

        existing_anchor = capture_existing_legend_settings()
        text_height, row_spacing, line_gap_below_text, line_length, indent_step, start_x, start_y, start_z = calculate_layout(layer_indices, existing_anchor)

        delete_all_existing_copied_or_live_legends()

        created_objects = []
        field_count = 0

        for row_index, layer_index in enumerate(layer_indices):
            layer = sc.doc.Layers[layer_index]
            full_path = get_layer_full_path(layer)
            fallback_display_name = get_layer_display_name(layer_index)
            hierarchy_level = get_layer_hierarchy_level(layer_index)
            indent = hierarchy_level * indent_step
            row_y = start_y - (row_index * row_spacing)
            text_point = (start_x + indent, row_y, start_z)

            text_id = rs.AddText(fallback_display_name, text_point, text_height, DEFAULT_FONT_NAME)
            if text_id:
                apply_match_source_attributes(text_id, layer_index, 'Live linked layer legend label - ' + full_path)
                apply_fixed_text_style_and_height(text_id, text_height)
                tag_generated_object(text_id, master_id, layer_index, 'Label', row_index)
                if set_text_to_own_layer_field(text_id, fallback_display_name):
                    field_count += 1
                apply_fixed_text_style_and_height(text_id, text_height)
                created_objects.append(text_id)
                text_bottom_y = get_text_bottom_y(text_id, row_y, text_height)
                line_y = text_bottom_y - line_gap_below_text
            else:
                line_y = row_y - (text_height * 0.45)

            line_start = (start_x + indent, line_y, start_z)
            line_end = (start_x + indent + line_length, line_y, start_z)
            line_id = rs.AddLine(line_start, line_end)
            if line_id:
                apply_match_source_attributes(line_id, layer_index, 'MATCHPROPERTIES SOURCE - ' + full_path)
                tag_generated_object(line_id, master_id, layer_index, 'LineSample', row_index)
                created_objects.append(line_id)

        if created_objects:
            group_name = get_unique_group_name('Layer_Legend_PORTABLE_LIVE_MATCHPROPERTIES')
            added_group = rs.AddGroup(group_name)
            if added_group:
                rs.AddObjectsToGroup(created_objects, added_group)

        try:
            rs.Command('_Regen', False)
        except:
            pass
        sc.doc.Views.Redraw()
        print(get_fixed_dimstyle_name_status())
        print('Live layer legend synchronized. Fixed style only. Text height: ' + str(text_height) + '. Rows: ' + str(len(layer_indices)) + '. Linked fields: ' + str(field_count) + '.')
        sc.sticky[UPDATING_STICKY_KEY] = False
        return created_objects
    except Exception as e:
        sc.sticky[UPDATING_STICKY_KEY] = False
        print('Layer legend synchronization failed: ' + str(e))
        return []


def sync_doc(doc):
    old_doc = sc.doc
    try:
        if doc is not None:
            sc.doc = doc
        sync_live_layer_legend()
    except Exception as e:
        print('Live layer legend document sync failed: ' + str(e))
    finally:
        sc.doc = old_doc


def get_doc_from_sender_or_event(sender, event_args):
    try:
        if sender is not None and hasattr(sender, 'Layers') and hasattr(sender, 'Objects'):
            return sender
    except:
        pass
    try:
        if event_args is not None and hasattr(event_args, 'Document'):
            return event_args.Document
    except:
        pass
    return sc.doc


def live_layer_table_event_handler(sender, event_args):
    if sc.sticky.has_key(UPDATING_STICKY_KEY) and sc.sticky[UPDATING_STICKY_KEY]:
        return
    doc = get_doc_from_sender_or_event(sender, event_args)
    sync_doc(doc)


def live_document_event_handler(sender, event_args):
    if sc.sticky.has_key(UPDATING_STICKY_KEY) and sc.sticky[UPDATING_STICKY_KEY]:
        return
    doc = get_doc_from_sender_or_event(sender, event_args)
    sync_doc(doc)


def remove_existing_handlers():
    if sc.sticky.has_key(HANDLER_STICKY_KEY):
        handlers = sc.sticky[HANDLER_STICKY_KEY]
        try:
            Rhino.RhinoDoc.LayerTableEvent -= handlers[0]
        except:
            pass
        try:
            Rhino.RhinoDoc.NewDocument -= handlers[1]
        except:
            pass
        try:
            Rhino.RhinoDoc.EndOpenDocument -= handlers[1]
        except:
            pass
        try:
            Rhino.RhinoDoc.ActiveDocumentChanged -= handlers[1]
        except:
            pass


def install_live_layer_legend_handlers():
    remove_existing_handlers()
    Rhino.RhinoDoc.LayerTableEvent += live_layer_table_event_handler
    try:
        Rhino.RhinoDoc.NewDocument += live_document_event_handler
    except:
        pass
    try:
        Rhino.RhinoDoc.EndOpenDocument += live_document_event_handler
    except:
        pass
    try:
        Rhino.RhinoDoc.ActiveDocumentChanged += live_document_event_handler
    except:
        pass
    sc.sticky[HANDLER_STICKY_KEY] = [live_layer_table_event_handler, live_document_event_handler]
    print('Live layer legend listener is ON. Fixed text style: ' + FIXED_TEXT_STYLE_NAME)


def sync_all_open_documents_or_current():
    synced_any = False
    old_doc = sc.doc
    try:
        docs = None
        try:
            docs = Rhino.RhinoDoc.OpenDocuments
        except:
            docs = None
        if docs:
            for doc in docs:
                if doc is not None:
                    sync_doc(doc)
                    synced_any = True
    except:
        synced_any = False
    finally:
        sc.doc = old_doc
    if not synced_any:
        sync_live_layer_legend()


def main():
    install_live_layer_legend_handlers()
    sync_all_open_documents_or_current()

main()
