import io

import negspy.coordinates as nc
from snovault import calculated_property, collection, load_schema

from ..ingestion.common import CGAP_CORE_PROJECT
from ..util import resolve_file_path
from .base import Item, get_item_or_none
from .variant import (
    ANNOTATION_ID,
    build_variant_sample_annotation_id,
    extend_embedded_list,
    load_extended_descriptions_in_schemas,
    SHARED_VARIANT_EMBEDS,
    SHARED_VARIANT_SAMPLE_EMBEDS,
)


def build_structural_variant_embedded_list():
    """Determines the embedded_list based on the information
    present in schemas/structural_variant_embeds.json

    :returns: list of structural variant embeds
    """
    embedded_list = SHARED_VARIANT_EMBEDS + []
    with io.open(
        resolve_file_path("schemas/structural_variant_embeds.json"), "r"
    ) as fd:
        extend_embedded_list(embedded_list, fd, "structural_variant")
    return embedded_list + Item.embedded_list


def build_structural_variant_sample_embedded_list():
    """Determines the embedded list for structural variants within
    structural_variant_sample ie: structural_variant.* is not
    sufficient for things embedded into structural variant
    that we'd like to search on in the structural_variant_sample
    context. Works very similary to the above function

    :returns: list of embeds from 'structural_variant' linkTo
    """
    embedded_list = SHARED_VARIANT_SAMPLE_EMBEDS + []
    with io.open(
        resolve_file_path("schemas/structural_variant_embeds.json"), "r"
    ) as fd:
        extend_embedded_list(
            embedded_list, fd, "structural_variant", prefix="structural_variant."
        )
    with io.open(
        resolve_file_path("schemas/structural_variant_sample_embeds.json"), "r"
    ) as fd:
        extend_embedded_list(embedded_list, fd, "structural_variant_sample")
    return ["structural_variant.*"] + embedded_list + Item.embedded_list


def build_structural_variant_display_title(sv_type, chrom, start, end):
    """Builds the structural variant display title according to the type of SV."""
    display_title = "%s_chr%s:%s-%s" % (sv_type, chrom, start, end)
    return display_title


