import csv
import os
from datetime import datetime

from django.core.paginator import Paginator
from django.contrib.auth.models import Group, User
from django.conf import settings
from django.db.models import Q, Count
from django.http import HttpResponse, Http404
from django.utils.translation import gettext_lazy as _
from django_filters import rest_framework as filters
from guardian.core import ObjectPermissionChecker
from guardian.shortcuts import get_objects_for_user
from openpyxl import Workbook
from rest_framework import filters as drf_filters
from rest_framework import permissions, status, viewsets
from rest_framework.decorators import action
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from rest_framework.serializers import ValidationError
from rest_framework.views import APIView
from rest_framework_guardian.filters import ObjectPermissionsFilter
from rest_framework_tricks.filters import OrderingFilter

from api import models, serializers, utils
from api.permissions import (
    BaseModelPermissions,
    TableEntryPermissions,
    TableCustomActionPermissions,
    IsAuthenticatedOrGetToken,
)

class EntriesPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = "perPage"
    max_page_size = 1000

    def get_paginated_response(self, data):
        return Response(
            {
                "links": {
                    "next": self.get_next_link(),
                    "previous": self.get_previous_link(),
                },
                "count": self.page.paginator.count,
                "total_pages": self.page.paginator.num_pages,
                "results": data,
            }
        )

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.order_by('pk').all()
    serializer_class = serializers.users.UserListSerializer
    pagination_class = EntriesPagination

    def get_serializer_class(self):
        if self.action == "create":
            return serializers.users.UserCreateSerializer
        elif self.action == "retrieve":
            return serializers.users.UserDetailSerializer
        elif self.action in ["update","partial_update"]:
            return serializers.users.UserUpdateSerializer
        return serializers.users.UserListSerializer

    def get_queryset(self):
        user = self.request.user
        ordering = self.request.GET.get('__order', 'pk')

        if 'admin' in user.groups.values_list('name', flat=True):
            return User.objects.order_by(ordering).all()
        return User.objects.filter(pk=user.pk).order_by('pk')

    @action(
        detail=True,
        methods=["get"],
        name=_("Toggle user activation"),
        url_path="toggle-activation"
    )
    def toggle_activation(self, request, pk):
        request_user = request.user
        user = self.get_object()
        if 'admin' in request_user.groups.values_list('name', flat=True):
            user.is_active = not user.is_active
            user.save()
        response = serializers.users.UserDetailSerializer(
            user, context={'request': request})
        return Response(response.data)

class UserView(APIView):
    """
    View to list all users in the system.

    * Requires token authentication.
    * Only admin users are able to access this view.

    TODO: This description does not seem accurate
    """

    def get(self, request, format=None):
        """
        Get the current user
        """
        user = request.user
        userprofile, _ = models.Userprofile.objects.get_or_create(user=user)
        profile_cards = [card.card for card in userprofile.dashboard_cards.all()]
        admin_group = Group.objects.get(name='admin')

        cards_serializer = serializers.cards.ListSerializer(
            profile_cards, many=True, context={'request': request})
        charts_serializer = serializers.charts.ListSerializer(
            userprofile.dashboard_charts.all(), many=True, context={'request': request})
        filters_serializer = serializers.filters.FilterListSerializer(
            userprofile.dashboard_filters.all(), many=True, context={'request': request})

        dashboard = {
            "cards": cards_serializer.data,
            "charts": charts_serializer.data,
            "filters": filters_serializer.data
        }

        response = {
            "username": user.username,
            "id": user.id,
            "language": userprofile.language,
            "dashboard": dashboard,
            "is_admin": admin_group in user.groups.all(),
            "avatar": request.build_absolute_uri(userprofile.avatar.url) if userprofile.avatar else None
        }
        return Response(response)

class DatabaseViewSet(viewsets.ModelViewSet):
    queryset = models.Database.objects.order_by('id').all()
    serializer_class = serializers.databases.DatabaseSerializer

class CanView(permissions.BasePermission):
    """
    Object-level permission to only allow owners of an object to access it.
    Assumes the model instance has an `user` attribute.
    """

    def has_object_permission(self, request, view, obj):
        # Instance must have an attribute named `user`.
        return obj.owner == request.user

class MyFilterBackend(filters.DjangoFilterBackend):
    def get_filterset_kwargs(self, request, queryset, view):
        kwargs = super().get_filterset_kwargs(request, queryset, view)

        # merge filterset kwargs provided by view class
        if hasattr(view, "get_filterset_kwargs"):
            kwargs.update(view.get_filterset_kwargs())

        return kwargs

