# generated by appcreator
import logging
import re

from django.db import models
from django.urls import reverse
from django.contrib.gis.geos import Polygon, Point, GeometryCollection
from django.contrib.gis.db.models import PolygonField, PointField, GeometryCollectionField
from django.utils.functional import cached_property
from next_prev import next_in_order, prev_in_order
from ckeditor_uploader.fields import RichTextUploadingField
from AcdhArcheAssets.uri_norm_rules import get_normalized_uri
from archiv.utils import parse_date
from archiv.text_processing import process_text

from browsing.browsing_utils import model_to_dict
from vocabs.models import SkosConcept
from story_map.models import Story

from webpage.metadata import PROJECT_METADATA
from layers.models import GeoJsonLayer


logger = logging.getLogger(__name__)


def set_extra(self, **kwargs):
    self.extra = kwargs
    return self


models.Field.set_extra = set_extra

LANG_CHOICES = (
    ('lat', 'lat'),
    ('gre', 'gre'),
    ('und', 'und')
)


class UseCase(models.Model):
    """ Use Case in regards of a specific research questions """
    title = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Title",
        help_text="Title of the Use Case",
    ).set_extra(
        is_public=True,
        arche_prop="hasTitle",
    )
    principal_investigator = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="PI",
        help_text="Principal Investigator of the Use Case",
    ).set_extra(
        is_public=True,
        arche_prop="hasPrincipalInvestigator",
    )
    pi_norm_id = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Some Norm-ID of the PI",
        help_text="e.g. GND-ID or ORCID",
    ).set_extra(
        is_public=True,
        arche_prop="hasPrincipalInvestigator",
    )
    description = models.TextField(
        blank=True, null=True,
        verbose_name="Description",
        help_text="Short Description of the Use Case",
    ).set_extra(
        is_public=True,
        arche_prop="hasDescription",
    )
    story_map = RichTextUploadingField(
        blank=True,
        null=True,
        verbose_name="Story Map",
        help_text="Story Map"
    )
    knightlab_stoy_map = models.ManyToManyField(
        Story,
        related_name='has_use_case',
        blank=True,
        verbose_name="Knightlab Story Map",
        help_text="Knightlab Story Map",
    ).set_extra(
        is_public=True,
    )
    layer = models.ManyToManyField(
        GeoJsonLayer,
        related_name='use_case',
        blank=True,
        verbose_name='GeoJson Layers',
        help_text="Select GeoJson Layers which should be related to this UseCase"
    )
    show_labels = models.BooleanField(
        default=False,
        verbose_name="Show Labels",
        help_text="True if labels of the Spatial Coverage should be visible in the front end"
    )
    published = models.BooleanField(
        default=False,
        verbose_name="Published",
        help_text="True if the use case should be presented in the frontend, false if not"
    )

    class Meta:

        ordering = [
            'title',
        ]
        verbose_name = "Use Case"

    def __str__(self):
        return f"{self.title}"

    def field_dict(self):
        return model_to_dict(self)

    @cached_property
    def get_texts(self):
        text = Text.objects.filter(
            rvn_stelle_text_text__use_case=self
        ).distinct()
        return text

    @cached_property
    def get_authors(self):
        author = Autor.objects.filter(
            rvn_text_autor_autor__in=self.get_texts
        ).distinct()
        return author

    @cached_property
    def get_keywords(self):
        keyword = KeyWord.objects.filter(
            rvn_stelle_key_word_keyword__use_case=self
        ).distinct()
        return keyword

    @cached_property
    def get_events(self):
        event = Event.objects.all()
        return event

    @classmethod
    def get_source_table(self):
        return None

    @classmethod
    def get_listview_url(self):
        return reverse('archiv:usecase_browse')

    @classmethod
    def get_natural_primary_key(self):
        return "id"

    @classmethod
    def get_createview_url(self):
        return reverse('archiv:usecase_create')

    def get_absolute_url(self):
        return reverse('archiv:usecase_detail', kwargs={'pk': self.id})

    def get_delete_url(self):
        return reverse('archiv:usecase_delete', kwargs={'pk': self.id})

    def get_edit_url(self):
        return reverse('archiv:usecase_edit', kwargs={'pk': self.id})

    def get_next(self):
        next = next_in_order(self)
        if next:
            return next.get_absolute_url()
        return False

    def get_prev(self):
        prev = prev_in_order(self)
        if prev:
            return prev.get_absolute_url()
        return False

    def get_timetable_data(self):
        time_table_data = []
        if self.get_texts:
            for x in self.get_texts:
                try:
                    time_table_data.append(
                        {
                            'id': x.id,
                            'start_date': int(x.start_date),
                            'end_date': x.end_date,
                            'ent_type': 'text',
                            'ent_title': x.title,
                            'ent_description': x.title,
                            'ent_detail_view': x.get_absolute_url()
                        }
                    )
                except Exception:
                    pass
        if self.get_authors:
            for x in self.get_authors:
                try:
                    time_table_data.append(
                        {
                            'id': x.id,
                            'start_date': int(x.start_date),
                            'end_date': x.end_date,
                            'ent_type': 'autor',
                            'ent_title': x.name,
                            'ent_description': x.name,
                            'ent_detail_view': x.get_absolute_url()
                        }
                    )
                except Exception:
                    pass
        if self.get_events:
            for x in self.get_events:
                try:
                    time_table_data.append(
                        {
                            'id': x.id,
                            'start_date': int(x.start_date),
                            'end_date': x.end_date,
                            'ent_type': 'event',
                            'ent_title': x.title,
                            'ent_description': x.description,
                            'ent_detail_view': x.get_absolute_url()
                        }
                    )
                except Exception:
                    pass
        return sorted(time_table_data, key=lambda k: k['start_date'])