@collection(
    name="structural-variants",
    properties={
        "title": "Structural variants",
        "description": "List of all structural variants",
    },
    unique_key="structural_variant:annotation_id",
)
class StructuralVariant(Item):
    """ Structural variant class """

    item_type = "structural_variant"
    name_key = "annotation_id"
    schema = load_schema("encoded:schemas/structural_variant.json")
    embedded_list = build_structural_variant_embedded_list()

    @classmethod
    def create(cls, registry, uuid, properties, sheets=None):
        """
        Sets the annotation_id field on this structural variant prior to passing on.
        """
        properties[ANNOTATION_ID] = build_structural_variant_display_title(
            properties["SV_TYPE"],
            properties["CHROM"],
            properties["START"],
            properties["END"],
        )
        return super().create(registry, uuid, properties, sheets)

    @calculated_property(
        schema={
            "title": "Display Title",
            "description": "A calculated title for every object in CGAP",
            "type": "string",
        }
    )
    def display_title(self, SV_TYPE, CHROM, START, END):
        return build_structural_variant_display_title(SV_TYPE, CHROM, START, END)

    @calculated_property(
        schema={
            "title": "Start Absolute Position",
            "description": "Start absolute position in genome coordinates",
            "type": "integer",
        }
    )
    def START_ABS(self, CHROM, START):
        chrom_info = nc.get_chrominfo("hg38")
        return nc.chr_pos_to_genome_pos("chr" + CHROM, START, chrom_info)

    @calculated_property(
        schema={
            "title": "End Absolute Position",
            "description": "End absolute position in genome coordinates",
            "type": "integer",
        }
    )
    def END_ABS(self, CHROM, END):
        chrom_info = nc.get_chrominfo("hg38")
        return nc.chr_pos_to_genome_pos("chr" + CHROM, END, chrom_info)

    @calculated_property(
        schema={
            "title": "Size",
            "description": "The size of this structural variant",
            "type": "number",
        }
    )
    def size(self, START, END):
        return END - START + 1

    @calculated_property(
        schema={
            "title": "Size Display",
            "description": "The abbreviated size of this structural variant",
            "type": "string",
        }
    )
    def size_display(self, START, END):
        """
        Create user-friendly display of size.

        Finds appropriate units "bucket" according to number of digits
        in the size, then rounds to 1 decimal place within the "bucket".

        :param START: int start location of SV
        :param END: int end location of SV
        :return: str size plus unit
        """
        result = None
        size = END - START + 1
        unit_max_exponents = [3, 6, 9, 12]  # Gb should be max given chromosome sizes
        display_units = ["bp", "Kb", "Mb", "Gb"]
        for exponent, unit in zip(unit_max_exponents, display_units):
            if size < 10 ** exponent:
                display_number = round(size * 10 ** (-exponent + 3), 1)
                result = str(display_number) + " " + unit
                break
        return result

    @calculated_property(
        schema={
            "title": "Cytoband(s)",
            "description": "The cytoband(s) of this structural variant",
            "type": "string",
        }
    )
    def cytoband_display(self, CHROM, cytoband_start=None, cytoband_end=None):
        """
        Create cytoband to display in portal.

        Requires cytobands at both start and end locations, and result
        is either single cytoband if both identical or dash-separated
        cytobands if different.

        :param CHROM: str chromosome
        :param cytoband_start: str cytoband at SV START
        :param cytoband_end: str cytoband at SV END
        :returns: str cytoband to display
        """
        result = None
        if cytoband_start and cytoband_end:
            if cytoband_start == cytoband_end:
                result = CHROM + cytoband_start
            else:
                result = CHROM + cytoband_start + "-" + CHROM + cytoband_end
        return result

    @calculated_property(
        schema={
            "title": "Gene Summary",
            "description": "An overview of genes overlapping this structural variant",
            "type": "object",
            "properties": {
                "contained": {
                    "title": "Genes Contained in SV",
                    "type": "string",
                    "description": (
                        "The number of genes entirely within the structural variant out"
                        " of all genes affected"
                    ),
                },
                "at_breakpoint": {
                    "title": "Genes Crossing an SV Breakpoint",
                    "type": "string",
                    "description": (
                        "The number of genes overlapping the ends of the structural"
                        " variant out of all genes affected"
                    ),
                },
                "omim_genes": {
                    "title": "Genes with OMIM IDs",
                    "type": "string",
                    "description": (
                        "The number of genes affected by the structural variant that"
                        " exist in the OMIM database out of all genes affected"
                    ),
                },
            },
        }
    )
    def gene_summary(self, request, START, END, transcript=None):
        """
        Calculate summary characteristics of genes overlapped by the
        structural variant.

        NOTE: Genes included here are only those that have some
        overlap with the given SV; genes entirely up-/downstream of the
        SV are not contributing towards any counts (including the
        total).

        :param request: cls pyramid request
        :param START: int SV hg38 start position
        :param END: int SV hg38 end position
        :param transcript: list of VEP annotations
        :returns: dict of summary characteristics
        """
        result = {}
        genes = []
        gene_count = 0
        contained_count = 0
        breakpoint_count = 0
        omim_count = 0
        if transcript:
            for item in transcript:
                gene = item.get("csq_gene")
                if gene and gene not in genes:
                    genes.append(gene)
        for gene in genes:
            gene = get_item_or_none(request, gene, "Gene", frame="raw")
            gene_start = gene.get("spos")
            gene_end = gene.get("epos")
            gene_omim_id = gene.get("omim_id")
            if gene_start and gene_end:  # True for all genes as of 09-14-2021 drr
                if int(gene_start) >= START and int(gene_end) <= END:
                    gene_count += 1
                    contained_count += 1
                    if gene_omim_id:
                        omim_count += 1
                elif int(gene_start) <= END and int(gene_end) >= START:
                    gene_count += 1
                    breakpoint_count += 1
                    if gene_omim_id:
                        omim_count += 1
        result["contained"] = str(contained_count) + "/" + str(gene_count)
        result["at_breakpoint"] = str(breakpoint_count) + "/" + str(gene_count)
        result["omim_genes"] = str(omim_count) + "/" + str(gene_count)
        return result