class TableViewSet(viewsets.ModelViewSet):
    queryset = models.Table.objects.all().prefetch_related("fields").select_related("database").order_by("id")
    pagination_class = EntriesPagination
    # permission_classes = (BaseModelPermissions, api_permissions.IsAuthenticatedOrGetToken )
    permission_classes = [BaseModelPermissions]
    filter_backends = [ObjectPermissionsFilter]
    # filterset_fields = ["active"]

    def get_serializer_class(self):
        if self.action == "list":
            return serializers.databases.DatabaseTableListSerializer
        elif self.action in ["create", "update", "partial_update"]:
            return serializers.tables.TableCreateSerializer
        return serializers.tables.TableSerializer

    def get_permissions(self):
        base_permissions = super(self.__class__, self).get_permissions()
        if self.action in ["csv_export", "xlsx_export"]:
            base_permissions = (TableCustomActionPermissions(),)
        elif self.action in ["search"]:
            base_permissions = ()
        return base_permissions

    def create(self, request):
        fields = request.data.get("fields")
        csv_import_pk = request.data.get("import_id")
        data = request.data
        serializer = serializers.tables.TableCreateSerializer(
            data=data,
            context={"request": request},
        )
        serializer.is_valid(raise_exception=True)

        self.perform_create(serializer)

        if not csv_import_pk:
            headers = self.get_success_headers(serializer.data)
            return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

        table = models.Table.objects.get(pk=serializer.data["id"])
        csv_import = models.CsvImport.objects.get(pk=csv_import_pk)

        for field in fields:
            table_column = models.TableColumn.objects.get(
                table=table,
                name=utils.snake_case(field["display_name"]),
            )
            csv_field_map = models.CsvFieldMap.objects.create(
                table=table,
                original_name=field["original_name"] or field["display_name"],
                display_name=field["display_name"],
                field_type=field["field_type"],
                field_format=field["field_format"],
                table_column=table_column
            )
            table_column.field_type = field["field_type"]
            table_column.required = field.get('required', False)
            table_column.unique = field.get('unique', False)
            table_column.save()
            csv_field_map.required = field.get('required', False)
            csv_field_map.unique = field.get('unique', False)
            csv_field_map.save()

        try:
            file_content = csv_import.file.read().decode("utf-8")
        except:
            csv_import.file.seek(0)
            file_content = csv_import.file.read().decode("windows-1252")
        decoded_file = file_content.splitlines()
        if not csv_import.delimiter:
            csv_import.file.seek(0)
            dialect = csv.Sniffer().sniff(file_content[:2000])
            reader = csv.DictReader(decoded_file, skipinitialspace=True, delimiter=dialect.delimiter)
        else:
            reader = csv.DictReader(decoded_file, skipinitialspace=True, delimiter=csv_import.delimiter)

        errors, errors_count, import_count_created, import_count_updated, import_count_skipped = utils.import_csv(reader, table)
        csv_import.errors = errors
        csv_import.errors_count = errors_count
        csv_import.import_count_created = import_count_created
        csv_import.import_count_updated = import_count_updated
        csv_import.import_count_skipped = import_count_skipped
        csv_import.table = table
        csv_import.save()
        response = {
            "errors_count": errors_count,
            "import_count_created": import_count_created,
            "import_count_updated": import_count_updated,
            "import_count_skipped": import_count_skipped,
            "errors": errors,
            "id": table.id,
        }
        return Response(response)

    @action(
        detail=False,
        methods=["post"],
        name=_("Create contacts table"),
        url_path="contacts",
    )
    def create_contacts_table(self, request):
        # For now we only allow one "contacts" table in the platform
        try:
            existing_table = models.Table.objects.filter(table_type=models.Table.TYPE_CONTACTS).all()[0]
        except IndexError:
            pass
        else:
            return Response({
                "detail": _("A contacts table already exists: {}").format(existing_table.name)
            }, status=status.HTTP_409_CONFLICT)

        if not settings.PLUGIN_MAILCHIMP_ENABLED:
            return Response({
                "detail": _("The Mailchimp plugin is not enabled for this installation.")
            }, status=status.HTTP_405_METHOD_NOT_ALLOWED)

        from plugin_mailchimp import utils as mailchimp_utils
        name = request.data.get("name", "").strip()
        contact_table_id = mailchimp_utils.create_mailchimp_tables(name)
        response_data = {
            "id": contact_table_id
        }
        return Response(response_data)

    @action(
        detail=False,
        methods=["get"],
        name=_("Find tables containing text"),
        url_path="search",
    )
    def search(self, request):
        needle = request.GET.get("query").strip()
        tables = models.Table.objects.all()
        table_ids = []

        # Only search in tables for which the user has view permissions
        checker = ObjectPermissionChecker(request.user)
        for table in tables:
            if 'view_table' in checker.get_perms(table):
                table_ids.append(table.id)

        queryset = models.Entry.objects.filter(
            table__id__in=table_ids, data__icontains=needle
        ).all().values("table").annotate(count=Count("table")).order_by("table")

        serializer = serializers.tables.TableSearchCountSerializer(queryset, many=True)
        return Response(serializer.data)

    @action(
        detail=True,
        methods=["post"],
        name=_("CSV manual import view"),
        url_path="csv-manual-import",
    )
    def csv_manual_import(self, request, pk):
        fields = request.data.get("fields")
        csv_import_pk = request.data.get("import_id")

        table = self.get_object()
        csv_import = models.CsvImport.objects.get(pk=csv_import_pk)

        for field in fields:
            csv_field_map= models.CsvFieldMap.objects.get(
                csv_import=csv_import,
                original_name=field["original_name"]
            )
            csv_field_map.field_format=field["field_format"]
            csv_field_map.unique=field["unique"]

            csv_field_map.required=field["required"]

            if field["table_field"]:
                csv_field_map.table_column_id=field["table_field"]

            csv_field_map.save()

        try:
            file_content = csv_import.file.read().decode("utf-8")
        except:
            csv_import.file.seek(0)
            file_content = csv_import.file.read().decode("windows-1252")
        decoded_file = file_content.splitlines()
        if not csv_import.delimiter:
            csv_import.file.seek(0)
            dialect = csv.Sniffer().sniff(file_content[:2000])
            reader = csv.DictReader(decoded_file, skipinitialspace=True, delimiter=dialect.delimiter)
        else:
            reader = csv.DictReader(decoded_file, skipinitialspace=True, delimiter=csv_import.delimiter)

        errors, errors_count, import_count_created, import_count_updated, import_count_skipped = utils.import_csv(reader, table, csv_import)

        csv_import.errors = errors
        csv_import.errors_count = errors_count
        csv_import.import_count_created = import_count_created
        csv_import.import_count_updated = import_count_updated
        csv_import.import_count_skipped = import_count_skipped
        csv_import.table = table
        csv_import.save()
        response = {
            "errors_count": errors_count,
            "import_count_created": import_count_created,
            "import_count_updated": import_count_updated,
            "import_count_skipped": import_count_skipped,
            "errors": errors,
            "id": table.id,
            "import_id": csv_import.pk,
        }
        return Response(response)

    @action(
        detail=True,
        methods=["get"],
        name=_("CSV Export"),
        url_path="csv-export",
    )
    def csv_export(self, request, pk):
        table = models.Table.objects.get(pk=pk)
        table_fields = {x.name: x for x in table.fields.all()}

        filter_dict = utils.request_get_to_filter(request.GET, table_fields, Q(), False)

        file_name = "{}__{}.csv".format(table.name, datetime.now().strftime("%d.%m.%Y"))
        with open("/tmp/{}".format(file_name), "w", encoding="utf-8-sig") as csv_export_file:
            writer = csv.DictWriter(
                csv_export_file,
                delimiter=",",
                quoting=csv.QUOTE_MINIMAL,
                fieldnames=table.fields.values_list("name", flat=True),
            )
            writer.writeheader()
            for row in table.entries.filter(filter_dict):
                writer.writerow(row.data)

        with open("/tmp/{}".format(file_name), "rb") as csv_export_file:
            response = HttpResponse(csv_export_file.read(), content_type="application/vnd.ms-excel")
            response["Content-Disposition"] = 'attachment; filename="{}"'.format(file_name)

        try:
            os.remove("/tmp/{}".format(file_name))
        except OSError:
            pass

        return response

    @action(
        detail=True,
        methods=["get"],
        name=_("XLSX Export"),
        url_path="xlsx-export",
    )
    def xlsx_export(self, request, pk):
        table = models.Table.objects.get(pk=pk)
        table_fields = {x.name: x for x in table.fields.all()}

        filter_dict = utils.request_get_to_filter(request.GET, table_fields, Q(), False)

        file_name = "{}__{}.xlsx".format(table.name, datetime.now().strftime("%d.%m.%Y"))
        file_path = "/tmp/{}".format(file_name)
        wb = Workbook()
        sheet = wb.active
        sheet.title = table.name

        field_names = table.fields.values_list("name", flat=True)
        for i, field_name in enumerate(field_names, start=1):
            sheet.cell(row=1, column=i).value = field_name

        row_num = 2
        for entry in table.entries.filter(filter_dict):
            for i, field_name in enumerate(field_names, start=1):
                if entry.data.get(field_name) is not None:
                    sheet.cell(row=row_num, column=i).value = entry.data.get(field_name)
            row_num += 1

        wb.save(filename=file_path)

        with open(file_path, "rb") as export_file:
            response = HttpResponse(export_file.read(), content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
            response["Content-Disposition"] = 'attachment; filename="{}"'.format(file_name)

        try:
            os.remove(file_path)
        except OSError:
            pass

        return response

    @action(
        detail=False,
        methods=["post"],
        name=_("Create from FilterView"),
        url_path="from-filter",
    )
    def create_from_filter(self, request):

        table_name = request.data.get('table_name')
        filter_id = request.data.get('filter_id')

        serializer = serializers.tables.TableCreateSerializer(
            data={'database': 1, 'name': table_name},
            context={"request": request},
        )
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)

        table = models.Table.objects.get(pk=serializer.data["id"])
        # table = models.Table.objects.get(name=table_name)

        filter = models.Filter.objects.get(pk=filter_id)
        filter_tables = [filter.primary_table] + [f for f in filter.join_tables.all()]
        for join_table in filter_tables:
            table_slug = join_table.table.slug
            for field in join_table.fields.all():
                table_column, _ = models.TableColumn.objects.get_or_create(
                    table=table,
                    name='{}_{}'.format(table_slug, field.name),
                    display_name=field.display_name,
                    field_type=field.field_type,
                    choices=field.choices,
                )

        primary_table = filter.primary_table
        primary_table_slug = primary_table.table.slug
        primary_table_join_field = primary_table.join_field.name

        secondary_table = filter.join_tables.all()[0]
        secondary_table_slug = secondary_table.table.slug
        secondary_table_join_field = secondary_table.join_field.name

        # Get all fields and display fields
        all_fields = []
        field_types = {}
        for field in primary_table.fields.all().order_by("id"):
            field_key = "{}__{}".format(primary_table.table.slug, field.name)
            all_fields.append(field_key)
            field_types[field_key] = field.field_type
        for field in secondary_table.fields.all().order_by("id"):
            field_key = "{}__{}".format(secondary_table.table.slug, field.name)
            all_fields.append(field_key)
            field_types[field_key] = field.field_type

        fields = all_fields

        primary_table_fields = []
        secondary_table_fields = []

        for field in fields:
            if field.startswith(primary_table_slug):
                primary_table_fields.append(field.replace(primary_table_slug + "__", "data__"))
            else:
                secondary_table_fields.append(field.replace(secondary_table_slug + "__", "data__"))

        secondary_table_fields.append("data__{}".format(secondary_table_join_field))

        # Create filters dict
        filter_dict = {
            primary_table_slug: {},
            secondary_table_slug: {},
        }

        # for key in filter.filters:
        #     table_field = "__".join(key.split("__")[:2])
        #     if key and table_field in all_fields:
        #         table = key.split("__")[0]
        #         field = key.replace(table + "__", "")

        #         filter_dict.setdefault(table, {})
        #         value = filter.filters.get(key).split(",")

        #         if len(value) == 1:
        #             value = value[0]
        #         else:
        #             field = field + "__in"

        #         if field_types[table_field] in [
        #             "float",
        #             "int",
        #         ]:
        #             filter_dict[table]["data__{}".format(field)] = float(value)
        #         else:
        #             filter_dict[table]["data__{}".format(field)] = value

        join_values = (
            models.Entry.objects.filter(table=primary_table.table)
            .filter(**filter_dict[primary_table_slug])
            .values("data__{}".format(primary_table_join_field))
        )

        filter_dict[secondary_table_slug]["data__{}__in".format(secondary_table_join_field)] = join_values

        result_values = (
            models.Entry.objects.filter(table__slug=secondary_table_slug)
            .filter(**filter_dict[secondary_table_slug])
            .values(*secondary_table_fields)
            .order_by("data__{}".format(secondary_table_join_field))
        )

        queryset = result_values

        if not fields:
            fields = [x.replace("data__", "{}__".format(primary_table_slug)) for x in primary_table_fields]
            fields += [x.replace("data__", "{}__".format(secondary_table_slug)) for x in secondary_table_fields]
        queryset_count = queryset.count()
        paginator = Paginator(queryset, 1000)  # Show 100 objects per page, you can choose any other value

        for i in paginator.page_range:  # A 1-based range iterator of page numbers, e.g. yielding [1, 2, 3, 4].
            data = paginator.get_page(i)
            page = data.object_list

            page_join_values = [x["data__{}".format(secondary_table_join_field)] for x in page]

            filter_dict[primary_table_slug]["data__{}__in".format(primary_table_join_field)] = page_join_values
            primary_table_values = {
                x.data[primary_table_join_field]: {"data__" + key: value for key, value in x.data.items()}
                for x in models.Entry.objects.filter(table=primary_table.table)
                .filter(**filter_dict[primary_table_slug])
                .exclude(data=None)
            }
            entries = []
            for entry in page:
                final_entry = {}
                final_entry_primary_table_values = {}

                entry_primary_table_values = primary_table_values[
                    entry["data__{}".format(secondary_table_join_field)]
                ]

                for key in entry:
                    final_entry[key.replace("data__", "{}_".format(secondary_table_slug))] = entry[key]
                for key in entry_primary_table_values:
                    final_entry_primary_table_values[
                        key.replace("data__", "{}_".format(primary_table_slug))
                    ] = entry_primary_table_values[key]

                final_entry.update(final_entry_primary_table_values)
                entry = models.Entry(table=table, data=final_entry)
                entries.append(entry)
            models.Entry.objects.bulk_create(entries)
        response = {
            'id': table.id
        }
        return Response(response)