class Autor(models.Model):
    """ Autor """
    legacy_id = models.CharField(
        max_length=300, blank=True,
        verbose_name="Legacy ID"
    )
    legacy_pk = models.IntegerField(
        blank=True, null=True,
        verbose_name="Legacy ID",
        help_text="Legacy id (from GENS database)",
    ).set_extra(
        is_public=False,
        data_lookup="aid",
        arche_prop="hasNonLinkedIdentifier",
        arche_prop_str_template="legacy ID: <value>",
    )
    name = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Name (de)",
        help_text="Author's name in German",
    ).set_extra(
        is_public=True,
        data_lookup="anamed",
        arche_prop="hasTitle",
    )
    gnd_id = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="GND ID",
        help_text="ID in GND",
    ).set_extra(
        is_public=True,
        arche_prop="hasNonLinkedIdentifier",
        arche_prop_str_template="GND-ID: <value>",
    )
    name_lat = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Name (lat)",
        help_text="Author's name in Latin",
    ).set_extra(
        is_public=True,
        data_lookup="anamelat",
        arche_prop="hasAlternativeTitle",
    )
    name_en = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Name (en)",
        help_text="Author's name in English",
    ).set_extra(
        is_public=True,
        data_lookup="anameeng",
        arche_prop="hasAlternativeTitle",
    )
    name_fr = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Name (fr)",
        help_text="Author's name in French",
    ).set_extra(
        is_public=True,
        data_lookup="anamefr",
        arche_prop="hasAlternativeTitle",
    )
    name_it = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Name (it)",
        help_text="Author's name in Italian",
    ).set_extra(
        is_public=True,
        data_lookup="anameit",
        arche_prop="hasAlternativeTitle",
    )
    name_gr = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Name (gr)",
        help_text="Author's name in Greek",
    ).set_extra(
        is_public=True,
        arche_prop="hasAlternativeTitle",
    )
    jahrhundert = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Century",
        help_text="Century or centuries active",
    ).set_extra(
        is_public=True,
        data_lookup="ajh",
    )
    start_date = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="From",
        help_text="Birth or earliest year of activity",
    ).set_extra(
        is_public=True,
        data_lookup="avon",
    )
    end_date = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="To",
        help_text="Death or end of activity",
    ).set_extra(
        is_public=True,
        data_lookup="abis",
    )
    start_date_year = models.SmallIntegerField(
        blank=True, null=True,
        verbose_name="Start Date",
        help_text="e.g. '300'; Muss (!) als Zahl eingegeben werden"
    ).set_extra(
        is_public=True,
        arche_prop="hasCoverageStartDate"
    )
    end_date_year = models.SmallIntegerField(
        blank=True, null=True,
        verbose_name="End Date",
        help_text="e.g. '350', Muss (!) als Zahl eingegeben werden"
    ).set_extra(
        is_public=True,
        arche_prop="hasCoverageEndDate"
    )
    ort = models.ForeignKey(
        "Ort",
        related_name='rvn_autor_ort_ort',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        verbose_name="Place",
        help_text="Place associated with author (of birth or activity)",
    ).set_extra(
        is_public=True,
        data_lookup="aort",
    )
    kommentar = models.TextField(
        blank=True, null=True,
        verbose_name="Comment",
        help_text="Comment",
    ).set_extra(
        is_public=True,
        data_lookup="akommentar",
        arche_prop="hasNote",
    )
    orig_data_csv = models.TextField(
        blank=True,
        null=True,
        verbose_name="Original data"
    ).set_extra(
        is_public=True
    )

    class Meta:

        ordering = [
            'id',
        ]
        verbose_name = "Autor"

    def __str__(self):
        if self.name_en or self.name_en != "":
            return f"{self.name_en}"
        else:
            return f"no english name provided for autor id:{self.id}"

    def field_dict(self):
        return model_to_dict(self)

    @classmethod
    def get_listview_url(self):
        return reverse('archiv:autor_browse')

    @classmethod
    def get_source_table(self):
        return "autor"

    @classmethod
    def get_natural_primary_key(self):
        return "legacy_pk"

    @classmethod
    def get_createview_url(self):
        return reverse('archiv:autor_create')

    @cached_property
    def get_stellen(self):
        stellen = Stelle.objects.filter(
            text__autor=self
        ).distinct()
        return stellen

    @cached_property
    def get_keywords(self):
        keywords = KeyWord.objects.filter(
            rvn_stelle_key_word_keyword__in=self.get_stellen
        ).distinct()
        return keywords

    def save(self, *args, **kwargs):
        if self.start_date and not self.start_date_year:
            try:
                self.start_date_year = int(self.start_date)
            except ValueError:
                pass
        if self.end_date and not self.end_date_year:
            try:
                self.end_date_year = int(self.end_date)
            except ValueError:
                pass
        if self.gnd_id:
            gnd = self.gnd_id
            if "lobid" in gnd:
                self.gnd_id = get_normalized_uri(gnd.replace("lobid.org", "d-nb.info"))
            elif gnd.startswith('http'):
                self.gnd_id = get_normalized_uri(gnd)
            else:
                self.gnd_id = get_normalized_uri(f"https://d-nb.info/gnd/{gnd}")
        super(Autor, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('archiv:autor_detail', kwargs={'pk': self.id})

    def get_delete_url(self):
        return reverse('archiv:autor_delete', kwargs={'pk': self.id})

    def get_edit_url(self):
        return reverse('archiv:autor_edit', kwargs={'pk': self.id})

    def get_next(self):
        next = next_in_order(self)
        if next:
            return next.get_absolute_url()
        return False

    def get_prev(self):
        prev = prev_in_order(self)
        if prev:
            return prev.get_absolute_url()
        return False

    def get_names_list(self):
        name_list = []
        for attr in self._meta.get_fields():
            if attr.name.startswith("name_"):
                name_list.append([
                    attr.name.split("_")[1], getattr(self, attr.name)
                ])
            elif attr.name == "name":
                name_list.append([
                    "de", getattr(self, attr.name)
                ])
        return name_list

    def start_date_tei_normalized(self):
        if self.start_date:
            startdate = re.search(r'\d+', self.start_date).group()
            return f"{int(startdate):04}"
        else:
            return ""

    def end_date_tei_normalized(self):
        if self.end_date:
            enddate = re.search(r'\d+', self.end_date).group()
            return f"{int(enddate):04}"
        else:
            return ""


class KeyWord(models.Model):
    """ Keyword """
    legacy_id = models.CharField(
        max_length=300, blank=True,
        verbose_name="Legacy ID"
    )
    legacy_pk = models.IntegerField(
        blank=True, null=True,
        verbose_name="Old primary key",
        help_text="Old primary key (from GENS database)",
    ).set_extra(
        is_public=False,
        data_lookup="stid",
        arche_prop="hasNonLinkedIdentifier",
        arche_prop_str_template="legacy ID: <value>",
    )
    stichwort = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Keyword",
        help_text="Keyword",
    ).set_extra(
        is_public=True,
        data_lookup="stichwort",
        arche_prop="hasTitle",
    )
    name_gr = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Name (gr)",
        help_text="Name in Greek",
    ).set_extra(
        is_public=True,
        arche_prop="hasAlternativeTitle",
    )
    art = models.CharField(
        max_length=250,
        blank=True,
        choices=(
            ('Keyword', 'Keyword'),
            ('Name', 'Name'),
            ('Ethnonym', 'Ethnonym'),
            ('Region', 'Region'),
            ('unclear', 'unclear')
        ),
        verbose_name="Type of keyword",
        help_text="Type of keyword",
    ).set_extra(
        is_public=True,
        data_lookup="start",
    )
    varianten = models.TextField(
        blank=True,
        null=True,
        verbose_name="Variants",
        help_text="Variants forms (separated with ;)"
    )
    wurzel = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Root",
        help_text="Root",
    ).set_extra(
        is_public=True,
        data_lookup="wurzel",
    )
    related_keyword = models.ManyToManyField(
        "KeyWord",
        related_name='rvn_related_keyword',
        blank=True,
        verbose_name="Linked keyword",
        help_text="Other keyword linked to this one",
    ).set_extra(
        is_public=True,
    )
    kommentar = models.TextField(
        blank=True, null=True,
        verbose_name="Comment",
        help_text="Comment",
    ).set_extra(
        is_public=True,
        data_lookup="anmerkung",
        arche_prop="hasNote",
    )
    orig_data_csv = models.TextField(
        blank=True,
        null=True,
        verbose_name="Original data"
    ).set_extra(
        is_public=True
    )

    class Meta:

        ordering = [
            'stichwort',
        ]
        verbose_name = "Keyword"

    def __str__(self):
        if self.stichwort:
            return f"{self.stichwort}, [wurzel: {self.wurzel}]"
        else:
            return "{}".format(self.legacy_id)

    @cached_property
    def get_texts(self):
        texts = Text.objects.filter(
            rvn_stelle_text_text__key_word=self
        ).distinct()
        return texts

    @cached_property
    def get_authors(self):
        authors = Autor.objects.filter(
            rvn_text_autor_autor__in=self.get_texts
        ).distinct()
        return authors

    @cached_property
    def get_orte(self):
        orte = Ort.objects.filter(
            rvn_autor_ort_ort__in=self.get_authors
        ).distinct()
        return orte

    def field_dict(self):
        return model_to_dict(self)

    @classmethod
    def get_listview_url(self):
        return reverse('archiv:keyword_browse')

    @classmethod
    def get_source_table(self):
        return None

    @classmethod
    def get_natural_primary_key(self):
        return "stichwort"

    @classmethod
    def get_createview_url(self):
        return reverse('archiv:keyword_create')

    def get_absolute_url(self):
        return reverse('archiv:keyword_detail', kwargs={'pk': self.id})

    def get_keyword_by_century(self):
        return reverse('archiv:keyword_by_century', kwargs={'pk': self.id})

    def get_delete_url(self):
        return reverse('archiv:keyword_delete', kwargs={'pk': self.id})

    def get_edit_url(self):
        return reverse('archiv:keyword_edit', kwargs={'pk': self.id})

    def get_next(self):
        next = next_in_order(self)
        if next:
            return next.get_absolute_url()
        return False

    def get_prev(self):
        prev = prev_in_order(self)
        if prev:
            return prev.get_absolute_url()
        return False