@collection(
    name="structural-variant-samples",
    properties={
        "title": "Structural variants (sample)",
        "description": "List of all structural variants with sample specific information",
    },
    unique_key="structural_variant_sample:annotation_id",
)
class StructuralVariantSample(Item):
    """Class for structural variant samples."""

    item_type = "structural_variant_sample"
    schema = load_extended_descriptions_in_schemas(
        load_schema("encoded:schemas/structural_variant_sample.json")
    )
    rev = {
        "variant_sample_list": (
            "VariantSampleList",
            "structural_variant_samples.structural_variant_sample_item",
        )
    }
    embedded_list = build_structural_variant_sample_embedded_list()

    POSSIBLE_GENOTYPE_LABEL_FIELDS = [
        "proband_genotype_label",
        "mother_genotype_label",
        "father_genotype_label",
        "sister_genotype_label",
        "sister_II_genotype_label",
        "sister_III_genotype_label",
        "sister_IV_genotype_label",
        "brother_genotype_label",
        "brother_II_genotype_label",
        "brother_III_genotype_label",
        "brother_IV_genotype_label",
        "co_parent_genotype_label",
        "daughter_genotype_label",
        "daughter_II_genotype_label",
        "son_genotype_label",
        "son_II_genotype_label",
    ]

    @classmethod
    def create(cls, registry, uuid, properties, sheets=None):
        """
        Sets the annotation_id field on this structural variant sample
        prior to passing on.
        """
        properties[ANNOTATION_ID] = "%s:%s:%s" % (
            properties["CALL_INFO"],
            properties["structural_variant"],
            properties["file"],
        )
        return super().create(registry, uuid, properties, sheets)

    @calculated_property(
        schema={
            "title": "Display Title",
            "description": "A calculated title for every object in CGAP",
            "type": "string",
        }
    )
    def display_title(self, request, CALL_INFO, structural_variant):
        structural_variant = get_item_or_none(
            request, structural_variant, "StructuralVariant", frame="raw"
        )
        structural_variant_display_title = build_structural_variant_display_title(
            structural_variant["SV_TYPE"],
            structural_variant["CHROM"],
            structural_variant["START"],
            structural_variant["END"],
        )
        if structural_variant:
            return CALL_INFO + ":" + structural_variant_display_title
        return CALL_INFO

    @calculated_property(
        schema={
            "title": "Variant Sample List",
            "description": "The list containing this variant sample",
            "type": "string",
            "linkTo": "VariantSampleList",
        }
    )
    def variant_sample_list(self, request):
        result = self.rev_link_atids(request, "variant_sample_list")
        if result:
            return result[0]  # expected one list per case

    @calculated_property(
        schema={
            "title": "Associated Gene Lists",
            "description": "Gene lists associated with project or case of variant sample",
            "type": "array",
            "items": {
                "title": "Gene list title",
                "type": "string",
                "description": "Gene list title",
            },
        }
    )
    def associated_genelists(self, request, project, structural_variant, CALL_INFO):
        """
        Identifies gene lists associated with the project or project and
        CALL_INFO of the structural variant sample, if the gene list has
        associated BAM sample IDs.

        NOTE: Gene lists retrieved with @@raw view to prevent costly
        @@object view of large gene lists.
        """
        gene_atids = []
        genelist_atids = []
        genelist_info = {}
        associated_genelists = []
        core_project = CGAP_CORE_PROJECT + "/"
        potential_projects = [core_project, project]
        variant_props = get_item_or_none(request, structural_variant)
        transcripts = variant_props.get("transcript", [])
        for transcript in transcripts:
            gene_atid = transcript.get("csq_gene")
            if gene_atid not in gene_atids:
                gene_atids.append(gene_atid)
        genes_object = [get_item_or_none(request, atid) for atid in gene_atids]
        for gene in genes_object:
            genelist_atids += gene.get("gene_lists", [])
        genelist_atids = list(set(genelist_atids))
        genelists_raw = [
            get_item_or_none(request, atid, frame="raw") for atid in genelist_atids
        ]
        for genelist in genelists_raw:
            title = genelist.get("title", "")
            bam_sample_ids = genelist.get("bam_sample_ids", [])
            project_uuid = genelist.get("project")
            project_object = get_item_or_none(request, project_uuid)
            project_atid = project_object.get("@id")
            genelist_info[title] = {
                "project": project_atid,
                "bam_sample_ids": bam_sample_ids,
            }
        for genelist_title, genelist_props in genelist_info.items():
            if genelist_title in associated_genelists:
                continue
            bam_sample_ids = genelist_props.get("bam_sample_ids")
            if genelist_props["project"] in potential_projects:
                if bam_sample_ids:
                    if CALL_INFO in bam_sample_ids:
                        associated_genelists.append(genelist_title)
                else:
                    associated_genelists.append(genelist_title)
        return associated_genelists

    @calculated_property(
        schema={
            "title": "Inheritance Modes",
            "description": "Inheritance Modes (only including those relevant to a proband-only analysis)",
            "type": "array",
            "items": {"type": "string"},
        }
    )
    def proband_only_inheritance_modes(
        self, request, structural_variant, inheritance_modes=[]
    ):
        """
        Inheritance modes for proband-only cases.

        NOTE: Limited only to sex-chromosome-linked inheritance modes
        at the moment. If compound hets aren't going to be included for
        SVs, can consider discarding this property.
        """
        proband_modes = []
        structural_variant = get_item_or_none(
            request, structural_variant, "StructuralVariant", frame="raw"
        )
        if structural_variant["CHROM"] in ["X", "Y"]:
            proband_modes.append(f"{structural_variant['CHROM']}-linked")
        if proband_modes:
            return proband_modes
        return None

    @calculated_property(
        schema={
            "title": "Associated Genotype Labels",
            "description": "Named Genotype Label fields that can be searched on",
            "type": "object",
            "additional_properties": True,
            "properties": {
                "proband_genotype_label": {
                    "title": "Proband Genotype",
                    "type": "string",
                },
                "mother_genotype_label": {"title": "Mother Genotype", "type": "string"},
                "father_genotype_label": {"title": "Father Genotype", "type": "string"},
                "sister_genotype_label": {"title": "Sister Genotype", "type": "string"},
                "sister_II_genotype_label": {
                    "title": "Sister II Genotype",
                    "type": "string",
                },
                "sister_III_genotype_label": {
                    "title": "Sister III Genotype",
                    "type": "string",
                },
                "sister_IV_genotype_label": {
                    "title": "Sister IV Genotype",
                    "type": "string",
                },
                "brother_genotype_label": {
                    "title": "Brother Genotype",
                    "type": "string",
                },
                "brother_II_genotype_label": {
                    "title": "Brother II Genotype",
                    "type": "string",
                },
                "brother_III_genotype_label": {
                    "title": "Brother III Genotype",
                    "type": "string",
                },
                "brother_IV_genotype_label": {
                    "title": "Brother IV Genotype",
                    "type": "string",
                },
                "co_parent_genotype_label": {
                    "title": "Co-Parent Genotype",
                    "type": "string",
                },
                "daughter_genotype_label": {
                    "title": "Daughter Genotype",
                    "type": "string",
                },
                "daughter_II_genotype_label": {
                    "title": "Daughter II Genotype",
                    "type": "string",
                },
                "son_genotype_label": {"title": "Son Genotype", "type": "string"},
                "son_II_genotype_label": {"title": "Son II Genotype", "type": "string"},
            },
        }
    )
    def associated_genotype_labels(
        self, structural_variant, CALL_INFO, samplegeno=None, genotype_labels=None
    ):
        """
        Builds the above sub-embedded object so we can search on the
        genotype labels.
        """
        possible_keys_set = set(StructuralVariantSample.POSSIBLE_GENOTYPE_LABEL_FIELDS)

        def infer_key_from_role(role):
            return role.replace(" ", "_").replace("-", "_") + "_genotype_label"

        # structural variant starts with sv-type_chr* where * is the chrom
        def extract_chrom_from_variant(v):
            chrom_string = v.split("_")[1]
            return chrom_string[3]

        # drop if there are no genotype labels or no samplegeno field or this is a
        # mitochondrial variant
        if (
            not genotype_labels
            or not samplegeno
            or extract_chrom_from_variant(structural_variant) == "M"
        ):
            return None

        new_labels = {}
        for entry in genotype_labels:
            role = entry.get("role", "")
            label = entry.get("labels", [])
            role_key = infer_key_from_role(role)
            if role_key not in possible_keys_set:
                continue
            elif len(label) == 1:
                new_labels[role_key] = label[0]
            else:
                new_labels[role_key] = " ".join(label)  # just in case

        return new_labels