class FilterViewSet(viewsets.ModelViewSet):
    queryset = models.Filter.objects.order_by('id').all()
    pagination_class = EntriesPagination
    filter_backends = (OrderingFilter,)

    ordering_fields = {
        'name': 'name',
        'creation_date': 'creation_date',
        'table': 'table__name',
        'owner.username': 'owner__username'
    }

    def get_queryset(self):
        queryset = self.queryset
        user = self.request.user
        user_view_tables = []

        for table in get_objects_for_user(user, 'api.view_table'):
            if user.has_perm('view_table', table):
                user_view_tables.append(table)
        q = Q(
                primary_table__table__in=user_view_tables,
                join_tables__table__in=user_view_tables) | \
            Q(
                primary_table__table__in=user_view_tables,
                join_tables=None
                )
        return queryset.filter(q)

    def get_serializer_class(self):
        if self.action == "list":
            return serializers.filters.FilterListSerializer
        elif self.action == "retrieve":
            return serializers.filters.FilterDetailSerializer
        elif self.action in ["create", "update", "partial_update"]:
            return serializers.filters.FilterCreateSerializer

        return serializers.filters.FilterListSerializer

    def get_permissions(self):
        base_permissions = super(self.__class__, self).get_permissions()
        if self.action == "csv_export":
            base_permissions = (IsAuthenticatedOrGetToken(),)
        return base_permissions

    @action(
        detail=False,
        methods=["get"],
        url_name="all-filters",
        url_path="all",
    )
    def all(self, request):
        response = serializers.filters.FilterListSerializer(self.queryset, many=True, context={'request': request})
        return Response(response.data)

    @action(
        detail=True,
        methods=["get"],
        url_name="add-to-dashboard",
        url_path="add-to-dashboard",
    )
    def add_to_dashboard(self, request, pk):
        filter = self.get_object()
        userprofile = request.user.userprofile

        if filter in userprofile.dashboard_filters.all():
            userprofile.dashboard_filters.remove(filter)
        else:
            userprofile.dashboard_filters.add(filter)
        userprofile.save()
        return Response({'filter_in_dashboard': filter in userprofile.dashboard_filters.all()})

    @action(methods=["get"], detail=True, url_path="entries", url_name="entries")
    def entries(self, request, pk):
        obj = models.Filter.objects.filter(pk=pk).prefetch_related("primary_table", "join_tables")[0]
        str_fields = request.GET.get("__fields", "") if request else None
        str_order = request.GET.get("__order", "") if request else None

        primary_table = obj.primary_table
        primary_table_slug = primary_table.table.slug

        is_two_tables_filter = False

        if obj.join_tables.all():
            primary_table_join_field = primary_table.join_field.name
            secondary_table = obj.join_tables.all()[0]
            secondary_table_slug = secondary_table.table.slug
            secondary_table_join_field = secondary_table.join_field.name
            is_two_tables_filter = True

        # Get all fields and display fields
        all_fields = []
        field_types = {}
        for field in primary_table.fields.all().order_by("id"):
            if obj.default_fields.all():
                if field in obj.default_fields.all():
                    field_key = "{}__{}".format(primary_table.table.slug, field.name)
                    all_fields.append(field_key)
                    field_types[field_key] = field.field_type
            else:
                field_key = "{}__{}".format(primary_table.table.slug, field.name)
                all_fields.append(field_key)
                field_types[field_key] = field.field_type

        if is_two_tables_filter:
            for field in secondary_table.fields.all().order_by("id"):
                if obj.default_fields.all():
                    if field in obj.default_fields.all():
                        field_key = "{}__{}".format(secondary_table.table.slug, field.name)
                        all_fields.append(field_key)
                        field_types[field_key] = field.field_type
                else:
                    field_key = "{}__{}".format(secondary_table.table.slug, field.name)
                    all_fields.append(field_key)
                    field_types[field_key] = field.field_type

        fields = all_fields
        if str_fields:
            if str_fields == "ALL":
                fields = all_fields
            else:
                fields = str_fields.split(",") if str_fields else None

        primary_table_fields = []
        secondary_table_fields = []

        for field in fields:
            if field.startswith(primary_table_slug):
                primary_table_fields.append(field.replace(primary_table_slug + "__", "data__"))
            else:
                secondary_table_fields.append(field.replace(secondary_table_slug + "__", "data__"))

        if is_two_tables_filter:
            secondary_table_fields.append("data__{}".format(secondary_table_join_field))

        # order_table = str_order.replace("-", "").split("__")[0]
        order_table = str_order.split("__")[0]
        str_order = str_order.replace(order_table + "__", "")

        if str_order:
            if order_table.startswith("-"):
                order_table = order_table[1:]
                order_by = "-data__{}".format(str_order)
            else:
                order_by = "data__{}".format(str_order)
        else:
            order_by = "id"

        table_order_by = "id"

        # Create filters dict
        filter_dict = {
            primary_table_slug: Q(),
        }
        if is_two_tables_filter:
            filter_dict[secondary_table_slug] = Q()

        filter_dict = utils.request_get_to_filter(request.GET, field_types, filter_dict, True)

        # If filter has only primary_table
        if not is_two_tables_filter:
            if order_table == primary_table_slug:
                table_order_by = order_by
            result_values = (
                models.Entry.objects.filter(table__slug=primary_table_slug)
                .filter(filter_dict[primary_table_slug])
                .values(*primary_table_fields)
                .order_by(table_order_by)
            )
            queryset = result_values
            if not fields:
                fields = [x.replace("data__", "{}__".format(primary_table_slug)) for x in primary_table_fields]
            page = self.paginate_queryset(queryset)
            if page is not None:
                final_page = []
                for entry in page:
                    final_entry = {}
                    for key in entry:
                        final_entry[key.replace("data__", "{}__".format(primary_table_slug))] = entry[key]
                    final_page.append(final_entry)

                serializer = serializers.filters.FilterEntrySerializer(final_page, many=True, context={"fields": fields})
                return self.get_paginated_response(serializer.data)
        # If filter has secondary table
        else:
            if order_table == secondary_table_slug:
                table_order_by = order_by

            join_values = (
                models.Entry.objects.filter(table=primary_table.table)
                .filter(filter_dict[primary_table_slug])
                .values("data__{}".format(primary_table.join_field.name))
                .order_by(table_order_by)
            )

            # filter_dict[secondary_table_slug]["data__{}__in".format(secondary_table_join_field)] = join_values
            filter_dict[secondary_table_slug] = filter_dict[secondary_table_slug] & Q(
                **{"data__{}__in".format(secondary_table_join_field) :join_values})
            table_order_by = "id"
            if order_table == secondary_table_slug:
                table_order_by = order_by

            result_values = (
                models.Entry.objects.filter(table__slug=secondary_table_slug)
                .filter(filter_dict[secondary_table_slug])
                .values(*secondary_table_fields)
                .order_by(table_order_by)
            )

            queryset = result_values

            if not fields:
                fields = [x.replace("data__", "{}__".format(primary_table_slug)) for x in primary_table_fields]
                fields += [x.replace("data__", "{}__".format(secondary_table_slug)) for x in secondary_table_fields]

            page = self.paginate_queryset(queryset)

            if page is not None:
                final_page = []
                page_join_values = [x["data__{}".format(secondary_table_join_field)] for x in page]

                filter_dict[primary_table_slug] = filter_dict[primary_table_slug] & Q(
                    **{"data__{}__in".format(primary_table_join_field): page_join_values})
                # filter_dict[primary_table_slug]["data__{}__in".format(primary_table_join_field)] = page_join_values
                primary_table_values = {
                    x.data[primary_table_join_field]: {"data__" + key: value for key, value in x.data.items()}
                    for x in models.Entry.objects.filter(table=primary_table.table)
                    .filter(filter_dict[primary_table_slug])
                    .exclude(data=None)
                }

                for entry in page:
                    final_entry = {}
                    final_entry_primary_table_values = {}

                    entry_primary_table_values = primary_table_values[entry["data__{}".format(secondary_table_join_field)]]
                    for key in entry:
                        final_entry[key.replace("data__", "{}__".format(secondary_table_slug))] = entry.get(key, None)
                    final_primary_table_fields = [x.replace('{}__'.format(primary_table_slug), '') for x in fields if not x.startswith('{}_'.format(secondary_table_slug))]
                    # for key in entry_primary_table_values:

                    for key in final_primary_table_fields:
                        final_entry_primary_table_values[
                            "{}__{}".format(primary_table_slug, key)
                        ] = entry_primary_table_values.get('data__' + key, None)

                    final_entry.update(final_entry_primary_table_values)
                    final_page.append(final_entry)
                serializer = serializers.filters.FilterEntrySerializer(final_page, many=True, context={"fields": fields})
                return self.get_paginated_response(serializer.data)

        return Response(serializer.data)

    @action(
        methods=["get"],
        detail=True,
        url_path="csv-export",
        url_name="csv-export")
    def csv_export(self, request, pk):
        obj = models.Filter.objects.filter(pk=pk).prefetch_related("primary_table", "join_tables")[0]
        str_fields = request.GET.get("__fields", "") if request else None
        str_order = request.GET.get("__order", "") if request else None

        primary_table = obj.primary_table
        primary_table_slug = primary_table.table.slug

        is_two_tables_filter = False

        if obj.join_tables.all():
            primary_table_join_field = primary_table.join_field.name
            secondary_table = obj.join_tables.all()[0]
            secondary_table_slug = secondary_table.table.slug
            secondary_table_join_field = secondary_table.join_field.name
            is_two_tables_filter = True

        # Get all fields and display fields
        all_fields = []
        field_types = {}
        for field in primary_table.fields.all().order_by("id"):
            if obj.default_fields.all():
                if field in obj.default_fields.all():
                    field_key = "{}__{}".format(primary_table.table.slug, field.name)
                    all_fields.append(field_key)
                    field_types[field_key] = field.field_type
            else:
                field_key = "{}__{}".format(primary_table.table.slug, field.name)
                all_fields.append(field_key)
                field_types[field_key] = field.field_type

        if is_two_tables_filter:
            for field in secondary_table.fields.all().order_by("id"):
                if obj.default_fields.all():
                    if field in obj.default_fields.all():
                        field_key = "{}__{}".format(secondary_table.table.slug, field.name)
                        all_fields.append(field_key)
                        field_types[field_key] = field.field_type
                else:
                    field_key = "{}__{}".format(secondary_table.table.slug, field.name)
                    all_fields.append(field_key)
                    field_types[field_key] = field.field_type

        fields = all_fields
        if str_fields:
            if str_fields == "ALL":
                fields = all_fields
            else:
                fields = str_fields.split(",") if str_fields else None

        primary_table_fields = []
        secondary_table_fields = []

        for field in fields:
            if field.startswith(primary_table_slug):
                primary_table_fields.append(field.replace(primary_table_slug + "__", "data__"))
            else:
                secondary_table_fields.append(field.replace(secondary_table_slug + "__", "data__"))

        if is_two_tables_filter:
            secondary_table_fields.append("data__{}".format(secondary_table_join_field))

        # order_table = str_order.replace("-", "").split("__")[0]
        order_table = str_order.split("__")[0]
        str_order = str_order.replace(order_table + "__", "")

        if str_order:
            if str_order.startswith("-"):
                order_by = "-data__{}".format(str_order[1:])
            else:
                order_by = "data__{}".format(str_order)
        else:
            order_by = "id"

        table_order_by = "id"

        if order_table == primary_table_slug:
            table_order_by = order_by

        # Create filters dict
        filter_dict = {
            primary_table_slug: Q(),
        }
        if is_two_tables_filter:
            filter_dict[secondary_table_slug] = Q()

        filter_dict = utils.request_get_to_filter(request.GET, field_types, filter_dict, True)

        # If filter has only primary_table
        if not is_two_tables_filter:
            result_values = (
                models.Entry.objects.filter(table__slug=primary_table_slug)
                .filter(filter_dict[primary_table_slug])
                .values(*primary_table_fields)
                .order_by(table_order_by)
            )
            queryset = result_values

            if not fields:
                fields = [x.replace("data__", "{}__".format(primary_table_slug)) for x in primary_table_fields]

            paginator = Paginator(queryset, 1000)  # Show 100 objects per page, you can choose any other value

            file_name = "{}__{}.csv".format(obj.slug, datetime.now().strftime("%d_%m_%Y__%H_%M"))
            with open("/tmp/{}".format(file_name), "w", encoding="utf-8-sig") as csv_export_file:
                writer = csv.DictWriter(
                    csv_export_file,
                    delimiter=",",
                    quoting=csv.QUOTE_MINIMAL,
                    fieldnames=fields,
                )
                writer.writeheader()
                for i in paginator.page_range:  # A 1-based range iterator of page numbers, e.g. yielding [1, 2, 3, 4].
                    data = paginator.get_page(i)
                    page = data.object_list

                    for entry in page:
                        final_entry = {}
                        final_entry_primary_table_values = {}

                        for key in entry:
                            final_entry[key.replace("data__", "{}__".format(primary_table_slug))] = entry[key]

                        writer.writerow({k: v for k, v in final_entry.items() if k in fields})

        else:
            join_values = (
                models.Entry.objects.filter(table=primary_table.table)
                .filter(filter_dict[primary_table_slug])
                .values("data__{}".format(primary_table.join_field.name))
                .order_by(table_order_by)
            )

            filter_dict[secondary_table_slug] = filter_dict[secondary_table_slug] & Q(
                **{"data__{}__in".format(secondary_table_join_field): join_values})
            # filter_dict[secondary_table_slug]["data__{}__in".format(secondary_table_join_field)] = join_values

            table_order_by = "id"
            if order_table == secondary_table_slug:
                table_order_by = order_by

            result_values = (
                models.Entry.objects.filter(table__slug=secondary_table_slug)
                .filter(filter_dict[secondary_table_slug])
                .values(*secondary_table_fields)
                .order_by(table_order_by)
            )

            queryset = result_values
            if not fields:
                fields = [x.replace("data__", "{}__".format(primary_table_slug)) for x in primary_table_fields]
                fields += [x.replace("data__", "{}__".format(secondary_table_slug)) for x in secondary_table_fields]
            queryset_count = queryset.count()
            paginator = Paginator(queryset, 1000)  # Show 100 objects per page, you can choose any other value

            file_name = "{}__{}.csv".format(obj.slug, datetime.now().strftime("%d_%m_%Y__%H_%M"))
            with open("/tmp/{}".format(file_name), "w", encoding="utf-8-sig") as csv_export_file:
                writer = csv.DictWriter(
                    csv_export_file,
                    delimiter=",",
                    quoting=csv.QUOTE_MINIMAL,
                    fieldnames=fields,
                )
                writer.writeheader()
                for i in paginator.page_range:  # A 1-based range iterator of page numbers, e.g. yielding [1, 2, 3, 4].
                    data = paginator.get_page(i)
                    page = data.object_list

                    page_join_values = [x["data__{}".format(secondary_table_join_field)] for x in page]
                    filter_dict[primary_table_slug] = filter_dict[primary_table_slug] & Q(
                        **{"data__{}__in".format(primary_table_join_field): page_join_values})
                    # filter_dict[primary_table_slug]["data__{}__in".format(primary_table_join_field)] = page_join_values
                    primary_table_values = {
                        x.data[primary_table_join_field]: {"data__" + key: value for key, value in x.data.items()}
                        for x in models.Entry.objects.filter(table=primary_table.table)
                        .filter(filter_dict[primary_table_slug])
                        .exclude(data=None)
                    }

                    for entry in page:
                        final_entry = {}
                        final_entry_primary_table_values = {}

                        entry_primary_table_values = primary_table_values[
                            entry["data__{}".format(secondary_table_join_field)]
                        ]

                        for key in entry:
                            final_entry[key.replace("data__", "{}__".format(secondary_table_slug))] = entry[key]
                        for key in entry_primary_table_values:
                            final_entry_primary_table_values[
                                key.replace("data__", "{}__".format(primary_table_slug))
                            ] = entry_primary_table_values[key]

                        final_entry.update(final_entry_primary_table_values)
                        writer.writerow({k: v for k, v in final_entry.items() if k in fields})

        with open("/tmp/{}".format(file_name), "rb") as csv_export_file:
            # response = HttpResponse(FileWrapper(csv_export_file), content_type='application/vnd.ms-excel')
            response = HttpResponse(csv_export_file.read(), content_type="application/vnd.ms-excel")
            response["Content-Disposition"] = 'attachment; filename="{}"'.format(file_name)
        try:
            os.remove("/tmp/{}".format(file_name))
        except OSError:
            pass

        return response