class Ort(models.Model):
    """ Ort """
    legacy_id = models.CharField(
        max_length=300, blank=True,
        verbose_name="Legacy ID"
    )
    legacy_pk = models.IntegerField(
        blank=True, null=True,
        verbose_name="Old primary key",
        help_text="Old primary key (from GENS database)",
    ).set_extra(
        is_public=False,
        data_lookup="ortid",
        arche_prop="hasNonLinkedIdentifier",
        arche_prop_str_template="legacy ID: <value>",
    )
    name = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Name (en)",
        help_text="Name of the place in English",
    ).set_extra(
        is_public=True,
        data_lookup="Ort_en",
        arche_prop="hasTitle",
    )
    norm_id = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Norm-ID",
        help_text="Norm ID",
    ).set_extra(
        is_public=True,
        arche_prop="hasNonLinkedIdentifier",
        arche_prop_str_template="NormID: <value>",
    )
    name_antik = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Name (ancient)",
        help_text="Name of the place in ancient sources",
    ).set_extra(
        is_public=True,
        data_lookup="Ort_antik",
        arche_prop="hasAlternativeTitle",
    )
    name_de = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Name (de)",
        help_text="Name of the place in German",
    ).set_extra(
        is_public=True,
        data_lookup="Ort_de",
        arche_prop="hasAlternativeTitle",
    )
    name_fr = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Name (fr)",
        help_text="Name of the place in French",
    ).set_extra(
        is_public=True,
        data_lookup="Ort_fr",
        arche_prop="hasAlternativeTitle",
    )
    name_it = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Name (it)",
        help_text="Name of the place in Italian",
    ).set_extra(
        is_public=True,
        data_lookup="Ort_it",
        arche_prop="hasAlternativeTitle",
    )
    name_gr = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Name (gr)",
        help_text="Name of the place in Greek",
    ).set_extra(
        is_public=True,
        arche_prop="hasAlternativeTitle",
    )
    long = models.FloatField(
        blank=True, null=True,
        verbose_name="Longitude",
        help_text="Longitude",
    ).set_extra(
        is_public=True,
        data_lookup="KoordW",
    )
    lat = models.FloatField(
        blank=True, null=True,
        verbose_name="Latitude",
        help_text="Latitude",
    ).set_extra(
        is_public=True,
        data_lookup="KoordN",
    )
    coords = PointField(
        blank=True,
        null=True,
        help_text="Coordinates (automatically generated)",
        verbose_name="Coordinates"
    )
    fuzzy_geom = GeometryCollectionField(
        blank=True, null=True,
        verbose_name="Approximate localisation",
        help_text="Approximate localisation of an area",
    ).set_extra(
        is_public=True,
        arche_prop="hasWkt",
    )
    art = models.ForeignKey(
        SkosConcept,
        related_name='rvn_ort_art_skosconcept',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        verbose_name="Type",
        help_text="Type of place",
    ).set_extra(
        is_public=True,
        data_lookup="Art",
    )
    kategorie = models.ForeignKey(
        SkosConcept,
        related_name='rvn_ort_kategorie_skosconcept',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        verbose_name="Category",
        help_text="Category of place",
    ).set_extra(
        is_public=True,
        data_lookup="Kategorie",
    )
    kommentar = models.TextField(
        blank=True, null=True,
        verbose_name="Comment",
        help_text="Comment",
    ).set_extra(
        is_public=True,
        data_lookup="Kommentar",
        arche_prop="hasNote",
    )
    orig_data_csv = models.TextField(
        blank=True,
        null=True,
        verbose_name="Original data"
    ).set_extra(
        is_public=True
    )

    class Meta:

        ordering = [
            'id',
        ]
        verbose_name = "Ort"

    def __str__(self):
        if self.name and self.name != "":
            return f"{self.name}"
        else:
            return f"no english name provided for id: {self.id}"

    def save(self, *args, **kwargs):
        if self.long and self.lat:
            self.coords = Point(self.long, self.lat)
        super(Ort, self).save(*args, **kwargs)

    def field_dict(self):
        return model_to_dict(self)

    @cached_property
    def place_as_dict(self):
        if self.art is not None:
            art = {
                "id": self.art.id,
                "label": self.art.pref_label
            }
        else:
            art = None

        pl = {
            "id": self.id,
            "name": self.name,
            "name_antik": self.name_antik,
            "lat": self.lat,
            "lng": self.long,
            "art": art
        }
        return pl

    @cached_property
    def all_geoms(self):
        if self.fuzzy_geom is not None:
            geom = self.fuzzy_geom
        else:
            geom = GeometryCollection()
        if self.coords:
            geom.append(self.coords)
        return geom

    @classmethod
    def get_listview_url(self):
        return reverse('archiv:ort_browse')

    @classmethod
    def get_source_table(self):
        return "orte"

    @classmethod
    def get_natural_primary_key(self):
        return "legacy_pk"

    @classmethod
    def get_createview_url(self):
        return reverse('archiv:ort_create')

    def get_absolute_url(self):
        return reverse('archiv:ort_detail', kwargs={'pk': self.id})

    def get_delete_url(self):
        return reverse('archiv:ort_delete', kwargs={'pk': self.id})

    def get_edit_url(self):
        return reverse('archiv:ort_edit', kwargs={'pk': self.id})

    def get_next(self):
        next = next_in_order(self)
        if next:
            return next.get_absolute_url()
        return False

    def get_prev(self):
        prev = prev_in_order(self)
        if prev:
            return prev.get_absolute_url()
        return False

    def get_names_list(self):
        name_list = []
        for attr in self._meta.get_fields():
            if attr.name.startswith("name_"):
                name_list.append([
                    attr.name.split("_")[1], getattr(self, attr.name)
                ])
            elif attr.name == "name":
                name_list.append([
                    "en", getattr(self, attr.name)
                ])
        return name_list

    @cached_property
    def kind(self):
        if self.art:
            art = {
                "id": self.art.id,
                "label": self.art.pref_label
            }
        else:
            art = {
                "id": None,
                "label": "no place type provided"
            }
        return art