import io

import negspy.coordinates as nc
from snovault import calculated_property, collection, load_schema

from ..ingestion.common import CGAP_CORE_PROJECT
from ..util import resolve_file_path
from .base import Item, get_item_or_none
from .variant import (
    ANNOTATION_ID,
    build_variant_sample_annotation_id,
    extend_embedded_list,
    load_extended_descriptions_in_schemas,
    SHARED_VARIANT_EMBEDS,
    SHARED_VARIANT_SAMPLE_EMBEDS,
)


def build_structural_variant_embedded_list():
    """Determines the embedded_list based on the information
    present in schemas/structural_variant_embeds.json

    :returns: list of structural variant embeds
    """
    embedded_list = SHARED_VARIANT_EMBEDS + []
    with io.open(
        resolve_file_path("schemas/structural_variant_embeds.json"), "r"
    ) as fd:
        extend_embedded_list(embedded_list, fd, "structural_variant")
    return embedded_list + Item.embedded_list


def build_structural_variant_sample_embedded_list():
    """Determines the embedded list for structural variants within
    structural_variant_sample ie: structural_variant.* is not
    sufficient for things embedded into structural variant
    that we'd like to search on in the structural_variant_sample
    context. Works very similary to the above function

    :returns: list of embeds from 'structural_variant' linkTo
    """
    embedded_list = SHARED_VARIANT_SAMPLE_EMBEDS + []
    with io.open(
        resolve_file_path("schemas/structural_variant_embeds.json"), "r"
    ) as fd:
        extend_embedded_list(
            embedded_list, fd, "structural_variant", prefix="structural_variant."
        )
    with io.open(
        resolve_file_path("schemas/structural_variant_sample_embeds.json"), "r"
    ) as fd:
        extend_embedded_list(embedded_list, fd, "structural_variant_sample")
    return ["structural_variant.*"] + embedded_list + Item.embedded_list