class EntryViewSet(viewsets.ModelViewSet):
    pagination_class = EntriesPagination
    serializer_class = serializers.entries.EntrySerializer
    permission_classes = (TableEntryPermissions, )
    filter_backends = (drf_filters.SearchFilter, )
    search_fields = ("data", )

    def get_queryset(self):
        return models.Entry.objects.order_by('id').filter(table=self.kwargs["table_pk"])

    def list(self, request, table_pk):
        table = models.Table.objects.get(pk=table_pk)

        str_fields = request.GET.get("__fields", "") if request else None
        str_order = request.GET.get("__order", "") if request else None
        str_search = request.GET.get("search", "").strip() if request else None
        table_fields = {x.name: x.field_type for x in table.fields.all().order_by("id")}
        default_fields = {x.name: x for x in table.default_fields.all().order_by("id")}

        if str_fields == "ALL":
            fields = [x for x in table_fields.keys()]
        else:
            fields = str_fields.split(",") if str_fields else None
            if not fields:
                if default_fields:
                    fields = [x for x in default_fields.keys()]
                else:
                    fields = [x for x in table_fields.keys()]

        filter_dict = utils.request_get_to_filter(request.GET, table_fields, Q(), False)

        if str_order and str_order.replace("-", "") in fields:
            if str_order.startswith("-"):
                queryset = table.entries.filter(filter_dict).order_by("-data__{}".format(str_order[1:]))
            else:
                queryset = table.entries.filter(filter_dict).order_by("data__{}".format(str_order))
        else:
            queryset = table.entries.filter(filter_dict).order_by("id")
            # queryset = table.entries.annotate(date_field=Cast(KeyTextTransform('data_iesire', "data"), DateField())).filter(date_field__exact='2020-07-21').order_by("id")

        # simple text search over the entire data field
        if str_search:
            queryset = queryset.filter(data__icontains=str_search)

        page = self.paginate_queryset(queryset)

        if page is not None:
            serializer = serializers.entries.EntrySerializer(
                page,
                many=True,
                context={"fields": fields, "table": table, "request": request},
            )
            return self.get_paginated_response(serializer.data)
        serializer = serializers.entries.EntrySerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, table_pk, pk):
        table = models.Table.objects.get(pk=table_pk)
        object = models.Entry.objects.get(pk=pk)

        fields = table.fields.values_list("name", flat=True).order_by("name")
        serializer = serializers.entries.EntrySerializer(
            object,
            context={"fields": fields, "table": table, "request": request},
        )

        return Response(serializer.data)

    def update(self, request, table_pk, pk, *args, **kwargs):
        table = models.Table.objects.get(pk=table_pk)
        object = self.get_object()

        fields = table.fields.values_list("name", flat=True).order_by("name")

        serializer = serializers.entries.EntrySerializer(
            object,
            data=request.data,
            context={"fields": fields, "table": table, "request": request},
        )
        serializer.is_valid(raise_exception=True)
        try:
            self.perform_update(serializer)
        except ValidationError as e:
            return Response({"detail": e.detail[0]}, status=status.HTTP_409_CONFLICT)
        return Response(serializer.data)

    def create(self, request, table_pk):
        table = models.Table.objects.get(pk=table_pk)
        data = request.data
        fields = table.fields.values_list("name", flat=True).order_by("name")

        serializer = serializers.entries.EntrySerializer(
            data=data,
            context={"fields": fields, "table": table, "request": request},
        )
        serializer.is_valid(raise_exception=False)
        try:
            self.perform_create(serializer)
        except ValidationError as e:
            return Response({"detail": e.detail[0]}, status=status.HTTP_409_CONFLICT)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    @action(
        detail=False,
        methods=["post"],
        url_name="bulk-delete",
        url_path="bulk-delete",
    )
    def bulk_delete(self, request, table_pk):
        """ Bulk delete up to 100 entries from the same table """

        entry_ids = request.data.get('entries', [])
        if not isinstance(entry_ids, list) or len(entry_ids) > 100:
            return Response(status=status.HTTP_400_BAD_REQUEST)

        if entry_ids:
            models.Entry.objects.filter(table__id=table_pk, id__in=entry_ids).delete()

        return Response(status=status.HTTP_204_NO_CONTENT)