class Stelle(models.Model):
    """ Stelle """
    legacy_id = models.CharField(
        max_length=300, blank=True,
        verbose_name="Legacy ID"
    )
    legacy_pk = models.IntegerField(
        blank=True, null=True,
        verbose_name="Old primary key",
        help_text="Old primary key (from GENS database)",
    ).set_extra(
        is_public=False,
        data_lookup="sid",
        arche_prop="hasNonLinkedIdentifier",
        arche_prop_str_template="legacy ID: <value>",
    )
    text = models.ForeignKey(
        "Text",
        related_name='rvn_stelle_text_text',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        verbose_name="Text",
        help_text="Text",
    ).set_extra(
        is_public=True,
        data_lookup="stexttitel",
    )
    summary = models.TextField(
        blank=True, null=True,
        verbose_name="Summary",
        help_text="Summary",
    ).set_extra(
        is_public=True,
        data_lookup="ssummary",
        arche_prop="hasDescription",
    )
    zitat = models.TextField(
        blank=True, null=True,
        verbose_name="Quotation",
        help_text="Text of the passage in the original language",
    ).set_extra(
        is_public=True,
        data_lookup="szitat",
        arche_prop="hasDescription",
    )
    zitat_stelle = models.CharField(
        max_length=250,
        blank=True, null=True,
        verbose_name="Quotation source",
        help_text="Chapter and/or page numbers for the passage",
    ).set_extra(
        is_public=True,
        arche_prop="hasNote",
    )
    translation = models.TextField(
        blank=True, null=True,
        verbose_name="Translation",
        help_text="Translation",
    ).set_extra(
        is_public=True,
        data_lookup="stranslation",
    )
    key_word = models.ManyToManyField(
        "KeyWord",
        related_name='rvn_stelle_key_word_keyword',
        blank=True,
        verbose_name="Keyword",
        help_text="Keyword(s) asssociated with the passage",
    ).set_extra(
        is_public=True,
        arche_prop="hasSubject",
    )
    start_date = models.SmallIntegerField(
        blank=True, null=True,
        verbose_name="From",
        help_text="Start date or earliest possible date"
    ).set_extra(
        is_public=True,
        arche_prop="hasCoverageStartDate"
    )
    end_date = models.SmallIntegerField(
        blank=True, null=True,
        verbose_name="To",
        help_text="End date or last possible date'"
    ).set_extra(
        is_public=True,
        arche_prop="hasCoverageEndDate"
    )
    kommentar = models.TextField(
        blank=True, null=True,
        verbose_name="Comment",
        help_text="Comment",
    ).set_extra(
        is_public=True,
        data_lookup="skommentar",
        arche_prop="hasNote",
    )
    display_label = models.CharField(
        max_length=250,
        blank=True,
        null=True,
        verbose_name="Display label"
    )
    orig_data_csv = models.TextField(
        blank=True,
        null=True,
        verbose_name="Original data"
    ).set_extra(
        is_public=True
    )
    use_case = models.ManyToManyField(
        "UseCase",
        related_name='has_stelle',
        blank=True,
        verbose_name="Use case",
        help_text="Associated use case(s)",
    ).set_extra(
        is_public=True,
    )
    lemmata = models.JSONField(
        blank=True, null=True,
        verbose_name="Quotation (lemmatised)",
        help_text="A lemmatised version of the passage"
    )

    class Meta:

        ordering = [
            'id',
        ]
        verbose_name = "Stelle"

    def save(self, *args, **kwargs):
        self.display_label = self.make_label()
        if self.zitat and self.lemmata is None:
            self.lemmata = process_text(self.zitat)
        super(Stelle, self).save(*args, **kwargs)

    def make_label(self):
        try:
            label = f"{self.zitat[:35]}...; ({self.text}, {self.zitat_stelle})"
        except Exception as e:
            logger.error(
                f'Stelle.make_label in object: {self.id} threw error {e}'
            )
            label = "{}".format(self.id)
        return label[:249]

    def __str__(self):
        if self.display_label:
            return f"{self.display_label} [{self.id}]"
        else:
            return f"{self.id}"

    def field_dict(self):
        return model_to_dict(self)

    @classmethod
    def get_listview_url(self):
        return reverse('archiv:stelle_browse')

    @classmethod
    def get_source_table(self):
        return "stelle"

    @classmethod
    def get_natural_primary_key(self):
        return "legacy_pk"

    @classmethod
    def get_createview_url(self):
        return reverse('archiv:stelle_create')

    def get_absolute_url(self):
        return reverse('archiv:stelle_detail', kwargs={'pk': self.id})

    def get_delete_url(self):
        return reverse('archiv:stelle_delete', kwargs={'pk': self.id})

    def get_edit_url(self):
        return reverse('archiv:stelle_edit', kwargs={'pk': self.id})

    def get_next(self):
        next = next_in_order(self)
        if next:
            return next.get_absolute_url()
        return False

    def get_prev(self):
        prev = prev_in_order(self)
        if prev:
            return prev.get_absolute_url()
        return False

    def tei_markup_foreign(self, res):
        text = res
        text = re.sub(r"<", "[", text, flags=re.IGNORECASE)
        text = re.sub(r">", "]", text, flags=re.IGNORECASE)
        language = self.text.text_lang
        if self.key_word:
            for k in self.key_word.all():
                if k.wurzel:
                    text = re.sub(rf"([“,,\",′,\s,\(,',‘])({ k.wurzel }\w+?)([′,\s,\.,\,,\!,\?,\),\",',’,”,;])",
                                  r"\1" + "<foreign xml:lang='%s'>" % (language) + r"\2" +
                                  "</foreign>" + r"\3",
                                  text,
                                  flags=re.IGNORECASE)
                else:
                    variants = k.varianten.split(";")
                    for v in variants:
                        if len(v) != 0:
                            text = re.sub(rf"([“,,\",′,\s,\(,',‘])({ v }\w+?)([′,\s,\.,\,,\!,\?,\),\",',’,”,;])",
                                          r"\1" + "<foreign xml:lang='%s'>" % (language) + r"\2" +
                                          "</foreign>" + r"\3",
                                          text,
                                          flags=re.IGNORECASE)
                if k.stichwort:
                    text = re.sub(rf"([“,,\",′,\s,\(,',‘])({ k.stichwort })([′,\s,\.,\,,\!,\?,\),\",',’,”,;])",
                                  r"\1" + "<foreign xml:lang='%s'>" % (language) + r"\2" +
                                  "</foreign>" + r"\3",
                                  text,
                                  flags=re.IGNORECASE)
            return text

    def translation_markup(self):
        if self.translation:
            res = self.translation
            return self.tei_markup_foreign(res)
        else:
            return ""

    def summary_markup(self):
        if self.summary:
            res = self.summary
            return self.tei_markup_foreign(res)
        else:
            return ""