def build_structural_variant_display_title(sv_type, chrom, start, end):
    """Builds the structural variant display title according to the type of SV."""
    display_title = "%s_chr%s:%s-%s" % (sv_type, chrom, start, end)
    return display_title


@collection(
    name="structural-variants",
    properties={
        "title": "Structural variants",
        "description": "List of all structural variants",
    },
    unique_key="structural_variant:annotation_id",
)
class StructuralVariant(Item):
    """ Structural variant class """

    item_type = "structural_variant"
    name_key = "annotation_id"
    schema = load_schema("encoded:schemas/structural_variant.json")
    embedded_list = build_structural_variant_embedded_list()

    @classmethod
    def create(cls, registry, uuid, properties, sheets=None):
        """
        Sets the annotation_id field on this structural variant prior to passing on.
        """
        properties[ANNOTATION_ID] = build_structural_variant_display_title(
            properties["SV_TYPE"],
            properties["CHROM"],
            properties["START"],
            properties["END"],
        )
        return super().create(registry, uuid, properties, sheets)

    @calculated_property(
        schema={
            "title": "Display Title",
            "description": "A calculated title for every object in CGAP",
            "type": "string",
        }
    )
    def display_title(self, SV_TYPE, CHROM, START, END):
        return build_structural_variant_display_title(SV_TYPE, CHROM, START, END)

    @calculated_property(
        schema={
            "title": "Start Absolute Position",
            "description": "Start absolute position in genome coordinates",
            "type": "integer",
        }
    )
    def START_ABS(self, CHROM, START):
        chrom_info = nc.get_chrominfo("hg38")
        return nc.chr_pos_to_genome_pos("chr" + CHROM, START, chrom_info)

    @calculated_property(
        schema={
            "title": "End Absolute Position",
            "description": "End absolute position in genome coordinates",
            "type": "integer",
        }
    )
    def END_ABS(self, CHROM, END):
        chrom_info = nc.get_chrominfo("hg38")
        return nc.chr_pos_to_genome_pos("chr" + CHROM, END, chrom_info)

    @calculated_property(
        schema={
            "title": "Size",
            "description": "The size of this structural variant",
            "type": "number",
        }
    )
    def size(self, START, END):
        return END - START + 1

    @calculated_property(
        schema={
            "title": "Size Display",
            "description": "The abbreviated size of this structural variant",
            "type": "string",
        }
    )
    def size_display(self, START, END):
        """
        Create user-friendly display of size.

        Finds appropriate units "bucket" according to number of digits
        in the size, then rounds to 1 decimal place within the "bucket".

        :param START: int start location of SV
        :param END: int end location of SV
        :return: str size plus unit
        """
        result = None
        size = END - START + 1
        unit_max_exponents = [3, 6, 9, 12]  # Gb should be max given chromosome sizes
        display_units = ["bp", "Kb", "Mb", "Gb"]
        for exponent, unit in zip(unit_max_exponents, display_units):
            if size < 10 ** exponent:
                display_number = round(size * 10 ** (-exponent + 3), 1)
                result = str(display_number) + " " + unit
                break
        return result

    @calculated_property(
        schema={
            "title": "Cytoband(s)",
            "description": "The cytoband(s) of this structural variant",
            "type": "string",
        }
    )
    def cytoband_display(self, CHROM, cytoband_start=None, cytoband_end=None):
        """
        Create cytoband to display in portal.

        Requires cytobands at both start and end locations, and result
        is either single cytoband if both identical or dash-separated
        cytobands if different.

        :param CHROM: str chromosome
        :param cytoband_start: str cytoband at SV START
        :param cytoband_end: str cytoband at SV END
        :returns: str cytoband to display
        """
        result = None
        if cytoband_start and cytoband_end:
            if cytoband_start == cytoband_end:
                result = CHROM + cytoband_start
            else:
                result = CHROM + cytoband_start + "-" + CHROM + cytoband_end
        return result

    @calculated_property(
        schema={
            "title": "Gene Summary",
            "description": "An overview of genes overlapping this structural variant",
            "type": "object",
            "properties": {
                "contained": {
                    "title": "Genes Contained in SV",
                    "type": "string",
                    "description": (
                        "The number of genes entirely within the structural variant out"
                        " of all genes affected"
                    ),
                },
                "at_breakpoint": {
                    "title": "Genes Crossing an SV Breakpoint",
                    "type": "string",
                    "description": (
                        "The number of genes overlapping the ends of the structural"
                        " variant out of all genes affected"
                    ),
                },
                "omim_genes": {
                    "title": "Genes with OMIM IDs",
                    "type": "string",
                    "description": (
                        "The number of genes affected by the structural variant that"
                        " exist in the OMIM database out of all genes affected"
                    ),
                },
            },
        }
    )
    def gene_summary(self, request, START, END, transcript=None):
        """
        Calculate summary characteristics of genes overlapped by the
        structural variant.

        NOTE: Genes included here are only those that have some
        overlap with the given SV; genes entirely up-/downstream of the
        SV are not contributing towards any counts (including the
        total).

        :param request: cls pyramid request
        :param START: int SV hg38 start position
        :param END: int SV hg38 end position
        :param transcript: list of VEP annotations
        :returns: dict of summary characteristics
        """
        result = {}
        genes = []
        gene_count = 0
        contained_count = 0
        breakpoint_count = 0
        omim_count = 0
        if transcript:
            for item in transcript:
                gene = item.get("csq_gene")
                if gene and gene not in genes:
                    genes.append(gene)
        for gene in genes:
            gene = get_item_or_none(request, gene, "Gene", frame="raw")
            gene_start = gene.get("spos")
            gene_end = gene.get("epos")
            gene_omim_id = gene.get("omim_id")
            if gene_start and gene_end:  # True for all genes as of 09-14-2021 drr
                if int(gene_start) >= START and int(gene_end) <= END:
                    gene_count += 1
                    contained_count += 1
                    if gene_omim_id:
                        omim_count += 1
                elif int(gene_start) <= END and int(gene_end) >= START:
                    gene_count += 1
                    breakpoint_count += 1
                    if gene_omim_id:
                        omim_count += 1
        result["contained"] = str(contained_count) + "/" + str(gene_count)
        result["at_breakpoint"] = str(breakpoint_count) + "/" + str(gene_count)
        result["omim_genes"] = str(omim_count) + "/" + str(gene_count)
        return result