class CsvImportViewSet(viewsets.ModelViewSet):
    queryset = models.CsvImport.objects.order_by('id').all()
    # permission_classes = (BaseModelPermissions,)

    def get_serializer_class(self):
        if self.action == "list":
            return serializers.csvs.CsvImportListSerializer
        return serializers.csvs.CsvImportSerializer

    def get_permissions(self):
        base_permissions = super(self.__class__, self).get_permissions()
        if self.action == "export_errors":
            base_permissions = (IsAuthenticatedOrGetToken(),)
        return base_permissions

    @action(
        detail=True,
        methods=["get"],
        name=_("CSV Errors Export"),
        url_path="export-errors",
    )
    def export_errors(self, request, pk):
        csv_import = self.get_object()

        file_name = "errors__" + csv_import.file.name.split("/")[-1]
        with open("/tmp/{}".format(file_name), "w", encoding="utf-8-sig") as csv_export_file:
            writer = csv.DictWriter(
                csv_export_file,
                delimiter=",",
                quoting=csv.QUOTE_MINIMAL,
                fieldnames=csv_import.errors[0]["row"].keys(),
            )
            writer.writeheader()
            for row in csv_import.errors:
                writer.writerow(row["row"])

        with open("/tmp/{}".format(file_name), "rb") as csv_export_file:
            # response = HttpResponse(FileWrapper(csv_export_file), content_type='application/vnd.ms-excel')
            response = HttpResponse(csv_export_file.read(), content_type="application/vnd.ms-excel")
            response["Content-Disposition"] = 'attachment; filename="{}"'.format(file_name)

        try:
            os.remove("/tmp/{}".format(file_name))
        except OSError:
            pass

        return response

    def create(self, request):
        # TODO: Check table entry WRITE permissions
        file = request.FILES["file"]
        delimiter = request.POST.get("delimiter", None)
        table_id = request.POST.get("table_id")
        if table_id:
            table = models.Table.objects.get(pk=table_id)
        fields = []

        try:
            file_content = file.read().decode("utf-8")
        except:
            try:
                file.seek(0)
                file_content = file.read().decode("windows-1252")
            except:
                response = {
                    "success": False,
                    "error_msg": 'Could not decode file',
                }
                return Response(response, status=status.HTTP_400_BAD_REQUEST)
        decoded_file = file_content.splitlines()

        if delimiter == 'null':
            delimiter = None
        if not delimiter:
            file.seek(0)
            dialect = csv.Sniffer().sniff(file_content[:2000])
            reader = csv.DictReader(decoded_file, skipinitialspace=True, delimiter=dialect.delimiter)
        else:
            reader = csv.DictReader(decoded_file, skipinitialspace=True, delimiter=delimiter)

        csv_import = models.CsvImport.objects.create(file=file, delimiter=delimiter)

        for field in reader.fieldnames:
            csv_field_map = models.CsvFieldMap.objects.create(
                csv_import=csv_import, original_name=field, display_name=field
            )
            existing_table_field = None
            existing_table_format = None

            if table_id:
                if table.csv_field_mapping.all():
                    field_maps = table.csv_field_mapping.filter(
                        original_name=field)
                    if field_maps:
                        try:
                            existing_table_field = field_maps[0].table_column.pk
                            csv_field_map.table_column = field_maps[0].table_column
                            csv_field_map.field_format = field_maps[0].field_format
                            csv_field_map.field_type = field_maps[0].field_type
                            csv_field_map.required = field_maps[0].required
                            csv_field_map.unique = field_maps[0].unique
                            csv_field_map.save()
                        except:
                            # existing_table_field = models.TableColumn.objects.get(
                            # table=table, name=utils.snake_case(field_maps[0].field_name)).pk
                            pass
                        existing_table_format = field_maps[0].field_format
            fields.append(
                {
                    "original_name": field.encode(),
                    "display_name": field,
                    "field_type": "text",
                    "field_format": existing_table_format,
                    "table_field": existing_table_field,
                    "required": csv_field_map.required,
                    "unique": csv_field_map.unique,
                }
            )

        response = {
            "success": True,
            "import_id": csv_import.pk,
            "fields": fields,
        }
        return Response(response)

class ChartViewSet(viewsets.ModelViewSet):
    queryset = models.Chart.objects.order_by('id').all()
    pagination_class = EntriesPagination

    filter_backends = (OrderingFilter,)

    ordering_fields = {
        'name': 'name',
        'creation_date': 'creation_date',
        'table': 'table__name',
        'owner.username': 'owner__username'
    }

    def get_queryset(self):
        queryset = self.queryset
        user = self.request.user
        user_view_tables = []

        for table in get_objects_for_user(user, 'api.view_table'):
            if user.has_perm('view_table', table) or 'admin' in user.groups.values_list('name', flat=True):
                user_view_tables.append(table)
        return queryset.filter(table__in=user_view_tables)

    def get_serializer_class(self):
        if self.action == "list":
            return serializers.charts.ListSerializer
        elif self.action == "retrieve":
            return serializers.charts.DetailSerializer
        elif self.action in ["create", "update", "partial_update"]:
            return serializers.charts.CreateSerializer

    @action(
        detail=True,
        methods=["get"],
        url_name="add-to-dashboard",
        url_path="add-to-dashboard",
    )
    def add_to_dashboard(self, request, pk):
        chart = self.get_object()
        userprofile = request.user.userprofile

        if chart in userprofile.dashboard_charts.all():
            userprofile.dashboard_charts.remove(chart)
        else:
            userprofile.dashboard_charts.add(chart)
        userprofile.save()
        return Response(
            {'chart_in_dashboard': chart in userprofile.dashboard_charts.all()})

    @action(
        detail=True,
        methods=["get"],
        url_name="data",
        url_path="data",
    )
    def get_data(self, request, pk):
        chart = self.get_object()
        data = utils.get_chart_data(request, chart, chart.table)
        return Response(data)

    @action(
        detail=False,
        methods=["get"],
        url_name="preview",
        url_path="preview",
    )
    def get_preview(self, request):
        chart = models.Chart()
        table = models.Table.objects.get(pk=request.GET.get('table', None))
        timeline_field = models.TableColumn.objects.get(pk=int(request.GET.get('timeline_field', None))) if request.GET.get('timeline_field', None) else None
        x_axis_field = models.TableColumn.objects.get(pk=int(request.GET.get('x_axis_field', None))) if request.GET.get('x_axis_field', None) else None
        y_axis_field = models.TableColumn.objects.get(pk=int(request.GET.get('y_axis_field', None))) if request.GET.get('y_axis_field', None) else None
        chart.table = table
        chart.timeline_field = timeline_field
        chart.x_axis_field = x_axis_field
        chart.y_axis_field = y_axis_field

        chart.chart_type = request.GET.get('chart_type', None)
        chart.timeline_period = request.GET.get('timeline_period', None)
        chart.timeline_include_nulls = True if request.GET.get('timeline_include_nulls', None) == 'true' else False
        chart.y_axis_function = request.GET.get('y_axis_function', None)

        data = utils.get_chart_data(request, chart, table, preview=True)

        return Response(data)

class CardViewSet(viewsets.ModelViewSet):
    queryset = models.Card.objects.order_by('id').all()
    pagination_class = EntriesPagination
    filter_backends = (OrderingFilter,)

    ordering_fields = {
        'name': 'name',
        'creation_date': 'creation_date',
        'table': 'table__name',
        'owner.username': 'owner__username'
    }
    def get_queryset(self):
        queryset = self.queryset
        user = self.request.user
        user_view_tables = []

        for table in get_objects_for_user(user, 'api.view_table'):
            if user.has_perm('view_table', table) or 'admin' in user.groups.values_list('name', flat=True):
                user_view_tables.append(table)
        return queryset.filter(table__in=user_view_tables)

    def get_serializer_class(self):
        if self.action == "list":
            return serializers.cards.ListSerializer
        elif self.action == "retrieve":
            return serializers.cards.DetailSerializer
        elif self.action in ["create", "update", "partial_update"]:
            return serializers.cards.CreateSerializer

    @action(
        detail=True,
        methods=["get"],
        url_name="add-to-dashboard",
        url_path="add-to-dashboard",
    )
    def add_to_dashboard(self, request, pk):
        card = self.get_object()
        userprofile = request.user.userprofile

        profile_card, created = models.UserCard.objects.get_or_create(
            profile=userprofile,
            card=card
            )

        if created:
            profile_card.order = userprofile.cards.count() + 1
            profile_card.save()
        else:
            profile_card.delete()

        return Response(
            {'card_in_dashboard': created})

    @action(
        detail=True,
        methods=["get"],
        url_name="data",
        url_path="data",
    )
    def get_data(self, request, pk):
        card = self.get_object()
        data = utils.get_card_data(request, card, card.table)
        return Response(data)

class TableLinkViewSet(viewsets.ModelViewSet):
    queryset = models.TableLink.objects.order_by('pk').all()
    serializer_class = serializers.tablelinks.TableLinkSerializer


# coding=utf-8
# This is a sample Python script.