class Text(models.Model):
    """ Text """
    legacy_id = models.CharField(
        max_length=300, blank=True,
        verbose_name="Legacy ID"
    )
    legacy_pk = models.IntegerField(
        blank=True, null=True,
        verbose_name="Old primary key",
        help_text="Old primary key (from GENS database)",
    ).set_extra(
        is_public=False,
        data_lookup="tID",
        arche_prop="hasNonLinkedIdentifier",
        arche_prop_str_template="legacy ID: <value>",
    )
    autor = models.ManyToManyField(
        "Autor",
        related_name='rvn_text_autor_autor',
        blank=True,
        verbose_name="Author",
        help_text="Author",
    ).set_extra(
        is_public=True,
        data_lookup="tautor",
        arche_prop="hasAuthor",
    )
    title = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Title",
        help_text="Title of the text",
    ).set_extra(
        is_public=True,
        data_lookup="ttitel",
        arche_prop="hasTitle",
    )
    alt_title = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Alternative title",
        help_text="Alternative title(s) of the text",
    ).set_extra(
        is_public=True,
        arche_prop="hasAlternativeTitle",
    )
    text_lang = models.CharField(
        max_length=250,
        blank=True,
        default='lat',
        verbose_name="Language",
        help_text="Language of the original text (default Latin)",
    ).set_extra(
        is_public=True,
        arche_prop="hasLanguage",
    )
    jahrhundert = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="Century",
        help_text="Century",
    ).set_extra(
        is_public=True,
        data_lookup="tjh",
    )
    start_date = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="From",
        help_text="Earliest possible date of composition",
    ).set_extra(
        is_public=True,
        data_lookup="tzeitvon",
    )
    end_date = models.CharField(
        max_length=250,
        blank=True,
        verbose_name="To",
        help_text="Last possible date of composition",
    ).set_extra(
        is_public=True,
        data_lookup="tzeitbis",
    )
    not_before = models.IntegerField(
        blank=True,
        null=True,
        verbose_name="not before",
        help_text="YYY or YYYY"
    )
    not_after = models.IntegerField(
        blank=True,
        null=True,
        verbose_name="not after",
        help_text="YYY or YYYY"
    )
    edition = models.CharField(
        max_length=350,
        blank=True,
        verbose_name="Edition",
        help_text="Edition",
    ).set_extra(
        is_public=True,
        data_lookup="tedition1",
    )
    art = models.ForeignKey(
        SkosConcept,
        related_name='rvn_text_art_skosconcept',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        verbose_name="Genre",
        help_text="Genre of text",
    ).set_extra(
        is_public=True,
        data_lookup="tart1",
    )
    ort = models.ManyToManyField(
        "Ort",
        related_name='rvn_text_ort_ort',
        blank=True,
        verbose_name="Place",
        help_text="Place of composition",
    ).set_extra(
        is_public=True,
        data_lookup="tort",
    )
    kommentar = models.TextField(
        blank=True, null=True,
        verbose_name="Comment",
        help_text="Comment",
    ).set_extra(
        is_public=True,
        data_lookup="tkommentar",
        arche_prop="hasNote",
    )
    orig_data_csv = models.TextField(
        blank=True,
        null=True,
        verbose_name="Original data"
    ).set_extra(
        is_public=True
    )

    class Meta:

        ordering = [
            'title',
        ]
        verbose_name = "Text"

    def __str__(self):
        if self.title:
            return f"{self.title} ({self.not_before} - {self.not_after})"
        else:
            return f"{self.legacy_id} ({self.not_before} - {self.not_after})"

    def field_dict(self):
        return model_to_dict(self)

    def get_tei_url(self):
        return reverse('archiv:text_xml', kwargs={'pk': self.id})

    @classmethod
    def get_listview_url(self):
        return reverse('archiv:text_browse')

    @classmethod
    def get_source_table(self):
        return "text"

    @classmethod
    def get_natural_primary_key(self):
        return "legacy_pk"

    @classmethod
    def get_createview_url(self):
        return reverse('archiv:text_create')

    def get_absolute_url(self):
        return reverse('archiv:text_detail', kwargs={'pk': self.id})

    def get_delete_url(self):
        return reverse('archiv:text_delete', kwargs={'pk': self.id})

    def get_edit_url(self):
        return reverse('archiv:text_edit', kwargs={'pk': self.id})

    def get_next(self):
        next = next_in_order(self)
        if next:
            return next.get_absolute_url()
        return False

    def get_prev(self):
        prev = prev_in_order(self)
        if prev:
            return prev.get_absolute_url()
        return False

    def get_next_id(self):
        next = next_in_order(self)
        if next:
            return next.id
        return False

    def get_prev_id(self):
        prev = prev_in_order(self)
        if prev:
            return prev.id
        return False

    def get_project_metadata(self):
        metadata = PROJECT_METADATA
        return metadata

    def save(self, *args, **kwargs):
        if self.start_date and not self.not_before:
            self.not_before = parse_date(self.start_date)
        if self.end_date and not self.not_after:
            self.not_after = parse_date(self.end_date, default=2000)
        if not self.start_date and not self.not_before:
            self.not_before = -100
        if not self.end_date and not self.not_after:
            self.not_after = 2020
        super(Text, self).save(*args, **kwargs)

    def not_before_tei_normalized(self):
        if self.not_before:
            notBefore = self.not_before
            if str(notBefore).startswith("-"):
                return f"{notBefore:05}"
            else:
                return f"{notBefore:04}"
        else:
            return ""

    def not_after_tei_normalized(self):
        if self.not_after:
            notAfter = self.not_after
            return f"{notAfter:04}"
        else:
            return ""