@collection(
    name="structural-variant-samples",
    properties={
        "title": "Structural variants (sample)",
        "description": "List of all structural variants with sample specific information",
    },
    unique_key="structural_variant_sample:annotation_id",
)
class StructuralVariantSample(Item):
    """Class for structural variant samples."""

    item_type = "structural_variant_sample"
    schema = load_extended_descriptions_in_schemas(
        load_schema("encoded:schemas/structural_variant_sample.json")
    )
    rev = {
        "variant_sample_list": (
            "VariantSampleList",
            "structural_variant_samples.structural_variant_sample_item",
        )
    }
    embedded_list = build_structural_variant_sample_embedded_list()

    POSSIBLE_GENOTYPE_LABEL_FIELDS = [
        "proband_genotype_label",
        "mother_genotype_label",
        "father_genotype_label",
        "sister_genotype_label",
        "sister_II_genotype_label",
        "sister_III_genotype_label",
        "sister_IV_genotype_label",
        "brother_genotype_label",
        "brother_II_genotype_label",
        "brother_III_genotype_label",
        "brother_IV_genotype_label",
        "co_parent_genotype_label",
        "daughter_genotype_label",
        "daughter_II_genotype_label",
        "son_genotype_label",
        "son_II_genotype_label",
    ]

    @classmethod
    def create(cls, registry, uuid, properties, sheets=None):
        """
        Sets the annotation_id field on this structural variant sample
        prior to passing on.
        """
        properties[ANNOTATION_ID] = "%s:%s:%s" % (
            properties["CALL_INFO"],
            properties["structural_variant"],
            properties["file"],
        )
        return super().create(registry, uuid, properties, sheets)

    @calculated_property(
        schema={
            "title": "Display Title",
            "description": "A calculated title for every object in CGAP",
            "type": "string",
        }
    )
    def display_title(self, request, CALL_INFO, structural_variant):
        structural_variant = get_item_or_none(
            request, structural_variant, "StructuralVariant", frame="raw"
        )
        structural_variant_display_title = build_structural_variant_display_title(
            structural_variant["SV_TYPE"],
            structural_variant["CHROM"],
            structural_variant["START"],
            structural_variant["END"],
        )
        if structural_variant:
            return CALL_INFO + ":" + structural_variant_display_title
        return CALL_INFO

    @calculated_property(
        schema={
            "title": "Variant Sample List",
            "description": "The list containing this variant sample",
            "type": "string",
            "linkTo": "VariantSampleList",
        }
    )
    def variant_sample_list(self, request):
        result = self.rev_link_atids(request, "variant_sample_list")
        if result:
            return result[0]  # expected one list per case

    @calculated_property(
        schema={
            "title": "Associated Gene Lists",
            "description": "Gene lists associated with project or case of variant sample",
            "type": "array",
            "items": {
                "title": "Gene list title",
                "type": "string",
                "description": "Gene list title",
            },
        }
    )
    def associated_genelists(self, request, project, structural_variant, CALL_INFO):
        """
        Identifies gene lists associated with the project or project and
        CALL_INFO of the structural variant sample, if the gene list has
        associated BAM sample IDs.

        NOTE: Gene lists retrieved with @@raw view to prevent costly
        @@object view of large gene lists.
        """
        gene_atids = []
        genelist_atids = []
        genelist_info = {}
        associated_genelists = []
        core_project = CGAP_CORE_PROJECT + "/"
        potential_projects = [core_project, project]
        variant_props = get_item_or_none(request, structural_variant)
        transcripts = variant_props.get("transcript", [])
        for transcript in transcripts:
            gene_atid = transcript.get("csq_gene")
            if gene_atid not in gene_atids:
                gene_atids.append(gene_atid)
        genes_object = [get_item_or_none(request, atid) for atid in gene_atids]
        for gene in genes_object:
            genelist_atids += gene.get("gene_lists", [])
        genelist_atids = list(set(genelist_atids))
        genelists_raw = [
            get_item_or_none(request, atid, frame="raw") for atid in genelist_atids
        ]
        for genelist in genelists_raw:
            title = genelist.get("title", "")
            bam_sample_ids = genelist.get("bam_sample_ids", [])
            project_uuid = genelist.get("project")
            project_object = get_item_or_none(request, project_uuid)
            project_atid = project_object.get("@id")
            genelist_info[title] = {
                "project": project_atid,
                "bam_sample_ids": bam_sample_ids,
            }
        for genelist_title, genelist_props in genelist_info.items():
            if genelist_title in associated_genelists:
                continue
            bam_sample_ids = genelist_props.get("bam_sample_ids")
            if genelist_props["project"] in potential_projects:
                if bam_sample_ids:
                    if CALL_INFO in bam_sample_ids:
                        associated_genelists.append(genelist_title)
                else:
                    associated_genelists.append(genelist_title)
        return associated_genelists

    @calculated_property(
        schema={
            "title": "Inheritance Modes",
            "description": "Inheritance Modes (only including those relevant to a proband-only analysis)",
            "type": "array",
            "items": {"type": "string"},
        }
    )
    def proband_only_inheritance_modes(
        self, request, structural_variant, inheritance_modes=[]
    ):
        """
        Inheritance modes for proband-only cases.

        NOTE: Limited only to sex-chromosome-linked inheritance modes
        at the moment. If compound hets aren't going to be included for
        SVs, can consider discarding this property.
        """
        proband_modes = []
        structural_variant = get_item_or_none(
            request, structural_variant, "StructuralVariant", frame="raw"
        )
        if structural_variant["CHROM"] in ["X", "Y"]:
            proband_modes.append(f"{structural_variant['CHROM']}-linked")
        if proband_modes:
            return proband_modes
        return None

    @calculated_property(
        schema={
            "title": "Associated Genotype Labels",
            "description": "Named Genotype Label fields that can be searched on",
            "type": "object",
            "additional_properties": True,
            "properties": {
                "proband_genotype_label": {
                    "title": "Proband Genotype",
                    "type": "string",
                },
                "mother_genotype_label": {"title": "Mother Genotype", "type": "string"},
                "father_genotype_label": {"title": "Father Genotype", "type": "string"},
                "sister_genotype_label": {"title": "Sister Genotype", "type": "string"},
                "sister_II_genotype_label": {
                    "title": "Sister II Genotype",
                    "type": "string",
                },
                "sister_III_genotype_label": {
                    "title": "Sister III Genotype",
                    "type": "string",
                },
                "sister_IV_genotype_label": {
                    "title": "Sister IV Genotype",
                    "type": "string",
                },
                "brother_genotype_label": {
                    "title": "Brother Genotype",
                    "type": "string",
                },
                "brother_II_genotype_label": {
                    "title": "Brother II Genotype",
                    "type": "string",
                },
                "brother_III_genotype_label": {
                    "title": "Brother III Genotype",
                    "type": "string",
                },
                "brother_IV_genotype_label": {
                    "title": "Brother IV Genotype",
                    "type": "string",
                },
                "co_parent_genotype_label": {
                    "title": "Co-Parent Genotype",
                    "type": "string",
                },
                "daughter_genotype_label": {
                    "title": "Daughter Genotype",
                    "type": "string",
                },
                "daughter_II_genotype_label": {
                    "title": "Daughter II Genotype",
                    "type": "string",
                },
                "son_genotype_label": {"title": "Son Genotype", "type": "string"},
                "son_II_genotype_label": {"title": "Son II Genotype", "type": "string"},
            },
        }
    )
    def associated_genotype_labels(
        self, structural_variant, CALL_INFO, samplegeno=None, genotype_labels=None
    ):
        """
        Builds the above sub-embedded object so we can search on the
        genotype labels.
        """
        possible_keys_set = set(StructuralVariantSample.POSSIBLE_GENOTYPE_LABEL_FIELDS)

        def infer_key_from_role(role):
            return role.replace(" ", "_").replace("-", "_") + "_genotype_label"

        # structural variant starts with sv-type_chr* where * is the chrom
        def extract_chrom_from_variant(v):
            chrom_string = v.split("_")[1]
            return chrom_string[3]

        # drop if there are no genotype labels or no samplegeno field or this is a
        # mitochondrial variant
        if (
            not genotype_labels
            or not samplegeno
            or extract_chrom_from_variant(structural_variant) == "M"
        ):
            return None

        new_labels = {}
        for entry in genotype_labels:
            role = entry.get("role", "")
            label = entry.get("labels", [])
            role_key = infer_key_from_role(role)
            if role_key not in possible_keys_set:
                continue
            elif len(label) == 1:
                new_labels[role_key] = label[0]
            else:
                new_labels[role_key] = " ".join(label)  # just in case

        return new_labels