# Press Shift+F10 to execute it or replace it with your code.
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Form(object):
    def setupUi(self, Data):
        Data.setObjectName("Data")
        Data.resize(607, 597)
        self.label = QtWidgets.QLabel(Data)
        self.label.setGeometry(QtCore.QRect(230, 40, 141, 16))
        font = QtGui.QFont()
        font.setPointSize(13)
        font.setBold(True)
        font.setWeight(75)

        #Data Mahasiswa
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.label_2 = QtWidgets.QLabel(Data)
        self.label_2.setGeometry(QtCore.QRect(60, 410, 171, 20))
        font = QtGui.QFont()
        font.setPointSize(11)
        font.setBold(True)
        font.setWeight(75)

        #NIM
        self.label_2.setFont(font)
        self.label_2.setObjectName("label_2")
        self.lineEdit = QtWidgets.QLineEdit(Data)
        self.lineEdit.setGeometry(QtCore.QRect(130, 410, 401, 20))
        self.lineEdit.setObjectName("lineEdit")

        #Nama
        self.lineEdit_2 = QtWidgets.QLineEdit(Data)
        self.lineEdit_2.setGeometry(QtCore.QRect(130, 440, 401, 20))
        self.lineEdit_2.setObjectName("lineEdit_2")
        self.label_3 = QtWidgets.QLabel(Data)
        self.label_3.setGeometry(QtCore.QRect(60, 440, 171, 20))
        font = QtGui.QFont()
        font.setPointSize(11)
        font.setBold(True)
        font.setWeight(75)
        self.label_3.setFont(font)
        self.label_3.setObjectName("label_3")

        #Jurusan
        self.lineEdit_3 = QtWidgets.QLineEdit(Data)
        self.lineEdit_3.setGeometry(QtCore.QRect(130, 470, 401, 20))
        self.lineEdit_3.setObjectName("lineEdit_3")
        self.label_4 = QtWidgets.QLabel(Data)
        self.label_4.setGeometry(QtCore.QRect(60, 470, 171, 20))
        font = QtGui.QFont()
        font.setPointSize(11)
        font.setBold(True)
        font.setWeight(75)
        self.label_4.setFont(font)
        self.label_4.setObjectName("label_4")

        #No.Telp
        self.lineEdit_4 = QtWidgets.QLineEdit(Data)
        self.lineEdit_4.setGeometry(QtCore.QRect(130, 500, 401, 20))
        self.lineEdit_4.setObjectName("lineEdit_4")
        self.label_5 = QtWidgets.QLabel(Data)
        self.label_5.setGeometry(QtCore.QRect(60, 500, 171, 20))
        font = QtGui.QFont()
        font.setPointSize(11)
        font.setBold(True)
        font.setWeight(75)
        self.label_5.setFont(font)
        self.label_5.setObjectName("label_5")

        #Button TAMBAH
        self.pushButton = QtWidgets.QPushButton(Data)
        self.pushButton.setGeometry(QtCore.QRect(210, 530, 75, 23))
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.pushButton.setFont(font)
        self.pushButton.setObjectName("pushButton")

        #Button EDIT
        self.pushButton_2 = QtWidgets.QPushButton(Data)
        self.pushButton_2.setGeometry(QtCore.QRect(290, 530, 75, 23))
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.pushButton_2.setFont(font)
        self.pushButton_2.setObjectName("pushButton_2")

        #Button CLEAR
        self.pushButton_3 = QtWidgets.QPushButton(Data)
        self.pushButton_3.setGeometry(QtCore.QRect(370, 530, 75, 23))
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.pushButton_3.setFont(font)
        self.pushButton_3.setObjectName("pushButton_3")

        #Button HAPUS
        self.pushButton_4 = QtWidgets.QPushButton(Data)
        self.pushButton_4.setGeometry(QtCore.QRect(450, 530, 75, 23))
        font = QtGui.QFont()
        font.setBold(True)
        font.setWeight(75)
        self.pushButton_4.setFont(font)
        self.pushButton_4.setObjectName("pushButton_4")

        #Hasil
        self.listWidget = QtWidgets.QListWidget(Data)
        self.listWidget.setGeometry(QtCore.QRect(60, 80, 471, 311))
        self.listWidget.setObjectName("listWidget")

        self.retranslateUi(Data)
        QtCore.QMetaObject.connectSlotsByName(Data)

    def retranslateUi(self, Data):
        _translate = QtCore.QCoreApplication.translate
        Data.setWindowTitle(_translate("Data", "Form"))
        self.label.setText(_translate("Data", "Data Mahasiswa"))
        self.label_2.setText(_translate("Data", "NIM"))
        self.label_3.setText(_translate("Data", "Nama"))
        self.label_4.setText(_translate("Data", "Jurusan"))
        self.label_5.setText(_translate("Data", "No.Telp"))
        self.pushButton.setText(_translate("Data", "TAMBAH"))
        self.pushButton_2.setText(_translate("Data", "EDIT"))
        self.pushButton_3.setText(_translate("Data", "CLEAR"))
        self.pushButton_4.setText(_translate("Data", "HAPUS"))

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Form = QtWidgets.QWidget()
    ui = Ui_Form()
    ui.setupUi(Form)
    Form.show()
    sys.exit(app.exec_())


class Card:
    def __init__(self, representation):
        self.suit = representation[-1]
        self.rank = self.__value(representation[:-1])

    @staticmethod
    def __value(card):
        if card == "T":
            return 10
        if card == "J":
            return 11
        if card == "Q":
            return 12
        if card == "K":
            return 13
        if card == "A":
            return 14
        return int(card)


'''
In this question we are give two variable, maxJump and minJump, here we are given binary string
'''
import collections

def jumpGame(self,s,minJump,maxJump):

    q = collections.defaultdict([0])
    max_reach = 0

    while q:
        curr_i = q.popleft()
        if curr_i == len(s)-1:
            return True

        start = max(curr_i+minJump,max_reach)

        for i in range (start,min(curr_i+maxJump+1,len(s))):
            if s[i] == '0':
                q.append(i)

        max_reach = curr_i+maxJump
    return False


##
# A small program setting up a window
#
# Author Nicholas O'Kelley
##

import pygame

# Initialize pygame
pygame.init()

# Set up the window
window = pygame.display.set_mode((500,400), 0, 32)

# Sets the windows "header"
pygame.display.set_caption('Hello World!')

# boolean for the state of the program
running = True

# Keep the program running until you click the X button
while running:

    # How the program evaluates if the X button was clicked
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

pygame.quit()


class node:
    def __init__(self,data):
        self.data=data
        self.next=None
class slinkedlist:
    def __init__(self):
        self.head=None
 #TRAVERSING THE NODES
    def listprint(self):
        printvalue=self.head
        while printvalue is not None:
            print(printvalue.data)
            printvalue=printvalue.next
list1=slinkedlist()
list1.head=node("100")
list2=node("200")
list3=node("300")
list1.head.next=list2
list2.next=list3
list1.listprint()


import numpy as np
import matplotlib.pyplot as plot
f=20
fs=2000
n=np.arange(0,200,1)
am=np.cos(2*np.pi*f*n/fs)
b=np.random.rand(am.shape[0])
c=am+b
p=input("enter order of system")
x=0
y=[]
for i in range(0,200,1):
	for j in range(0,int(p)-1,1):
		x=x+c[i-j]
		print(x)
	z=float((x)/(p))
	y.append(z)
	x=0
plot.subplot(411)
plot.plot(n,am)
plot.subplot(412)
plot.plot(n,b)
plot.subplot(413)
plot.plot(n,c)
plot.subplot(414)
plot.plot(n,y)
plot.show()


def longest_zig_zag(root):
    """
    Time: O(n) beats 80%
    Space: O(n) beats 45%
    """

    res = 0

    def dfs(node, went_left, last_length):
        if not node: return

        cur_length = last_length + 1
        nonlocal res
        res = max(res, cur_length)

        if went_left:
            dfs(node.left, True, 0)
            dfs(node.right, False, cur_length)
        else:
            dfs(node.left, True, cur_length)
            dfs(node.right, False, 0)

    dfs(root.left, True, 0)
    dfs(root.right, False, 0)
    return res

def longest_zig_zag1(root):
    """
    Time: O(n)
    Space: O(n)
    """

    res = 0

    def dfs(node, last_direction, last_length):
        if not node: return

        cur_length = last_length + 1
        nonlocal res
        res = max(res, cur_length)

        if last_direction == 'left':
            dfs(node.left, 'left', 0)
            dfs(node.right, 'right', cur_length)
        elif last_direction == 'right':
            dfs(node.left, 'left', cur_length)
            dfs(node.right, 'right', 0)
        else:
            dfs(node.left, 'left', 0)
            dfs(node.right, 'right', 0)

    dfs(root, '', -1)
    return res

Tree/1372. Longest ZigZag Path in a Binary Tree.py


from ...tools import check_names, md_table
from ..structure.types import RegType, RegisterSet, Register, WriteEventType, FieldChangeType, Field, FieldType, FieldFunction

import math
from dataclasses import dataclass
import enum

class RegisterMdGenerator:

    def __init__(self, registers: RegisterSet):
        self.registers = registers

        gen = RegisterMdGeneratorHelper(registers)
        self.md = gen.md

    def get_md(self) -> str:
        return self.md

    def save(self, filename: str):
        with open(filename, 'w') as fp:
            fp.write(self.get_md())

class RegisterMdGeneratorHelper:

    def __init__(self, registers: RegisterSet):
        self.registers = registers

        self.generate()

    def generate(self):

        md = []

        md.append(self.registers.name)
        md.append('==========')
        md.append('')
        md.append(f'Base address is 0x{self.registers.get_base_address():08X}.')
        md.append('')
        md.append(f'All registers are {self.registers.port_size} bit wide, granularity is 8 bit')

        md.append('')
        md.append('')
        md.append('## Registers')
        md.append('')

        table = [['Rel. Offset', 'Abs. Address', 'Name', 'Description', 'Access', 'Hardware', 'Comments']]
        comments = []
        for reg in self.registers.registers:

            if reg.regtype == RegType.Write:
                typ, hw = 'Write-Only', 'Out'
            elif reg.regtype == RegType.Read:
                typ, hw = 'Read-Only', 'In'
            elif reg.regtype == RegType.WriteRead:
                typ, hw = 'Write/Read', 'In/Out'
            elif reg.regtype == RegType.ReadEvent:
                typ, hw = 'Event', 'Input'
            elif reg.regtype == RegType.Strobe or reg.regtype == RegType.Handshake:
                typ, hw = 'Strobe', 'Output'
            else:
                raise ValueError()

            if reg.comment is None:
                com = ''
            else:
                comments.append(reg.comment)
                com = len(comments)

            table.append([f'0x{reg.get_relative_address():08X}', f'0x{reg.get_absolute_address():08X}', reg.name, reg.description, typ, hw, com])

        md.extend(md_table(table))

        if len(comments) > 0:
            md.append('')
            for i,c in enumerate(comments):
                md.append(f'{i+1}. {c}')

        md.append('')
        md.append('')
        md.append('## Register Fields')
        for reg in self.registers.registers:

            md.append('')
            md.append('')
            md.append(f'### {reg.description}')
            md.append('')

            if reg.comment is not None:
                md.append(reg.comment)
                md.append('')

            if reg.regtype == RegType.Write:
                md.append('This register is write-only.')
            elif reg.regtype == RegType.Read:
                md.append('This register is read-only.')
            elif reg.regtype == RegType.WriteRead:
                md.append('This register is write/read.')
            elif reg.regtype == RegType.ReadEvent:
                md.append('This register is read-only. It latches changes.')
            elif reg.regtype == RegType.Strobe or reg.regtype == RegType.Handshake:
                md.append('This register is write-only. It only sends triggers to the hardware.')
            if reg.write_event != 0 and reg.write_event is not None:
                md.append('Writing to this register triggers the hardware.')
            md.append('')

            table = [['Bits', 'Name', 'Description', 'Default', 'Access', 'Specials', 'Comments']]
            comments = []
            for field in reg.fields:

                if len(field.bits) == 1:
                    bits = f'[{field.bits[0]}]'
                else:
                    bits = f'[{field.bits[0]}:{field.bits[1]}]'

                if field.default is None:
                    default = 'N/A'
                elif field.datatype == FieldType.Boolean:
                    default = f'{field.default}'
                else:
                    default = f'0x{field.default:X}'

                accesses = []
                if FieldFunction.Read in field.functions:
                    accesses.append('Read')
                if FieldFunction.ReadShadow in field.functions:
                    accesses.append('Read (shadow)')
                if FieldFunction.Overwrite in field.functions:
                    accesses.append('Overwrite')
                if FieldFunction.WriteMasked in field.functions:
                    accesses.append('Write Masked')
                if FieldFunction.WriteShadow in field.functions:
                    accesses.append('Write (shadow)')
                if FieldFunction.ReadModifyWrite in field.functions:
                    accesses.append('RMW')
                if FieldFunction.Strobe in field.functions:
                    accesses.append('Strobe')
                access = ', '.join(accesses)

                specials = []
                if reg.regtype == RegType.ReadEvent:
                    events = []
                    if FieldChangeType.Rising in field.trigger_on:
                        events.append('rising')
                    if FieldChangeType.Falling in field.trigger_on:
                        events.append('falling')
                    if FieldChangeType.High in field.trigger_on:
                        events.append('high')
                    if FieldChangeType.Low in field.trigger_on:
                        events.append('low')
                    if len(events) != 0:
                        specials.append('Trigger on ' + '/'.join(events))
                special = ', '.join(specials)

                if field.comment is None:
                    com = ''
                else:
                    comments.append(field.comment)
                    com = len(comments)

                table.append([bits, field.name, field.description, default, access, special, com])
            md.extend(md_table(table))

            if len(comments) > 0:
                md.append('')
                for i,c in enumerate(comments):
                    md.append(f'{i+1}. {c}')

        md.append('')

        self.md = '\n'.join(md)