class Event(models.Model):
    title = models.CharField(
        max_length=250,
        blank=True, null=True,
        verbose_name="Title",
        help_text="Name of the event"
    )
    description = models.TextField(
        blank=True, null=True,
        verbose_name="Description",
        help_text="Description"
    )
    start_date = models.IntegerField(
        blank=True,
        null=True,
        verbose_name="From",
        help_text="Start date or earliest possible date",
    ).set_extra(
        is_public=True,
    )
    end_date = models.IntegerField(
        blank=True,
        null=True,
        verbose_name="To",
        help_text="End date or last possible date",
    ).set_extra(
        is_public=True,
    )
    written_date = models.CharField(
        max_length=250,
        blank=True, null=True,
        verbose_name="Date",
        help_text="Specific year"
    )
    use_case = models.ManyToManyField(
        "UseCase",
        related_name='has_event',
        blank=True,
        verbose_name="Use case",
        help_text="Associated use case(s)",
    ).set_extra(
        is_public=True
    )

    class Meta:

        ordering = [
            'title',
        ]
        verbose_name = "Event"

    def __str__(self):
        return f"{self.title}"

    def field_dict(self):
        return model_to_dict(self)

    @classmethod
    def get_listview_url(self):
        return reverse('archiv:event_browse')

    @classmethod
    def get_createview_url(self):
        return reverse('archiv:event_create')

    def get_absolute_url(self):
        return reverse('archiv:event_detail', kwargs={'pk': self.id})

    def get_delete_url(self):
        return reverse('archiv:event_delete', kwargs={'pk': self.id})

    def get_edit_url(self):
        return reverse('archiv:event_edit', kwargs={'pk': self.id})

    def get_next(self):
        next = next_in_order(self)
        if next:
            return next.get_absolute_url()
        return False

    def get_prev(self):
        prev = prev_in_order(self)
        if prev:
            return prev.get_absolute_url()
        return False