Determines the embedded_list based on the information     present in schemas/structural_variant_embeds.json      :returns: list of structural variant embeds Determines the embedded list for structural variants within     structural_variant_sample ie: structural_variant.* is not     sufficient for things embedded into structural variant     that we'd like to search on in the structural_variant_sample     context. Works very similary to the above function      :returns: list of embeds from 'structural_variant' linkTo Builds the structural variant display title according to the type of SV. Structural variant class Sets the annotation_id field on this structural variant prior to passing on. Create user-friendly display of size.          Finds appropriate units "bucket" according to number of digits         in the size, then rounds to 1 decimal place within the "bucket".          :param START: int start location of SV         :param END: int end location of SV         :return: str size plus unit # Gb should be max given chromosome sizes Create cytoband to display in portal.          Requires cytobands at both start and end locations, and result         is either single cytoband if both identical or dash-separated         cytobands if different.          :param CHROM: str chromosome         :param cytoband_start: str cytoband at SV START         :param cytoband_end: str cytoband at SV END         :returns: str cytoband to display Calculate summary characteristics of genes overlapped by the         structural variant.          NOTE: Genes included here are only those that have some         overlap with the given SV; genes entirely up-/downstream of the         SV are not contributing towards any counts (including the         total).          :param request: cls pyramid request         :param START: int SV hg38 start position         :param END: int SV hg38 end position         :param transcript: list of VEP annotations         :returns: dict of summary characteristics # True for all genes as of 09-14-2021 drr Class for structural variant samples. Sets the annotation_id field on this structural variant sample         prior to passing on. # expected one list per case Identifies gene lists associated with the project or project and         CALL_INFO of the structural variant sample, if the gene list has         associated BAM sample IDs.          NOTE: Gene lists retrieved with @@raw view to prevent costly         @@object view of large gene lists. Inheritance modes for proband-only cases.          NOTE: Limited only to sex-chromosome-linked inheritance modes         at the moment. If compound hets aren't going to be included for         SVs, can consider discarding this property. Builds the above sub-embedded object so we can search on the         genotype labels. # structural variant starts with sv-type_chr* where * is the chrom # drop if there are no genotype labels or no samplegeno field or this is a # mitochondrial variant # just in case