#!/usr/bin/env python
# coding=utf-8

import logging
import time

import Global
from handlers.kbeServer.Editor.Data import data_course
from handlers.kbeServer.Editor.Data import data_obj, data_project
from handlers.kbeServer.Editor.Data import data_work
from handlers.kbeServer.Editor.Interface import interface_account
from handlers.kbeServer.Editor.Interface import interface_global, interface_obj
from methods.DBManager import DBManager

# 客户端列表(带版本号)转json，用来比对
def clientVersionDataToJson(pdata):
    p_server = {}
    if pdata is not None and len(pdata) > 0:
        l_pdata = pdata.split('*')
        for info in l_pdata:
            sdata = info.split('^')
            uid = int(sdata[0])
            pid = int(sdata[1])
            version = int(sdata[2])
            if uid not in p_server.keys():
                p_server[uid] = {}
            p_server[uid][pid] = version

    return p_server

# 同步数据
def Get(pdata, self_uid, itype):
    # 版本验证
    p_server = clientVersionDataToJson(pdata)
    # 业务执行
    db = DBManager()
    sql = data_project.GetProjectSQLFromType(itype, self_uid)
    result = data_project.Data_Projects_Base(sql, db, p_server, 0, self_uid)
    db.destroy()
    logging.info("GetProjects result: %s" % result)
    return result[1] + "！" + result[2]

# 是否需要更新
def GetPVersion(db, uid, pid, target):
    return data_project.GetPVersion(db, uid, pid, target)

# 获取版本号
def GetVersion(data, uid, db):
    pid = int(data)
    pversion = data_project.GetVersion(pid, uid, db, 0)
    _back = str(pid) + "^" + str(pversion) + "^" + data_obj.GetVersion(pid, uid, db, 0) + "^"
    return _back

# 上传数据
def UpLoad(l_pdata, uid, db):
    code = 1
    ##print("l_pdata",l_pdata)
    # ['1',
    # '10087',
    #  '10087`qffg`1621908973`1621914045`1002`0`1.691426`5.963621`-7.707121`8.500001`357`-2.104186E-06`0`0`1,1,1`45,180`0`0``0`0`0`0`5`1277`0`0`1`1`',
    #  '20`场景编程区`1621908973`0`0`0`0`0`0`1`1`1`0``&0,0&```0`0`1```0`0``0`7`0!5`3D相机`1621908973`5`6`5`20.75002`345.5`6.847463E-07`1`1`1`0``&0,0&```0`0`2```0`0``0`11`0!13`跳点`1621909005`-4.99883`0`-0.004079991`0`180`0`1`1`1`0``1@5@-236.3524,135.8373,0@D:2@@~2@111@@@1$1|1$0@&0,0&```0`0`3```0`0``0`13`0!23370`女人二`1621909037`-0.0004117982`0`-1.28284`0`184.5`0`1`1`1`0``1@7@-209.9685,56.3863,0@D:2@1$1@~2@103@@@1$1|1$<2d>5|1$0|1$0|2$0@&0,0&```0`0`4```0`0``0`11`0']
    pid = int(l_pdata[1])
    if l_pdata[2] != "":
        project_data = l_pdata[2].split('`')
        ProjectToDB(db, pid, uid, 0, project_data)
    if l_pdata[3] != "":
        object_data = l_pdata[3]
        # object_del = l_pdata[5]
        interface_obj.UpdateToDB(0, db, uid, pid, object_data)

# 发布到背包
def FB(db, self_uid, uid, pid, template):
    sql = "update tb_project set Template = {0} where UID = {1} and PID = {2}".format(str(template), str(uid), str(pid))
    if db.edit(sql, None):
        return 1
    return 0

# 发布到背包
def SetPublish(db, self_uid, uid, pid, publish):
    sql = "update tb_project set Publish = {0} where UID = {1} and PID = {2}".format(str(publish), str(uid), str(pid))
    if db.edit(sql, None):
        return 1
    return 0

# 是否空闲状态
def PFlag(db, uid, pid, dtype):
    list_work_base = data_work.Data_Work_Base(uid, pid, 0, db, 2)
    if list_work_base:
        # #print("xxxxxxxxxxxxxxxxxx : ", list_work_base)
        if int(list_work_base[7]) == 1 and int(list_work_base[4]) == pid:
            return 0  # 作品审核中不能移出
    if data_course.ProjectInCourses(db, uid, pid, dtype):
        return -1  # 课程审核中不能移出

    return 1

# 移出
def RemoveP(db, self_uid, uid, pid):
    code = PFlag(db, uid, pid, 0)
    if code == 1:
        sql = "update tb_project set Template = 0 where UID = " + str(uid) + " and PID = " + str(pid)
        result = db.edit(sql, None)
        if result:
            sql = "delete from tb_workbag where uid = " + str(uid) + " and WID = " + str(pid)
            db.edit(sql, None)
        return 1
    return code

# 删除
def DeleteP(db, self_uid, uid, pid, nocheck):
    if nocheck == 1:
        code = 1
    else:
        code = PFlag(db, uid, pid, 1)
    if code == 1:
        if data_project.Delete(db, uid, pid, 0):
            if data_obj.Delete(db, uid, pid, 0):
                interface_account.DelPPackage(db, uid, pid)
                return 1
            else:
                return -99
        else:
            return -99
    return code

# 存库
def ProjectToDB(db, pid, uid, target, data):
    table_name = ""
    if target == 0:
        table_name = "tb_project"
    else:
        table_name = "tb_mproject"

    sql = "select id from " + table_name + " where uid = " + str(uid) + " and PID = " + str(pid)
    result = db.fetchone(sql, None)
    if result:
        sql = "Update " + table_name + " set PID=" + str(data[0]) + ", PName='" + str(data[1]) + "', CreateDate=" + str(
                data[2]) + ", EditDate=" + str(data[3]) + ", SID=" + str(data[4]) + ",BDelete = " + str(
                data[5]) + ", C_Posx=" + str(data[6]) + ", C_Posy=" + str(data[7]) + ", C_Posz=" + str(
                data[8]) + ", C_Rotx=" + str(data[9]) + ", C_Roty=" + str(data[10]) + ", C_Rotz=" + str(
                data[11]) + ", Template=" + str(data[12]) + ", LightIntensity=" + str(data[13]) + ", LightColor='" + str(
                data[14]) + "', LightAngle='" + str(data[15]) + "', Power=" + str(data[16]) + ", Sort=" + str(
                data[17]) + ",FullViewPath='" + str(data[18]) + "', ParentPid=" + str(data[19]) + ", Publish=" + str(
                data[20]) + ", Skybox=" + str(data[21]) + ", HType=" + str(data[22]) + ",Version=" + str(
                data[23]) + ",p_uid = " + str(data[25]) + ",P_TYPE= " + str(data[26]) + ",PDate= " + str(
                data[27]) + ",`From` = " + str(data[28]) + ",FromPam = '" + str(data[29]) + "' where PID = " + str(
                pid) + " and uid =" + str(uid)
    else:
        sql = "Insert into " + table_name + " (PID, PName, CreateDate, EditDate, SID,BDelete, C_Posx, C_Posy, C_Posz, C_Rotx, C_Roty, C_Rotz, Template, LightIntensity, LightColor, LightAngle, Power, Sort, FullViewPath, ParentPid, Publish, Skybox, HType,Version,UID,p_uid,P_TYPE,PDate,`From`,FromPam) values (" + str(
                data[0]) + ",'" + str(data[1]) + "'," + str(data[2]) + "," + str(data[3]) + "," + str(data[4]) + "," + str(
                data[5]) + "," + str(data[6]) + "," + str(data[7]) + "," + str(data[8]) + "," + str(data[9]) + "," + str(
                data[10]) + "," + str(data[11]) + "," + str(data[12]) + "," + str(data[13]) + ",'" + str(
                data[14]) + "','" + str(data[15]) + "'," + str(data[16]) + "," + str(data[17]) + ",'" + str(
                data[18]) + "'," + str(data[19]) + "," + str(data[20]) + "," + str(data[21]) + "," + str(
                data[22]) + "," + str(data[23]) + "," + str(uid) + "," + str(data[25]) + "," + str(data[26]) + "," + str(
                data[27]) + "," + str(data[28]) + ",'" + str(data[29]) + "')"
    # print("insert Into P : " , sql)
    db.edit(sql, None)

def SetFrom(db, uid, pid, From, FromPam):
    sql = "Update tb_project set `From`={0},FromPam = \'{1}\' where uid = {2} and pid = {3}".format(str(From), str(FromPam), str(uid), str(pid))
    db.edit(sql, None)
    return 1