class SpatialCoverage(models.Model):
    """ Spatial Coverage of a Keyword bound to a specifc source document"""
    stelle = models.ManyToManyField(
        "Stelle",
        related_name='has_spatial_coverage',
        blank=True,
        verbose_name="Passage",
        help_text="Passage associated with coverage",
    ).set_extra(
        is_public=True,
    )
    key_word = models.ForeignKey(
        "KeyWord",
        related_name='has_spatial_coverage',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        verbose_name="Keyword",
        help_text="Keyword associated with coverage",
    ).set_extra(
        is_public=True,
        arche_prop="hasSubject",
    )
    kommentar = models.TextField(
        blank=True, null=True,
        verbose_name="Comment",
        help_text="Comment",
    ).set_extra(
        is_public=True,
        arche_prop="hasNote",
    )
    fuzzy_geom = PolygonField(
        blank=True, null=True,
        verbose_name="Approximate localisation",
        help_text="Approximate localisation of an area",
    ).set_extra(
        is_public=True,
        arche_prop="hasWkt",
    )
    geom_collection = GeometryCollectionField(
        blank=True, null=True,
        verbose_name="Points and lines",
        help_text="points and lines",
    ).set_extra(
        is_public=True,
        arche_prop="hasWkt",
    )
    fuzzyness = models.IntegerField(
        choices=[(i, i) for i in range(1, 11)],
        blank=True,
        default=1,
        verbose_name="Degree of uncertainty",
        help_text="Uncertainty of location on a scale from 1 (very secure) to 10 (very insecure)"
    ).set_extra(
        is_public=True,
    )

    class Meta:

        ordering = [
            'id',
        ]
        verbose_name = "Spatial Coverage"

    def __str__(self):
        return f"{self.stelle.all()} - {self.key_word}"

    def field_dict(self):
        return model_to_dict(self)

    @classmethod
    def get_source_table(self):
        return None

    @classmethod
    def get_listview_url(self):
        return reverse('archiv:spatialcoverage_browse')

    @classmethod
    def get_natural_primary_key(self):
        return "id"

    @classmethod
    def get_createview_url(self):
        return reverse('archiv:spatialcoverage_create')

    def get_absolute_url(self):
        return reverse('archiv:spatialcoverage_detail', kwargs={'pk': self.id})

    def get_delete_url(self):
        return reverse('archiv:spatialcoverage_delete', kwargs={'pk': self.id})

    def get_edit_url(self):
        return reverse('archiv:spatialcoverage_edit', kwargs={'pk': self.id})

    def get_next(self):
        next = next_in_order(self)
        if next:
            return next.get_absolute_url()
        return False

    def get_prev(self):
        prev = prev_in_order(self)
        if prev:
            return prev.get_absolute_url()
        return False

    def get_author_coords(self):
        cur_item = SpatialCoverage.objects.filter(id=self.id)
        items = cur_item.values_list(
            'stelle__text__ort__long',
            'stelle__text__ort__lat',
        )
        no_blanks = [x for x in items if x[0]]
        return no_blanks

    def geom_points(self):
        try:
            return list(self.fuzzy_geom.coords[0])
        except AttributeError:
            return []

    def new_coords(self):
        poly_cords = self.geom_points()
        new_coords = poly_cords[:1] + self.get_author_coords() + poly_cords[1:]
        return new_coords

    @cached_property
    def convex_hull(self):
        poly = Polygon(self.new_coords()).convex_hull
        return poly

    @cached_property
    def stellen_objects(self):
        return self.stelle.all()

    @cached_property
    def stellen(self):
        return [
            {
                "id": x.id,
                "start_date": x.start_date,
                "end_date": x.end_date,
                "display_label": x.display_label
            }
            for x in self.stellen_objects
        ]

    @cached_property
    def texts(self):
        t = [x.text for x in self.stellen_objects]
        texts = [
            {
                "title": x.title,
                "id": x.id,
                "places": [
                    getattr(p, 'place_as_dict') for p in x.ort.all()
                ],
                "authors": [
                    {
                        "id": p.id,
                        "name": p.name,
                        "place": getattr(p.ort, 'place_as_dict', None)
                    } for p in x.autor.all()
                ]
            } for x in t
        ]
        return texts