# 复制或者转移工程到别的账号
# pid 我的工程ID
# cmode 0-复制 1-转移
# username 转移到的账号
def CopyMyProjectToAccount(db, self_uid, pid, pname, username, cmode):
    if len(pname) < 1:
        return 0  # 工程名称长度不足

    if cmode == 1:
        # 转移
        code = PFlag(db, self_uid, pid, 1)
        if code != 1:
            return -1

    ARRID = interface_global.NewPIDFromUserName(db, username)
    NPID = ARRID[0] + 1
    P_UID = ARRID[1]

    b_type = False
    user_power = interface_account.get_user_power(db, username)
    # 判断是否是B类用户
    if user_power in (2, 3, 4):
        b_type = True

    # B类用户不需要判断，直接转移
    target_bag = 0
    if not b_type:
        target_bag = interface_account.get_user_bag(db, username)
        if not target_bag:
            # 用户无空余包裹位
            return -2

    # 复制工程数据
    data__p = data_project.Data_Project_Base(self_uid, pid, 0, db, 2)
    if data__p:
        data__p[0] = NPID
        data__p[1] = pname
        data__p[2] = int(time.time())
        data__p[3] = int(time.time())
        data__p[12] = 0
        data__p[19] = 0
        data__p[20] = 0
        data__p[24] = P_UID
        data__p[25] = 0
        data__p[27] = 0
        data__p[28] = 104
        data__p[29] = str(self_uid)
        ProjectToDB(db, NPID, P_UID, 0, data__p)

    # 复制资源
    data_o = data_obj.Data_Objs_Base(0, db, pid, self_uid, {}, 0)
    if data_o:
        # print("data_o:", data_o)
        # interface_obj.UpdateToDB(0, db, P_UID, NPID, data_o)

        interface_obj.CopyToDB(0, db, P_UID, NPID, Global.GetObjTableName(self_uid, pid))

        # NPID 转移的工程PID
        # target_bag 目标账号的一个空余包裹位
        if not b_type:
            interface_account.update_user_bag(db, NPID, target_bag)

    if cmode == 1:
        # 工程转移时调用，清除工程并清除本账号当前的存储位
        del_pro = DeleteP(db, self_uid, self_uid, pid, 1)
        logging.info("转移工程: %s" % del_pro)
        # data_project.Delete(db,self_uid,pid,0)

    return 1


import os
import json
from pathlib import Path

## Config
os.environ = {}
CONFIG_OPTIONS = {}                     # This'll be populated on import
CONFIG_NAME = "config.json"	            # The name of the config file
DEV_CONFIG_NAME = "config.dev.json"     # The name of the dev config file (overrides properties stored in the normal and prod config files)
PROD_CONFIG_NAME = "config.prod.json"   # The name of the prod config file (overrides properties stored in the normal config file)
DIRS_FROM_ROOT = 1			            # How many directories away this script is from the root

def get_root_path() -> Path:
    path = Path(__file__)

    for _ in range(DIRS_FROM_ROOT + 1):  # the '+ 1' includes this script in the path
        path = path.parent

    return path

def load_json(path: Path) -> dict:
    with open(path) as fd:
        return json.load(fd)

def save_json(path: Path, data: dict):
    with open(path, 'w') as fd:
        json.dump(data, fd)

def load_config(directory_path: Path = None) -> dict:
    '''
    Parses one or more JSON configuration files to build a dictionary with proper precedence for configuring the program

    :param directory_path: Optional path to load configuration files from. If None, then the program's root (cwd/..) will be searched.
    :type directory_path: Path, optional
    :return: A dictionary containing key-value pairs for use in configuring parts of the program.
    :rtype: dictionary
    '''

    path = directory_path or get_root_path()

    config_path = Path.joinpath(path, CONFIG_NAME)
    if (not config_path.exists()):
        raise RuntimeError("Unable to find config.json file in root!")

    config = load_json(config_path)

    ## Override the config values if the prod config file exists.
    prod_config_path = Path.joinpath(path, PROD_CONFIG_NAME)
    if (prod_config_path.exists()):
        prod_config = load_json(prod_config_path)

        for key, value in prod_config.items():
            config[key] = value

    ## Override the config values if the dev config file exists.
    dev_config_path = Path.joinpath(path, DEV_CONFIG_NAME)
    if (dev_config_path.exists()):
        dev_config = load_json(dev_config_path)

        for key, value in dev_config.items():
            config[key] = value

    return config


from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:  # pragma: no cover
    from pyNastran.bdf.bdf import BDF
from pyNastran.bdf.cards.optimization import DRESP1

def constrain_solid_stress_from_properties(model: BDF, pids: list[int],
                                          constraint_id: int, dresp_id: int, uallow: float):
    """adds VM stress constraints for solids"""
    skip_properties = [
        'PROD', 'PELAS', 'PDAMP',
        'PBAR', 'PBEAM', 'PBARL', 'PBEAML', 'PBCOMP', 'PBEND',
        'PSHELL', 'PCOMP', 'PCOMPG', 'PSHEAR',
    ]
    response_type = 'PSOLID'
    for pid in pids:
        prop = model.properties[pid]
        if prop.type in skip_properties:
            continue
        if prop.type == 'PSOLID':
            label = f'p{pid}'[:8]
            property_type = 'PSOLID'
            atta = 13 # von mises stress
            region = None
            attb = None
            atti = [pid]
            model.add_dresp1(dresp_id, label, response_type, property_type, region, atta, attb, atti)
            model.add_dconstr(constraint_id, dresp_id, uid=uallow)
        else:
            raise NotImplementedError(prop)

def constrain_shell_stress_from_properties(model: BDF, pids: list[int],
                                           constraint_id: int, uallow: float,
                                           dresp_id: int=1):
    """adds VM stress constraints for shells"""
    skip_properties = [
        'PROD', 'PELAS', 'PDAMP',
        'PBAR', 'PBEAM', 'PBARL', 'PBEAML', 'PBCOMP', 'PBEND',
        'PSOLID', 'PLSOLID',
        'PCOMP', 'PCOMPG', 'PSHEAR',
    ]
    response_type = 'STRESS'
    for pid in pids:
        prop = model.properties[pid]
        if prop.type in skip_properties:
            continue
        if prop.type == 'PSHELL':
            property_type = 'PSHELL'
            atta = [9, 17] # von mises upper/lower surface stress
            region = None
            attb = None
            atti = [pid]

            label = f'o{pid}U'[:8]
            model.add_dresp1(dresp_id, label, response_type, property_type, region, atta[0], attb, atti)
            model.add_dconstr(constraint_id, dresp_id, uid=uallow)
            dresp_id += 1

            label = f'o{pid}L'[:8]
            model.add_dresp1(dresp_id, label, response_type, property_type, region, atta[1], attb, atti)
            model.add_dconstr(constraint_id, dresp_id, uid=uallow)
            dresp_id += 1

        #elif prop.type == 'PCOMP':
            #label = 'resp2'
            #property_type = 'PCOMP'
            #layer = 4
            #atta = 9 # von mises upper surface stress
            #region = None
            #attb = layer
            #atti = [pid]
            #DRESP1(dresp_id, label, response_type, property_type, region, atta, attb, atti)
        else:
            raise NotImplementedError(prop)

    DRESP1(dresp_id, label, response_type, property_type, region, atta, attb, atti,
           comment='')

def solid_topology_optimization(model: BDF, pids_to_optimize: list[int], xinit=0.5, uallow=1.e20):
    model.sol = 200
    eids_dict = model.get_element_ids_dict_with_pids(pids_to_optimize)
    pid = max(model.properties) + 1
    model.uncross_reference()

    response_type = 'STRESS'
    dresp_id = 1
    constraint_id = 1
    for pid_old, eids in sorted(eids_dict.items()):
        prop_old = model.properties[pid_old]
        if prop_old.type != 'PSOLID':
            continue
        assert prop_old.type == 'PSOLID', prop_old.get_stats()

        opt_id = 101
        label = f'top{pid}'[:8]
        prop_type = 'PSOLID'
        model.add_topvar(opt_id, label, prop_type, xinit, pid, xlb=0.001, delxv=0.2, power=3)

        label = f'p{pid}'[:8]
        property_type = 'PSOLID'
        atta = 13 # von mises stress
        region = None
        attb = None
        atti = [pid]
        model.add_dresp1(dresp_id, label, response_type, property_type, region, atta, attb, atti)
        model.add_dconstr(constraint_id, dresp_id, uid=uallow)
        dresp_id += 1

        #for eid in eids:
            #elem = model.elements[eid]
            #assert elem.type in ['CTETRA', 'CHEXA', 'CPENTA', 'CPYRAM'], elem.get_stats()
            #elem.pid = pid
            #model.properties[pid] = deepcopy(prop_old)

            #label = f'e{eid}'
            #response_type = 'STRESS'
            #property_type = 'PSHELL'
            #atta = 9 # von mises upper surface stress
            #region = None
            #attb = None
            #atti = [pid]
            #DRESP1(dresp_id, label, response_type, property_type, region, atta, attb, atti)
            #model.add_dresp1(dresp_id, label, response_type, property_type, region,
                             #atta, attb, atti, validate=True, comment='')
            #pid += 1

    #model.properties[pid]


from .base_installer import FlaskExtInstaller
from ..config import TAB

class FlaskTalismanInstaller(FlaskExtInstaller):
    package_name = "Flask-Talisman"
    imports = ["from flask_talisman import Talisman"]
    inits = ["talisman = Talisman()"]
    attachments = [
        'force_https = True if app.config.get("ENV") != "testing" else False',
        "talisman.init_app(",
        f"{TAB}app,",
        f"{TAB}force_https=force_https",
        ")",
    ]


class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        # 逻辑上将二维矩阵展平成一维，然后进行二分搜索
        if len(matrix) == 0 or len(matrix[0]) == 0:
            return False
        row = len(matrix)
        col = len(matrix[0])
        left, right = 0, row*col - 1
        while left <= right:
            mid = left + (right - left) // 2
            val = matrix[mid//col][mid % col] #求中点的值
            if val == target:
                return True
            elif val < target:
                left = mid + 1
            elif val > target:
                right = mid - 1
        return False