from django.shortcuts import render, redirect
from django.http import Http404, HttpResponseRedirect
from django.urls import reverse
from .models import Article, Category, ArticleCategoryRelation
from django.utils import timezone
from .forms import UserRegistrationForm
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.views import View
from django.views.generic import ListView
import json
from django.http import JsonResponse
from django.template.loader import render_to_string

items_on_page = 3

def index(request):
    article_list = Article.objects.all()[:items_on_page]
    categories = Category.objects.all()
    return render(request, 'articles/main.html', {'all_articles': article_list,
                                                  'categories': categories,
                                                  'is_admin': request.user.is_staff, })

class ArticleListView(ListView):

    def get(self, request, **kwargs):
        user = request.user
        if user.groups.filter(name='admin').count():
            is_admin = True
        article_list = Article.objects.all()
        current_page = Paginator(article_list, items_on_page)
        page = request.GET.get('page')
        try:
            page_articles = current_page.page(page)
        except:
            page_articles = current_page.page(1)

        data = json.dumps(list(Article.objects.values_list('id', 'title')))
        categories = Category.objects.all()
        return render(request, 'articles/list.html',
                      {'all_articles': page_articles, 'is_admin': request.user.is_staff, 'qs_json': data,
                       'categories': categories, })

def create_article(request):
    if not request.user.is_staff:
        raise Http404('Доступ запрещен!')

    if request.method == 'POST':
        try:
            category_choices = [x for x in request.POST.getlist('category')]
            category_list = [Category.objects.get(id=category_id) for category_id in category_choices]
        except:
            raise Http404('Категория не найдена!')
        request.user.article_set.create(title=request.POST['title'], text=request.POST['text'], date=timezone.now())
        current_article = Article.objects.all()[0]
        for category in category_list:
            category.includes_article.add(current_article)
        return redirect('/')
    category_list = Category.objects.all()
    return render(request, 'articles/create.html', {'category_list': category_list})

def update_article(request, article_id):
    if not request.user.is_staff:
        raise Http404('Доступ запрещен!')

    current_article = Article.objects.get(id=article_id)
    if not current_article:
        raise Http404('Статья не найдена!')

    if request.method == 'POST':

        # try:
        #     category_choices = [x for x in request.POST.getlist('category')]
        #     category_list = [Category.objects.get(id=category_id) for category_id in category_choices]
        # except:
        #     raise Http404('Категория не найдена!')

        current_article.title=request.POST['title']
        current_article.text=request.POST['text']
        current_article.save()
        # ArticleCategoryRelation.objects.filter(article=current_article).delete()
        #
        # for category in category_list:
        #     category.includes_article.add(current_article)

        return redirect('/')

    category_list = Category.objects.all()
    category_of_article = ArticleCategoryRelation.objects.filter(article=current_article)

    return render(request, 'articles/update.html', {'category_list': category_list,
                                                    'article': current_article,
                                                    'article_category': category_of_article})

def leave_comment(request, article_id):
    try:
        article = Article.objects.get(id=article_id)
    except:
        raise Http404('Статья не найдена!')
    article.comment_set.create(author=request.user, text=request.POST['text'], date=timezone.now())
    return HttpResponseRedirect(reverse('newnotes:view_article', args=(article.id,)))

def profile(request):
    if request.user.is_anonymous:
        raise Http404('Доступ запрещен!')

    categories = Category.objects.all()
    return render(request, 'account/profile.html', {'categories': categories, })

def register(request):
    if request.method == 'POST':
        form = UserRegistrationForm(request.POST)
        if form.is_valid():
            new_user = form.save()
            return render(request, 'registration/register_done.html', {'new_user': new_user})
        else:
            print(form.errors.as_data())
    else:
        form = UserRegistrationForm()
    return render(request, 'registration/register.html', {'form': form})

def delete_article(request, article_id):
    if not request.user.is_staff:
        raise Http404('Доступ запрещен!')
    try:
        article = Article.objects.get(id=article_id)
    except:
        raise Http404('Статья не найдена!')
    if request.method == "POST":
        article.delete()
        return redirect('/')
    return render(request, 'articles/delete.html', {'article': article})

def create_category(request):
    if not request.user.is_staff:
        raise Http404('Доступ запрещен!')
    if request.method == 'POST':
        Category.objects.create(name=request.POST['name'])
        return redirect('/')
    category_list = Category.objects.all()
    return render(request, 'categories/create.html', {'category_list': category_list, })

def delete_category(request, category_id):
    if not request.user.is_staff:
        raise Http404('Доступ запрещен!')
    try:
        category = Category.objects.get(id=category_id)
    except:
        raise Http404('Категория не найдена!')
    if request.method == "POST":
        category.delete()
        return redirect('/')
    category_list = Category.objects.all()
    return render(request, 'categories/delete.html', {'category': category, 'category_list': category_list, })

def update_category(request, category_id):
    if not request.user.is_staff:
        raise Http404('Доступ запрещен!')
    try:
        category = Category.objects.get(id=category_id)
    except:
        raise Http404('Категория не найдена!')

    if request.method == 'POST':
        Category.objects.filter(id=category_id).update(name=request.POST['name'])
        return redirect('/')
    category_list = Category.objects.all()
    return render(request, 'categories/update.html', {'category': category, 'category_list': category_list, })

class ListCategoryArticles(ListView):

    def get(self, request, category_id, **kwargs):
        rel_category_article = ArticleCategoryRelation.objects.filter(category=category_id).order_by('-id')
        category = Category.objects.all().get(id=category_id)
        article_list = [Article.objects.get(id=x.article.id) for x in rel_category_article]
        current_page = Paginator(article_list, items_on_page)
        page = request.GET.get('page')
        try:
            context = current_page.page(page)
        except:
            context = current_page.page(1)

        data = json.dumps(list(Article.objects.values_list('id', 'title')))
        categories = Category.objects.all()
        return render(request, 'categories/list.html',
                      {'all_articles': context, 'is_admin': request.user.is_staff, 'qs_json': data,
                       'categories': categories,
                       'category': category, })

def get_paginated_page(request, objects, number=items_on_page):
    current_page = Paginator(objects, number)
    page = request.GET.get('page') if request.method == 'GET' else request.POST.get('page')
    try:
        return current_page.page(page)
    except PageNotAnInteger:
        return current_page.page(1)
    except EmptyPage:
        return current_page.page(current_page.num_pages)

def is_ajax(request):
    return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'

class ViewArticle(View):

    def get(self, request, article_id):
        try:
            article = Article.objects.get(id=article_id)
        except:
            raise Http404('Статья не найдена!')
        list_comments = article.comment_set.order_by('-id')
        if not request.user.is_anonymous:
            article.readers.add(request.user)
        watched = article.readers.count()
        categories = Category.objects.all()
        return render(request, 'articles/view.html',
                      {'article': article, 'list_comments': get_paginated_page(request, list_comments),
                       'watched': watched, 'categories': categories, })

    def post(self, request, article_id):
        if is_ajax(request):
            try:
                article = Article.objects.get(id=article_id)
            except:
                raise Http404('Статья не найдена!')
            return JsonResponse({
                "result": True,
                "comms": render_to_string(
                    request=request,
                    template_name='articles/comms.html',
                    context={'list_comments': get_paginated_page(request, article.comment_set.order_by('-id'))}
                )
            })
        else:
            raise Http404()


# This class defines the control data that we want to keep for debugging purposes
class LogDataSet():
	def __init__(self):
		# sensors' values
		self.sensors = LogSensorsData()
		# control values
		self.control = LogControlData()

	def setSensorsValues(self, axes, gyroscopeRate):
		# set sensors' values
		self.sensors = axes
		self.sensors["gyroscopeRate"] = gyroscopeRate
		#self.sensors.setSensorsValues(axes, gyroscopeRate)

	def setControlValues(self, accelerometerAngle, angle, error, integral_error, differential_error, u, dt):
		# set the control values
		self.control.setControlValues(accelerometerAngle, angle, error, integral_error, differential_error, u, dt)

class LogSensorsData:
	def __init__(self):
		# sensors' values
		self.axes = None
		self.gyroscopeRate = None

class LogControlData:
	def __init__(self):
		# control values
		self.accelerometerAngle = None
		self.angle = None
		self.error = None
		self.integral_error = None
		self.differential_error = None
		self.u = None
		self.dt = None

	def setControlValues(self, accelerometerAngle, angle, error, integral_error, differential_error, u, dt):
		# set the control values
		self.accelerometerAngle = accelerometerAngle
		self.angle = angle
		self.error = error
		self.integral_error = integral_error
		self.differential_error = differential_error
		self.u = u
		self.dt = dt


import numpy as np

class GradientDescentLinearRegression:
    def __init__(self, learning_rate=0.01, iterations=1000):
        self.learning_rate, self.iterations = learning_rate, iterations

    def fit(self, X, y):
        b = 0
        m = 5
        n = X.shape[0]
        for _ in range(self.iterations):
            b_gradient = -2 * np.sum(y - m*X + b) / n
            m_gradient = -2 * np.sum(X*(y - (m*X + b))) / n
            b = b + (self.learning_rate * b_gradient)
            m = m - (self.learning_rate * m_gradient)
        self.m, self.b = m, b

    def predict(self, X):
        return self.m*X + self.b

np.random.seed(42)
X = np.array(sorted(list(range(5))*20)) + np.random.normal(size=100, scale=0.5)
y = np.array(sorted(list(range(5))*20)) + np.random.normal(size=100, scale=0.25)

clf = GradientDescentLinearRegression()
clf.fit(X, y)

import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')

plt.scatter(X, y, color='black')
plt.plot(X, clf.predict(X))
plt.gca().set_title("Gradient Descent Linear Regressor")

print("The intercept of the best fit line, b= ",clf.b)

print("The slope of the best fit line, m= ",clf.m)


#!/usr/bin/python3

# birds{id:{'pos':[x,y], 'ori':[x,y]}}
pos = 'pos'
ori = 'dir'
X = 0
Y = 1
swarmSize = 1
swarm = {1:{pos:[0,0], ori:[0,0]}}

def updatePos(target):
	target[pos] = [target[pos][X]+target[ori][X], target[pos][Y]+target[ori][Y]]

for target in swarm:
	force = [0,0]

	for neighbor in repulsionZone(target):
		force += target[param[repulsion]] * vector(target, neighbor) * (1/distance(target, neighbor))

	for neighbor in alignmentZone(target):
		force += target[param[alignment]] * neighbor[ori] * (1/distance(target, neighbor))

	for neighbor in attractionZone(target):
		force += target[param[attraction]] * vector(target, neighbor) * (1/distance(target, neighbor))

	dx = force[X]
	dy = force[Y]

	target[ori] = [dx,dy]


"""
This file is meant to optimize the import speed. Import modules from YOLOv7 projects
and Ultralytics take significant amount of time
"""
import glob
import math
import logging
import numpy as np
import os
import re
import time
import urllib

from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
from threading import Thread

import cv2
import torch
import torch.nn as nn
import torchvision

logging.basicConfig(filename="history.log",
                    format="%(asctime)s - %(levelname)s - %(module)s: %(message)s",
                    datefmt="%Y-%m-%d %H:%M:%S %p",
                    level=logging.INFO)

"""
From utils.general
"""

def box_iou(box1, box2):
    # https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
    """
    Return intersection-over-union (Jaccard index) of boxes.
    Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
    Arguments:
        box1 (Tensor[N, 4])
        box2 (Tensor[M, 4])
    Returns:
        iou (Tensor[N, M]): the NxM matrix containing the pairwise
            IoU values for every element in boxes1 and boxes2
    """

    def box_area(box):
        # box = 4xn
        return (box[2] - box[0]) * (box[3] - box[1])

    area1 = box_area(box1.T)
    area2 = box_area(box2.T)

    # inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)
    inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)
    return inter / (area1[:, None] + area2 - inter)  # iou = inter / (area1 + area2 - inter)

def clean_str(s):
    # Cleans a string by replacing special characters with underscore _
    return re.sub(pattern="[|@#!¡·$€%&()=?¿^*;:,¨´><+]", repl="_", string=s)

def make_divisible(x, divisor):
    # Returns x evenly divisible by divisor
    return math.ceil(x / divisor) * divisor

def check_img_size(img_size, s=32):
    # Verify img_size is a multiple of stride s
    new_size = make_divisible(img_size, int(s))  # ceil gs-multiple
    if new_size != img_size:
        print('WARNING: --img-size %g must be multiple of max stride %g, updating to %g' % (img_size, s, new_size))
    return new_size

def clip_coords(boxes, img_shape):
    # Clip bounding xyxy bounding boxes to image shape (height, width)
    boxes[:, 0].clamp_(0, img_shape[1])  # x1
    boxes[:, 1].clamp_(0, img_shape[0])  # y1
    boxes[:, 2].clamp_(0, img_shape[1])  # x2
    boxes[:, 3].clamp_(0, img_shape[0])  # y2

def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
    # Rescale coords (xyxy) from img1_shape to img0_shape
    if ratio_pad is None:  # calculate from img0_shape
        gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])  # gain  = old / new
        pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2  # wh padding
    else:
        gain = ratio_pad[0][0]
        pad = ratio_pad[1]

    coords[:, [0, 2]] -= pad[0]  # x padding
    coords[:, [1, 3]] -= pad[1]  # y padding
    coords[:, :4] /= gain
    clip_coords(coords, img0_shape)
    return coords

def xyxy2xywh(x):
    # Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] where xy1=top-left, xy2=bottom-right
    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
    y[:, 0] = (x[:, 0] + x[:, 2]) / 2  # x center
    y[:, 1] = (x[:, 1] + x[:, 3]) / 2  # y center
    y[:, 2] = x[:, 2] - x[:, 0]  # width
    y[:, 3] = x[:, 3] - x[:, 1]  # height
    return y

def xywh2xyxy(x):
    # Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
    y[:, 0] = x[:, 0] - x[:, 2] / 2  # top left x
    y[:, 1] = x[:, 1] - x[:, 3] / 2  # top left y
    y[:, 2] = x[:, 0] + x[:, 2] / 2  # bottom right x
    y[:, 3] = x[:, 1] + x[:, 3] / 2  # bottom right y
    return y

def apply_classifier(x, model, img, im0):
    # applies a second stage classifier to yolo outputs
    im0 = [im0] if isinstance(im0, np.ndarray) else im0
    for i, d in enumerate(x):  # per image
        if d is not None and len(d):
            d = d.clone()

            # Reshape and pad cutouts
            b = xyxy2xywh(d[:, :4])  # boxes
            b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1)  # rectangle to square
            b[:, 2:] = b[:, 2:] * 1.3 + 30  # pad
            d[:, :4] = xywh2xyxy(b).long()

            # Rescale boxes from img_size to im0 size
            scale_coords(img.shape[2:], d[:, :4], im0[i].shape)

            # Classes
            pred_cls1 = d[:, 5].long()
            ims = []
            for j, a in enumerate(d):  # per item
                cutout = im0[i][int(a[1]):int(a[3]), int(a[0]):int(a[2])]
                im = cv2.resize(cutout, (224, 224))  # BGR
                # cv2.imwrite('test%i.jpg' % j, cutout)

                im = im[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416
                im = np.ascontiguousarray(im, dtype=np.float32)  # uint8 to float32
                im /= 255.0  # 0 - 255 to 0.0 - 1.0
                ims.append(im)

            pred_cls2 = model(torch.Tensor(ims).to(d.device)).argmax(1)  # classifier prediction
            x[i] = x[i][pred_cls1 == pred_cls2]  # retain matching class detections

    return x

def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False,
                        labels=()):
    """Runs Non-Maximum Suppression (NMS) on inference results

    Returns:
         list of detections, on (n,6) tensor per image [xyxy, conf, cls]
    """

    nc = prediction.shape[2] - 5  # number of classes
    xc = prediction[..., 4] > conf_thres  # candidates

    # Settings
    min_wh, max_wh = 2, 4096  # (pixels) minimum and maximum box width and height
    max_det = 300  # maximum number of detections per image
    max_nms = 30000  # maximum number of boxes into torchvision.ops.nms()
    time_limit = 10.0  # seconds to quit after
    redundant = True  # require redundant detections
    multi_label &= nc > 1  # multiple labels per box (adds 0.5ms/img)
    merge = False  # use merge-NMS

    t = time.time()
    output = [torch.zeros((0, 6), device=prediction.device)] * prediction.shape[0]
    for xi, x in enumerate(prediction):  # image index, image inference
        # Apply constraints
        # x[((x[..., 2:4] < min_wh) | (x[..., 2:4] > max_wh)).any(1), 4] = 0  # width-height
        x = x[xc[xi]]  # confidence

        # Cat apriori labels if autolabelling
        if labels and len(labels[xi]):
            l = labels[xi]
            v = torch.zeros((len(l), nc + 5), device=x.device)
            v[:, :4] = l[:, 1:5]  # box
            v[:, 4] = 1.0  # conf
            v[range(len(l)), l[:, 0].long() + 5] = 1.0  # cls
            x = torch.cat((x, v), 0)

        # If none remain process next image
        if not x.shape[0]:
            continue

        # Compute conf
        if nc == 1:
            x[:, 5:] = x[:, 4:5] # for models with one class, cls_loss is 0 and cls_conf is always 0.5,
                                 # so there is no need to multiplicate.
        else:
            x[:, 5:] *= x[:, 4:5]  # conf = obj_conf * cls_conf

        # Box (center x, center y, width, height) to (x1, y1, x2, y2)
        box = xywh2xyxy(x[:, :4])

        # Detections matrix nx6 (xyxy, conf, cls)
        if multi_label:
            i, j = (x[:, 5:] > conf_thres).nonzero(as_tuple=False).T
            x = torch.cat((box[i], x[i, j + 5, None], j[:, None].float()), 1)
        else:  # best class only
            conf, j = x[:, 5:].max(1, keepdim=True)
            x = torch.cat((box, conf, j.float()), 1)[conf.view(-1) > conf_thres]

        # Filter by class
        if classes is not None:
            x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]

        # Apply finite constraint
        # if not torch.isfinite(x).all():
        #     x = x[torch.isfinite(x).all(1)]

        # Check shape
        n = x.shape[0]  # number of boxes
        if not n:  # no boxes
            continue
        elif n > max_nms:  # excess boxes
            x = x[x[:, 4].argsort(descending=True)[:max_nms]]  # sort by confidence

        # Batched NMS
        c = x[:, 5:6] * (0 if agnostic else max_wh)  # classes
        boxes, scores = x[:, :4] + c, x[:, 4]  # boxes (offset by class), scores
        i = torchvision.ops.nms(boxes, scores, iou_thres)  # NMS
        if i.shape[0] > max_det:  # limit detections
            i = i[:max_det]
        if merge and (1 < n < 3E3):  # Merge NMS (boxes merged using weighted mean)
            # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)
            iou = box_iou(boxes[i], boxes) > iou_thres  # iou matrix
            weights = iou * scores[None]  # box weights
            x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True)  # merged boxes
            if redundant:
                i = i[iou.sum(1) > 1]  # require redundancy

        output[xi] = x[i]
        if (time.time() - t) > time_limit:
            print(f'WARNING: NMS time limit {time_limit}s exceeded')
            break  # time limit exceeded

    return output

"""
From models.common.py
"""

def autopad(k, p=None):  # kernel, padding
    # Pad to 'same'
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # auto-pad
    return p

class Conv(nn.Module):
    # Standard convolution
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super(Conv, self).__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def fuseforward(self, x):
        return self.act(self.conv(x))

"""
From models.experimental.py
"""

class Ensemble(nn.ModuleList):
    # Ensemble of models
    def __init__(self):
        super(Ensemble, self).__init__()

    def forward(self, x, augment=False):
        y = []
        for module in self:
            y.append(module(x, augment)[0])
        # y = torch.stack(y).max(0)[0]  # max ensemble
        # y = torch.stack(y).mean(0)  # mean ensemble
        y = torch.cat(y, 1)  # nms ensemble
        return y, None  # inference, train output

def attempt_load(weights, map_location=None):
    # Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a
    model = Ensemble()
    for w in weights if isinstance(weights, list) else [weights]:
        # attempt_download(w)
        ckpt = torch.load(w, map_location=map_location)  # load
        model.append(ckpt['ema' if ckpt.get('ema') else 'model'].float().fuse().eval())  # FP32 model

    # Compatibility updates
    for m in model.modules():
        if type(m) in [nn.Hardswish, nn.LeakyReLU, nn.ReLU, nn.ReLU6, nn.SiLU]:
            m.inplace = True  # pytorch 1.7.0 compatibility
        elif type(m) is nn.Upsample:
            m.recompute_scale_factor = None  # torch 1.11.0 compatibility
        elif type(m) is Conv:
            m._non_persistent_buffers_set = set()  # pytorch 1.6.0 compatibility

    if len(model) == 1:
        return model[-1]  # return model
    else:
        print('Ensemble created with %s\n' % weights)
        for k in ['names', 'stride']:
            setattr(model, k, getattr(model[-1], k))
        return model  # return ensemble

"""
From utils.datasets.py
"""
img_formats = ['bmp', 'jpg', 'jpeg', 'png', 'tif', 'tiff', 'dng', 'webp', 'mpo']  # acceptable image suffixes
vid_formats = ['mov', 'avi', 'mp4', 'mpg', 'mpeg', 'm4v', 'wmv', 'mkv', 'webm']  # acceptable video suffixes

def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):
    # Resize and pad image while meeting stride-multiple constraints
    shape = img.shape[:2]  # current shape [height, width]
    if isinstance(new_shape, int):
        new_shape = (new_shape, new_shape)

    # Scale ratio (new / old)
    r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
    if not scaleup:  # only scale down, do not scale up (for better test mAP)
        r = min(r, 1.0)

    # Compute padding
    ratio = r, r  # width, height ratios
    new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
    dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh padding
    if auto:  # minimum rectangle
        dw, dh = np.mod(dw, stride), np.mod(dh, stride)  # wh padding
    elif scaleFill:  # stretch
        dw, dh = 0.0, 0.0
        new_unpad = (new_shape[1], new_shape[0])
        ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]  # width, height ratios

    dw /= 2  # divide padding into 2 sides
    dh /= 2

    if shape[::-1] != new_unpad:  # resize
        img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
    top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
    left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
    img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)  # add border
    return img, ratio, (dw, dh)

class LoadImages:  # for inference
    def __init__(self, path, img_size=640, stride=32):
        p = str(Path(path).absolute())  # os-agnostic absolute path
        if '*' in p:
            files = sorted(glob.glob(p, recursive=True))  # glob
        elif os.path.isdir(p):
            files = sorted(glob.glob(os.path.join(p, '*.*')))  # dir
        elif os.path.isfile(p):
            files = [p]  # files
        else:
            raise Exception(f'ERROR: {p} does not exist')

        images = [x for x in files if x.split('.')[-1].lower() in img_formats]
        videos = [x for x in files if x.split('.')[-1].lower() in vid_formats]
        ni, nv = len(images), len(videos)

        self.img_size = img_size
        self.stride = stride
        self.files = images + videos
        self.nf = ni + nv  # number of files
        self.video_flag = [False] * ni + [True] * nv
        self.mode = 'image'
        if any(videos):
            self.new_video(videos[0])  # new video
        else:
            self.cap = None
        assert self.nf > 0, f'No images or videos found in {p}. ' \
                            f'Supported formats are:\nimages: {img_formats}\nvideos: {vid_formats}'

    def __iter__(self):
        self.count = 0
        return self

    def __next__(self):
        if self.count == self.nf:
            raise StopIteration
        path = self.files[self.count]

        if self.video_flag[self.count]:
            # Read video
            self.mode = 'video'
            ret_val, img0 = self.cap.read()
            if not ret_val:
                self.count += 1
                self.cap.release()
                if self.count == self.nf:  # last video
                    raise StopIteration
                else:
                    path = self.files[self.count]
                    self.new_video(path)
                    ret_val, img0 = self.cap.read()

            self.frame += 1
            # print(f'video {self.count + 1}/{self.nf} ({self.frame}/{self.nframes}) {path}: \n', end='')

        else:
            # Read image
            self.count += 1
            img0 = cv2.imread(path)  # BGR
            assert img0 is not None, 'Image Not Found ' + path
            #print(f'image {self.count}/{self.nf} {path}: ', end='')

        # Padded resize
        img = letterbox(img0, self.img_size, stride=self.stride)[0]

        # Convert
        img = img[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416
        img = np.ascontiguousarray(img)

        return path, img, img0, self.cap

    def new_video(self, path):
        self.frame = 0
        self.cap = cv2.VideoCapture(path)
        self.nframes = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))

    def __len__(self):
        return self.nf  # number of files

class LoadStreams:  # multiple IP or RTSP cameras
    def __init__(self, sources='streams.txt', img_size=640, stride=32):
        self.mode = 'stream'
        self.img_size = img_size
        self.stride = stride

        if os.path.isfile(sources):
            with open(sources, 'r') as f:
                sources = [x.strip() for x in f.read().strip().splitlines() if len(x.strip())]
        else:
            sources = [sources]

        n = len(sources)
        self.imgs = [None] * n
        self.sources = [clean_str(x) for x in sources]  # clean source names for later
        for i, s in enumerate(sources):
            # Start the thread to read frames from the video stream
            print(f'{i + 1}/{n}: {s}... ', end='')
            url = eval(s) if s.isnumeric() else s

            # Remove support for Youtube video

            cap = cv2.VideoCapture(url)
            assert cap.isOpened(), f'Failed to open {s}'
            w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
            h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
            self.fps = cap.get(cv2.CAP_PROP_FPS) % 100

            _, self.imgs[i] = cap.read()  # guarantee first frame
            thread = Thread(target=self.update, args=([i, cap]), daemon=True)
            print(f' success ({w}x{h} at {self.fps:.2f} FPS).')
            thread.start()
        print('')  # newline

        # check for common shapes
        s = np.stack([letterbox(x, self.img_size, stride=self.stride)[0].shape for x in self.imgs], 0)  # shapes
        self.rect = np.unique(s, axis=0).shape[0] == 1  # rect inference if all shapes equal
        if not self.rect:
            print('WARNING: Different stream shapes detected. For optimal performance supply similarly-shaped streams.')

    def update(self, index, cap):
        # Read next stream frame in a daemon thread
        n = 0
        while cap.isOpened():
            n += 1
            # _, self.imgs[index] = cap.read()
            cap.grab()
            if n == 4:  # read every 4th frame
                success, im = cap.retrieve()
                self.imgs[index] = im if success else self.imgs[index] * 0
                n = 0
            if self.fps != 0:
                time.sleep(1 / self.fps)  # wait time
            else:
                time.sleep(0.2)   # in rtsp situation self.fps may be zero. to avoid div by zero, take constant sleep.

    def __iter__(self):
        self.count = -1
        return self

    def __next__(self):
        self.count += 1
        img0 = self.imgs.copy()
        if cv2.waitKey(1) == ord('q'):  # q to quit
            cv2.destroyAllWindows()
            raise StopIteration

        # Letterbox
        img = [letterbox(x, self.img_size, auto=self.rect, stride=self.stride)[0] for x in img0]

        # Stack
        img = np.stack(img, 0)

        # Convert
        img = img[:, :, :, ::-1].transpose(0, 3, 1, 2)  # BGR to RGB, to bsx3x416x416
        img = np.ascontiguousarray(img)

        return self.sources, img, img0, None

    def __len__(self):
        return 0  # 1E12 frames = 32 streams at 30 FPS for 30 years

"""
From ultralytics.yolo.utils.check.py
"""
FILE = Path(__file__).resolve()
ROOT = FILE.parents[2]  # YOLO

def check_suffix(file='yolov8n.pt', suffix=('.pt',), msg=''):
    # Check file(s) for acceptable suffix
    if file and suffix:
        if isinstance(suffix, str):
            suffix = [suffix]
        for f in file if isinstance(file, (list, tuple)) else [file]:
            s = Path(f).suffix.lower()  # file suffix
            if len(s):
                assert s in suffix, f"{msg}{f} acceptable suffix is {suffix}"

def check_file(file, suffix=''):
    # Search/download file (if necessary) and return path
    check_suffix(file, suffix)  # optional
    file = str(file)  # convert to str()
    if Path(file).is_file() or not file:  # exists
        return file
    elif file.startswith(('http:/', 'https:/')):  # download
        url = file  # warning: Pathlib turns :// -> :/
        file = Path(urllib.parse.unquote(file).split('?')[0]).name  # '%2F' to '/', split https://url.com/file.txt?auth
        if Path(file).is_file():
            logging.info(f'Found {url} locally at {file}')  # file already exists
        else:
            logging.info(f'Downloading {url} to {file}...')
            torch.hub.download_url_to_file(url, file)
            assert Path(file).exists() and Path(file).stat().st_size > 0, f'File download failed: {url}'  # check
        return file
    else:  # search
        files = []
        for d in 'models', 'yolo/data':  # search directories
            files.extend(glob.glob(str(ROOT / d / '**' / file), recursive=True))  # find file
        if not files:
            raise FileNotFoundError(f"'{file}' does not exist")
        elif len(files) > 1:
            raise FileNotFoundError(f"Multiple files match '{file}', specify exact path: {files}")
        return files[0]  # return file

"""
From ultralytics.yolo.utils.check.py
"""

def is_ascii(s) -> bool:
    """
    Check if a string is composed of only ASCII characters.

    Args:
        s (str): String to be checked.

    Returns:
        bool: True if the string is composed only of ASCII characters, False otherwise.
    """
    # Convert list, tuple, None, etc. to string
    s = str(s)

    # Check if the string is composed of only ASCII characters
    return all(ord(c) < 128 for c in s)

"""
From ultralytics.yolo.utils.plotting.py
"""

def scale_image(im1_shape, masks, im0_shape, ratio_pad=None):
    """
    Takes a mask, and resizes it to the original image size

    Args:
      im1_shape (tuple): model input shape, [h, w]
      masks (torch.Tensor): [h, w, num]
      im0_shape (tuple): the original image shape
      ratio_pad (tuple): the ratio of the padding to the original image.

    Returns:
      masks (torch.Tensor): The masks that are being returned.
    """
    # Rescale coordinates (xyxy) from im1_shape to im0_shape
    if ratio_pad is None:  # calculate from im0_shape
        gain = min(im1_shape[0] / im0_shape[0], im1_shape[1] / im0_shape[1])  # gain  = old / new
        pad = (im1_shape[1] - im0_shape[1] * gain) / 2, (im1_shape[0] - im0_shape[0] * gain) / 2  # wh padding
    else:
        pad = ratio_pad[1]
    top, left = int(pad[1]), int(pad[0])  # y, x
    bottom, right = int(im1_shape[0] - pad[1]), int(im1_shape[1] - pad[0])

    if len(masks.shape) < 2:
        raise ValueError(f'"len of masks shape" should be 2 or 3, but got {len(masks.shape)}')
    masks = masks[top:bottom, left:right]
    # masks = masks.permute(2, 0, 1).contiguous()
    # masks = F.interpolate(masks[None], im0_shape[:2], mode='bilinear', align_corners=False)[0]
    # masks = masks.permute(1, 2, 0).contiguous()
    masks = cv2.resize(masks, (im0_shape[1], im0_shape[0]))

    if len(masks.shape) == 2:
        masks = masks[:, :, None]
    return masks

class Annotator:
    # YOLOv8 Annotator for train/val mosaics and jpgs and detect/hub inference annotations
    def __init__(self, im, line_width=None, font_size=None, font='Arial.ttf', pil=False, example='abc'):
        assert im.data.contiguous, 'Image not contiguous. Apply np.ascontiguousarray(im) to Annotator() input images.'
        non_ascii = not is_ascii(example)  # non-latin labels, i.e. asian, arabic, cyrillic
        self.pil = pil or non_ascii
        if self.pil:  # use PIL
            self.im = im if isinstance(im, Image.Image) else Image.fromarray(im)
            self.draw = ImageDraw.Draw(self.im)
            self.font = ImageFont.load_default()  # For simplicity and Performance
        else:  # use cv2
            self.im = im
        self.lw = line_width or max(round(sum(im.shape) / 2 * 0.003), 2)  # line width

    def box_label(self, box, label='', color=(128, 128, 128), txt_color=(255, 255, 255)):
        # Add one xyxy box to image with label
        if self.pil or not is_ascii(label):
            self.draw.rectangle(box, width=self.lw, outline=color)  # box
            if label:
                w, h = self.font.getsize(label)  # text width, height (WARNING: deprecated) in 9.2.0
                # _, _, w, h = self.font.getbbox(label)  # text width, height (New)
                outside = box[1] - h >= 0  # label fits outside box
                self.draw.rectangle(
                    (box[0], box[1] - h if outside else box[1], box[0] + w + 1,
                     box[1] + 1 if outside else box[1] + h + 1),
                    fill=color,
                )
                # self.draw.text((box[0], box[1]), label, fill=txt_color, font=self.font, anchor='ls')  # for PIL>8.0
                self.draw.text((box[0], box[1] - h if outside else box[1]), label, fill=txt_color, font=self.font)
        else:  # cv2
            p1, p2 = (int(box[0]), int(box[1])), (int(box[2]), int(box[3]))
            cv2.rectangle(self.im, p1, p2, color, thickness=self.lw, lineType=cv2.LINE_AA)
            if label:
                tf = max(self.lw - 1, 1)  # font thickness
                w, h = cv2.getTextSize(label, 0, fontScale=self.lw / 3, thickness=tf)[0]  # text width, height
                outside = p1[1] - h >= 3
                p2 = p1[0] + w, p1[1] - h - 3 if outside else p1[1] + h + 3
                cv2.rectangle(self.im, p1, p2, color, -1, cv2.LINE_AA)  # filled
                cv2.putText(self.im,
                            label, (p1[0], p1[1] - 2 if outside else p1[1] + h + 2),
                            0,
                            self.lw / 3,
                            txt_color,
                            thickness=tf,
                            lineType=cv2.LINE_AA)

    def masks(self, masks, colors, im_gpu, alpha=0.5, retina_masks=False):
        """Plot masks at once.
        Args:
            masks (tensor): predicted masks on cuda, shape: [n, h, w]
            colors (List[List[Int]]): colors for predicted masks, [[r, g, b] * n]
            im_gpu (tensor): img is in cuda, shape: [3, h, w], range: [0, 1]
            alpha (float): mask transparency: 0.0 fully transparent, 1.0 opaque
        """
        if self.pil:
            # convert to numpy first
            self.im = np.asarray(self.im).copy()
        if len(masks) == 0:
            self.im[:] = im_gpu.permute(1, 2, 0).contiguous().cpu().numpy() * 255
        colors = torch.tensor(colors, device=im_gpu.device, dtype=torch.float32) / 255.0
        colors = colors[:, None, None]  # shape(n,1,1,3)
        masks = masks.unsqueeze(3)  # shape(n,h,w,1)
        masks_color = masks * (colors * alpha)  # shape(n,h,w,3)

        inv_alph_masks = (1 - masks * alpha).cumprod(0)  # shape(n,h,w,1)
        mcs = (masks_color * inv_alph_masks).sum(0) * 2  # mask color summand shape(n,h,w,3)

        im_gpu = im_gpu.flip(dims=[0])  # flip channel
        im_gpu = im_gpu.permute(1, 2, 0).contiguous()  # shape(h,w,3)
        im_gpu = im_gpu * inv_alph_masks[-1] + mcs
        im_mask = (im_gpu * 255)
        im_mask_np = im_mask.byte().cpu().numpy()
        self.im[:] = im_mask_np if retina_masks else scale_image(im_gpu.shape, im_mask_np, self.im.shape)
        if self.pil:
            # convert im back to PIL and update draw
            self.fromarray(self.im)

    def rectangle(self, xy, fill=None, outline=None, width=1):
        # Add rectangle to image (PIL-only)
        self.draw.rectangle(xy, fill, outline, width)

    def text(self, xy, text, txt_color=(255, 255, 255), anchor='top'):
        # Add text to image (PIL-only)
        if anchor == 'bottom':  # start y from font bottom
            w, h = self.font.getsize(text)  # text width, height
            xy[1] += 1 - h
        self.draw.text(xy, text, fill=txt_color, font=self.font)

    def fromarray(self, im):
        # Update self.im from a numpy array
        self.im = im if isinstance(im, Image.Image) else Image.fromarray(im)
        self.draw = ImageDraw.Draw(self.im)

    def result(self):
        # Return annotated image as array
        return np.asarray(self.im)

class Colors:
    # Ultralytics color palette https://ultralytics.com/
    def __init__(self):
        # hex = matplotlib.colors.TABLEAU_COLORS.values()
        hexs = ('FF3838', 'FF9D97', 'FF701F', 'FFB21D', 'CFD231', '48F90A', '92CC17', '3DDB86', '1A9334', '00D4BB',
                '2C99A8', '00C2FF', '344593', '6473FF', '0018EC', '8438FF', '520085', 'CB38FF', 'FF95C8', 'FF37C7')
        self.palette = [self.hex2rgb(f'#{c}') for c in hexs]
        self.n = len(self.palette)

    def __call__(self, i, bgr=False):
        c = self.palette[int(i) % self.n]
        return (c[2], c[1], c[0]) if bgr else c

    @staticmethod
    def hex2rgb(h):  # rgb order (PIL)
        return tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))

colors = Colors()  # create instance for 'from utils.plots import colors'


from django.shortcuts import render, redirect
from kajaki_app.models import Route, Kayak, Order, OrderKayak
from django.urls import reverse, reverse_lazy
from datetime import date
from django.views import View
from kajaki_app.forms import AddKayakForm, AddRouteForm, ContactForm
from django.views.generic import ListView, CreateView, UpdateView, DetailView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin, PermissionRequiredMixin

class AddRouteView(View):
    # permission_required = ['kajaki_app.add_route']

    def get(self, request):
        form = AddRouteForm()
        return render(request, 'kajaki_app/add_route.html', {'form': form, 'submit_value_text': 'Dodaj'})

    def post(self, request):
        form = AddRouteForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect(reverse('add_route'))
        return render(request, 'kajaki_app/add_route.html', {'form': form, 'submit_value_text': 'Dodaj'})

class RouteListView(ListView):
    model = Route
    template_name = 'kajaki_app/route_list.html'

class AddKayakView(View):
    # permission_required = ['kajaki_app.add_kayak']

    def get(self, request):
        form = AddKayakForm()
        return render(request, 'kajaki_app/add_kayak.html', {'form': form, 'submit_value_text': 'Dodaj'})

    def post(self, request):
        form = AddKayakForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect(reverse('add_kayak'))
        return render(request, 'kajaki_app/add_kayak.html', {'form': form, 'submit_value_text': 'Dodaj'})

class KayakListView(ListView):
    model = Kayak
    template_name = 'kajaki_app/kayak_list.html'

class KayakUpdateView(LoginRequiredMixin, UpdateView):
    # permission_required = ['filmy.change_film']

    model = Kayak
    template_name = 'kajaki_app/add_kayak.html'
    fields = '__all__'

    def get_success_url(self):
        super().get_success_url()
        return reverse("add_kayak", args=(self.object.id,))

class KayakDeleteView(LoginRequiredMixin, DeleteView):
    model = Kayak
    template_name = 'kajaki_app/kayak_delete.html'
    success_url = reverse_lazy('kayak_list')

class KayakDetailView(DetailView):
    model = Kayak
    template_name = 'kajaki_app/details_kayak.html'

class CheckoutView(View):
    def get(self, request):
        return render(request, 'kajaki_app/checkout.html')

    def post(self, request):
        name = request.POST.get('name', '')
        email = request.POST.get('email', '')
        date = request.POST.get('date', '')
        phone = request.POST.get('phone', '')

        return render(request, 'kajaki_app/checkout.html')

class OrderView(LoginRequiredMixin, View):
    def get(self, request):
        routes = Route.objects.all()
        kayaks = Kayak.objects.all()
        return render(request, 'kajaki_app/order.html', {'kayaks': kayaks, 'routes': routes})

    def post(self, request):
        user = request.user
        route = request.POST.get('route')
        date = request.POST.get('date')
        kayak = request.POST.get('kayak')
        amount = request.POST.get('amount')
        if route and date and int(amount) >= 1 and kayak:
            route = Route.objects.get(name=route)
            order = Order.objects.create(route=route, buyer=user, date=date)
            kayak = Kayak.objects.get(name=kayak)
            order_kayak = OrderKayak.objects.create(kayak=kayak, order=order, amount=amount)
            return redirect(reverse('my_account'))
        return render(request, 'kajaki_app/order.html', {'message': 'Wypełnij poprawnie wszystkie pola'})

class ContactView(View):
    def get(self, request):
        form = ContactForm()
        return render(request, 'kajaki_app/contact.html', {'form': form, 'submit_value_text': 'Wyślij'})

    def post(self, request):
        form = ContactForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect(reverse('index'))
        return render(request, 'kajaki_app/contact.html', {'form': form, 'submit_value_text': 'Wyślij'})

class AboutUsView(View):
    def get(self, request):
        return render(request, 'kajaki_app/about_us.html')


## Program name: sort_fruits.py
## UoPeople CS-1101 December 2015
## Unit 7
## Roger Stillick Jr.
## The purpose of this program is to read a file containing a list of
## fruits, then sort the list and write the sorted list to a new file.

# Set the working directory of the path
wd = '/home/roger/bin/'

#courtesy prompt
print ('Program started')

# open the files to read and write
infile = open(wd + 'unsorted_fruits.txt', 'r')
outfile = open(wd + 'sorted_fruits.txt', 'w')

# the readlines() method is commented out, because I wanted to try
# a different way to accomplish the same task.  It works, so I left it.
#fruit = infile.readlines()
fruit = list(infile)

#sort the list
fruit.sort()

# iterate over the list "fruit", and write the line to "outfile" if the
# line is not blank. The instructors YouTube example was helpful in
# figuring out how to remove the blank lines.
for line in fruit:
    if line > "/n":
        outfile.write(line)

# don't forget to close the file operations!
infile.close()
outfile.close()

#courtesy prompt
print('''Operation successful, look for the "sorted_fruits.txt" file in
your working directory.''')


import sys, os
import subprocess
import datetime as dt
from random import randint
import argparse
import web3
from web3 import Web3
from web3.middleware import geth_poa_middleware
from eth_utils import decode_hex

# Project modules
import utils
from TextColor.color import bcolors

URL = "http://127.0.0.1:8545"
ACCOUNT_DB_NAME = 'car.json'
MGMT_CONTRACT_DB_NAME = utils.MGMT_CONTRACT_DB_NAME
MGMT_CONTRACT_SRC_PATH = utils.MGMT_CONTRACT_SRC_PATH
CONFIG = utils.open_data_base(ACCOUNT_DB_NAME)
DATABASE = utils.open_data_base(MGMT_CONTRACT_DB_NAME)

if DATABASE is None:
    sys.exit(f"{bcolors.FAIL}Setup hasn't been done{bcolors.ENDC}")

def generate_private_key(_w3: Web3) -> str:
    """
    Generate private key for car account using current time and random int

    :param Web3 _w3: Web3 instance
    :return: Private Key
    :rtype: str
    """

    t = int(dt.datetime.utcnow().timestamp())
    k = randint(0, 2 ** 16)
    privateKey = _w3.toHex(_w3.sha3(((t + k).to_bytes(32, 'big'))))
    if privateKey[:2] == '0x':
        privateKey = privateKey[2:]

    return (privateKey)

def new_car_account(_w3: Web3) -> None:
    """
    Create new addres for car account

    :param Web3 _w3: Web3 instance
    """

    privateKey = generate_private_key(_w3)
    data = {"key": privateKey}
    utils.write_data_base(data, ACCOUNT_DB_NAME)
    print(f"{bcolors.HEADER}{_w3.eth.account.privateKeyToAccount(data['key']).address}{bcolors.ENDC}")

def get_car_account_from_db(_w3: Web3) -> None:
    """
    Get car account from database

    :param Web3 _w3: Web3 instance
    """

    return (_w3.eth.account.privateKeyToAccount(utils.get_data_from_db(ACCOUNT_DB_NAME, 'key')).address)

def register_car(_w3: Web3):
    """
    Register new car

    :param Web3 _w3: Web3 instance
    """

    data = utils.open_data_base(MGMT_CONTRACT_DB_NAME)

    if data is None:
        return f'{bcolors.FAIL}Cannot access management contract database{bcolors.ENDC}'

    data = CONFIG

    if data is None:
        return f'{bcolors.FAIL}Cannot access account database{bcolors.ENDC}'

    private_key = data['key']
    mgmt_contract = utils.init_management_contract(_w3)
    car_address = _w3.eth.account.privateKeyToAccount(private_key).address
    registration_required_gas = 50000
    gas_price = utils.get_actual_gas_price(_w3)

    if registration_required_gas * gas_price > _w3.eth.getBalance(car_address):
        return 'No enough funds to send transaction'

    nonce = _w3.eth.getTransactionCount(car_address)
    tx = {'gasPrice': gas_price, 'nonce': nonce}

    regTx = mgmt_contract.functions.registerCar().buildTransaction(tx)
    signTx = _w3.eth.account.signTransaction(regTx, private_key)
    txHash = _w3.eth.sendRawTransaction(signTx.rawTransaction)
    receipt = web3.eth.wait_for_transaction_receipt(_w3, txHash, 120, 0.1)

    if receipt.status == 1:
        return f'{bcolors.OKGREEN}Registered successfully{bcolors.ENDC}'
    else:
        return f'{bcolors.FAIL}Car registration failed{bcolors.ENDC}'

def create_parser() -> argparse.ArgumentParser:
    """
    Create cli argument parser

    :return: Parser
    :rtype: argparse.ArgumentParser
    """

    parser = argparse.ArgumentParser(
        description='Car management tool',
        epilog="""
                  It is expected that Web3 provider specified by WEB3_PROVIDER_URI
                  environment variable. E.g.
                  WEB3_PROVIDER_URI=file:///path/to/node/rpc-json/file.ipc
                  WEB3_PROVIDER_URI=http://192.168.1.2:8545
               """
    )

    parser.add_argument(
        '--new', action='store_true', required=False,
        help='Generate a new account for the particular AGV'
    )

    parser.add_argument(
        '--account', action='store_true', required=False,
        help='Get identificator (Ethereum address) of AGV from the private key stored in car.json'
    )

    parser.add_argument(
        '--reg', action='store_true', required=False,
        help='Register the vehicle in the chain'
    )

    parser.add_argument(
        '--verify', type=str, required=False,
        help='Verify battery'
    )

    parser.add_argument(
        '--initiate_replacement', nargs=2, required=False,
        help='Initiate deal <car_battery> <sc_battery>'
    )

    return parser

def ask_for_replacement(car_battery_id: str, sc_battery_id: str, car_address: str) -> None:
    """
    Ask service center for replacement approval

    :param str car_battery_id: Car's battery
    :param str sc_battery_id: Service center's battery
    :param str car_address: Car's blockchain address
    :return: Nothing
    :rtype: None
    """

    if os.path.exists(f"scenter.py"):
        subprocess.run(
            [
                "python",
                "scenter.py",
                "--approve_replacement",
                f"{car_battery_id}",
                f"{sc_battery_id}",
                f"{car_address}",
            ]
        )
    else:
        sys.exit(f"{bcolors.FAIL}The asked service center does not exists{bcolors.ENDC}")

def get_sc_address() -> str:
    """
    Get address of the service center

    return: Service center's address
    rtype: str
    """

    command = "python scenter.py --get_address".split(' ')
    result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

    return result.stdout[:-1]

def transfer_battery_to_sc(w3: Web3, car_battery_id: str, sc_address: str):
    """
    Transfer battery to service center

    :param Web3 w3: Web3 instance
    :param str car_battery_id: Car's battery id
    :param str sc_battery_id: Service centers's battery id

    return: Nothing
    rtype: None
    """

    data = utils.open_data_base(MGMT_CONTRACT_DB_NAME)

    if data is None:
        return 'Cannot access management contract database'

    data = utils.open_data_base(ACCOUNT_DB_NAME)

    if data is None:
        return 'Cannot access account database'

    private_key = data['key']
    battery_mgmt_contract_addr = utils.get_battery_managment_contract_addr(w3)
    battery_mgmt_contract = utils.init_battery_management_contract(w3, battery_mgmt_contract_addr)
    car_address = w3.eth.account.privateKeyToAccount(private_key).address
    gas_price = utils.get_actual_gas_price(w3)

    nonce = w3.eth.getTransactionCount(car_address)
    tx = {'gasPrice': gas_price, 'nonce': nonce, 'gas': 2204 * 68 + 21000}

    reg_tx = battery_mgmt_contract.functions.transfer(sc_address, decode_hex(car_battery_id)).buildTransaction(tx)
    sign_tx = w3.eth.account.signTransaction(reg_tx, private_key)
    tx_hash = w3.eth.sendRawTransaction(sign_tx.rawTransaction)
    receipt = web3.eth.wait_for_transaction_receipt(w3, tx_hash, 120, 0.1)

    if receipt.status != 1:
        sys.exit(f"{bcolors.FAIL}The car does not own this battery!{bcolors.ENDC}")

def get_new_battery(car_account: str, car_battery_id: str, sc_battery_id) -> float:
    """
    Call battery replacement in service center

    :param str car_account: Car account
    :param str car_battery_id: Car's battery id
    :return: Work's cost
    :rtype: float
    """

    command = f"python scenter.py --transfer_battery_to_car {car_account} {car_battery_id} {sc_battery_id}".split(' ')
    result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

    return float(result.stdout[:-1])

def initiate_replacement(w3: Web3, car_battery_id: str, sc_battery_id: str) -> None:
    """
    Initiate battery replacement

    :param Web3 w3: Web3 instance
    :param str car_battery_id: Car's battery
    :param str sc_battery_id: Service center's battery
    :return: Nothing
    :rtype: None
    """

    sc_battery_id_path = f"firmware/{car_battery_id[:8]}.py"
    car_battery_id_path = f"firmware/{sc_battery_id[:8]}.py"

    print("Verifying battery...")

    data = utils.verify_battery(w3, sc_battery_id_path)

    if not data[0]:
        sys.exit(f"{bcolors.FAIL}The battery is fake{bcolors.ENDC}")

    sys.stdout.write("\033[F") #back to previous line
    sys.stdout.write("\033[K") #clear line

    print(f"Verifying battery...{bcolors.OKGREEN}Success{bcolors.ENDC}", u'\u2713')

    print("Asking service center for replacement...")

    ask_for_replacement(car_battery_id, sc_battery_id, get_car_account_from_db(w3))

    message = utils.open_data_base('replacement.json')

    if message is None:
        sys.exit(f"{bcolors.FAIL}Somethong went wrong...{bcolors.ENDC}")

    if not message['approved']:
        sys.exit(message['error'])

    sys.stdout.write("\033[F") #back to previous line
    sys.stdout.write("\033[K") #clear line

    print(f"Asking service center for replacement...{bcolors.OKGREEN}Approved{bcolors.ENDC}", u'\u2713')

    print("Getting address of the service center...")

    sc_address = get_sc_address()

    sys.stdout.write("\033[F") #back to previous line
    sys.stdout.write("\033[K") #clear line

    print(f"Getting address of the service center...{bcolors.OKGREEN}Success{bcolors.ENDC}", u'\u2713')

    print("Transferring battery to the service center...")

    transfer_battery_to_sc(w3, car_battery_id, sc_address)

    sys.stdout.write("\033[F") #back to previous line
    sys.stdout.write("\033[K") #clear line

    print(f"Transferring battery to the service center...{bcolors.OKGREEN}Success{bcolors.ENDC}", u'\u2713')

    print("Waiting for new battery installation...")

    result = get_new_battery(get_car_account_from_db(w3), car_battery_id, sc_battery_id)

    sys.stdout.write("\033[F") #back to previous line
    sys.stdout.write("\033[K") #clear line

    print(f"Battery was installed...{bcolors.OKGREEN}Success{bcolors.ENDC}", u'\u2713')

    return result

def main():
    w3 = Web3(Web3.HTTPProvider(URL))

    # configure provider to work with PoA chains
    w3.middleware_onion.inject(geth_poa_middleware, layer=0)

    parser = create_parser()
    args = parser.parse_args()

    if args.new:
        new_car_account(w3)

    elif args.account:
        print(get_car_account_from_db(w3))

    elif args.reg:
        print(register_car(w3))

    elif args.verify:
        data = utils.verify_battery(w3, args.verify)
        print(f"Verified: {data[0]}")
        print(f"Total charges: {data[1]}")
        print(f"Vendor id: {data[2]}")
        print(f"Vendor name: {data[3]}")

    elif args.initiate_replacement:
        cost = initiate_replacement(w3, args.initiate_replacement[0], args.initiate_replacement[1])
        print(f"Cost of work: {cost} eth")

if __name__ == "__main__":
    main()


#Why the fuck am i doing this shit
t = int(input())
while(t > 0):
	n = int(input())
	see = map(int,input().split(" "))
	mods = [0,0,0]
	for i in see:
		mods[i%3] +=1
	ans = min(mods[1],mods[2])
	mods[1] -= ans
	mods[2] -= ans
	#print(mods[1])
	#print(mods[2])
	print(ans + mods[0] +mods[2]//3 + mods[1]//3)
	t-=1


import sys
import typing as t
import importlib
from pathlib import Path

import pkg_resources

from starwhale.utils import console
from starwhale.utils.venv import (
    guess_current_py_env,
    get_user_python_sys_paths,
    check_python_interpreter_consistency,
)

def import_object(
    workdir: t.Union[Path, str], handler_path: str, py_env: str = ""
) -> t.Any:
    workdir_path = str(Path(workdir).absolute())
    external_paths = [workdir_path]
    py_env = py_env or guess_current_py_env()
    _ok, _cur_py, _ex_py = check_python_interpreter_consistency(py_env)
    if not _ok:
        console.print(
            f":speaking_head: [red]swcli python prefix:{_cur_py}, runtime env python prefix:{_ex_py}[/], swcli will inject sys.path"
        )
        external_paths.extend(get_user_python_sys_paths(py_env))

    prev_paths = sys.path[:]
    sys_changed = False

    for _path in external_paths[::-1]:
        if _path not in sys.path:
            sys.path.insert(0, _path)
            pkg_resources.working_set.add_entry(_path)
            sys_changed = True

    try:
        module_name, handler_name = handler_path.split(":", 1)
        console.print(
            f":speaking_head: [green]import module:{module_name}, handler:{handler_name}[/]"
        )
        _module = importlib.import_module(module_name, package=workdir_path)
        _obj = getattr(_module, handler_name, None)

        if not _obj:
            raise ModuleNotFoundError(f"{handler_path}")
    except Exception:
        console.print_exception()
        if sys_changed:
            sys.path[:] = prev_paths
        raise

    return _obj

def load_module(module: str, path: Path) -> t.Any:
    workdir_path = str(path.absolute())

    external_paths = [workdir_path]
    for _path in external_paths[::-1]:
        if _path not in sys.path:
            sys.path.insert(0, _path)
            pkg_resources.working_set.add_entry(_path)

    return importlib.import_module(module, package=workdir_path)


from __future__ import absolute_import
import logging
import string
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
import re

# py2 vs py3 transition
from ..six import text_type as unicode
from ..six import string_types as basestring
from ..six import ensure_binary
from io import BytesIO

## XML isn't as forgiving as HTML, so rather than generate as strings,
## use DOM to generate the XML files.
from xml.dom.minidom import getDOMImplementation

import bs4

from .base_writer import BaseStoryWriter
from ..htmlcleanup import stripHTML,removeEntities
from ..story import commaGroups

logger = logging.getLogger(__name__)

class EpubWriter(BaseStoryWriter):

    @staticmethod
    def getFormatName():
        return 'epub'

    @staticmethod
    def getFormatExt():
        return '.epub'

    def __init__(self, config, story):
        BaseStoryWriter.__init__(self, config, story)

        self.EPUB_CSS = string.Template('''${output_css}''')

        self.EPUB_TITLE_PAGE_START = string.Template('''<?xml version="1.0" encoding="UTF-8"?>

<title>${title} by ${author}</title>
<link href="stylesheet.css" type="text/css" rel="stylesheet"/>

${title} by ${authorHTML}

''')

        self.EPUB_TITLE_ENTRY = string.Template('''
${label}: ${value}

''')

        self.EPUB_NO_TITLE_ENTRY = string.Template('''
${value}

''')

        self.EPUB_TITLE_PAGE_END = string.Template('''

''')

        self.EPUB_TABLE_TITLE_PAGE_START = string.Template('''<?xml version="1.0" encoding="UTF-8"?>

<title>${title} by ${author}</title>
<link href="stylesheet.css" type="text/css" rel="stylesheet"/>

${title} by ${authorHTML}

''')

        self.EPUB_TABLE_TITLE_ENTRY = string.Template('''

${label}:	${value}

''')

        self.EPUB_TABLE_TITLE_WIDE_ENTRY = string.Template('''

${label}: ${value}

''')

        self.EPUB_TABLE_NO_TITLE_ENTRY = string.Template('''

${label}${value}

''')

        self.EPUB_TABLE_TITLE_PAGE_END = string.Template('''

''')

        self.EPUB_TOC_PAGE_START = string.Template('''<?xml version="1.0" encoding="UTF-8"?>

<title>${title} by ${author}</title>
<link href="stylesheet.css" type="text/css" rel="stylesheet"/>

Table of Contents

''')

        self.EPUB_TOC_ENTRY = string.Template('''
${chapter}

''')

        self.EPUB_TOC_PAGE_END = string.Template('''

''')

        self.EPUB_CHAPTER_START = string.Template('''<?xml version="1.0" encoding="UTF-8"?>

<title>${chapter}</title>
<link href="stylesheet.css" type="text/css" rel="stylesheet"/>
<meta name="chapterurl" content="${url}" />
<meta name="chapterorigtitle" content="${origchapter}" />
<meta name="chaptertoctitle" content="${tocchapter}" />
<meta name="chaptertitle" content="${chapter}" />

${chapter}

''')

        self.EPUB_CHAPTER_END = string.Template('''

''')

        self.EPUB_LOG_PAGE_START = string.Template('''<?xml version="1.0" encoding="UTF-8"?>

<title>Update Log</title>
<link href="stylesheet.css" type="text/css" rel="stylesheet"/>

Update Log

''')

        self.EPUB_LOG_UPDATE_START = string.Template('''

''')

        self.EPUB_LOG_ENTRY = string.Template('''
${label}: ${value}
''')

        self.EPUB_LOG_UPDATE_END = string.Template('''

''')

        self.EPUB_LOG_PAGE_END = string.Template('''

''')

        self.EPUB_LOG_PAGE_END = string.Template('''

''')

        self.EPUB_COVER = string.Template('''
<title>Cover</title>

''')

    def writeLogPage(self, out):
        """
        Write the log page, but only include entries that there's
        metadata for.  START, ENTRY and END are expected to already be
        string.Template().  START and END are expected to use the same
        names as Story.metadata, but ENTRY should use id, label and value.
        """
        if self.hasConfig("logpage_start"):
            START = string.Template(self.getConfig("logpage_start"))
        else:
            START = self.EPUB_LOG_PAGE_START

        if self.hasConfig("logpage_end"):
            END = string.Template(self.getConfig("logpage_end"))
        else:
            END = self.EPUB_LOG_PAGE_END

        # if there's a self.story.logfile, there's an existing log
        # to add to.
        if self.story.logfile:
            logger.debug("existing logfile found, appending")
            # logger.debug("existing data:%s"%self._getLastLogData(self.story.logfile))
            replace_string = "" # "
"
            self._write(out,self.story.logfile.replace(replace_string,self._makeLogEntry(self._getLastLogData(self.story.logfile))+replace_string))
        else:
            # otherwise, write a new one.
            self._write(out,START.substitute(self.story.getAllMetadata()))
            self._write(out,self._makeLogEntry())
            self._write(out,END.substitute(self.story.getAllMetadata()))

    # self parsing instead of Soup because it should be simple and not
    # worth the overhead.
    def _getLastLogData(self,logfile):
        """
        Make a dict() of the most recent(last) log entry for each piece of metadata.
        Switch rindex to index to search from top instead of bottom.
        """
        values = {}
        for entry in self.getConfigList("logpage_entries") + self.getConfigList("extra_logpage_entries"):
            try:
                # 1975-04-15
                span = ''%entry
                idx = logfile.rindex(span)+len(span)
                values[entry] = logfile[idx:logfile.index('\n',idx)]
            except Exception as e:
                #print("e:%s"%e)
                pass

        return values

    def _makeLogEntry(self, oldvalues={}):
        if self.hasConfig("logpage_update_start"):
            START = string.Template(self.getConfig("logpage_update_start"))
        else:
            START = self.EPUB_LOG_UPDATE_START

        if self.hasConfig("logpage_entry"):
            ENTRY = string.Template(self.getConfig("logpage_entry"))
        else:
            ENTRY = self.EPUB_LOG_ENTRY

        if self.hasConfig("logpage_update_end"):
            END = string.Template(self.getConfig("logpage_update_end"))
        else:
            END = self.EPUB_LOG_UPDATE_END

        retval = START.substitute(self.story.getAllMetadata())

        ## words_added is only used in logpage because it's the only
        ## place we know the previous version's word count.
        if 'words_added' in (self.getConfigList("logpage_entries") + self.getConfigList("extra_logpage_entries")):
            new_words = self.story.getMetadata('numWords')
            old_words = oldvalues.get('numWords',None)
            if new_words and old_words:
                self.story.setMetadata('words_added',commaGroups(unicode(int(new_words.replace(',',''))-int(old_words.replace(',','')))))

        for entry in self.getConfigList("logpage_entries") + self.getConfigList("extra_logpage_entries"):
            if self.isValidMetaEntry(entry):
                val = self.story.getMetadata(entry)
                if val and ( entry not in oldvalues or val != oldvalues[entry] ):
                    label=self.get_label(entry)
                    # if self.hasConfig(entry+"_label"):
                    #     label=self.getConfig(entry+"_label")
                    # elif entry in self.titleLabels:
                    #     logger.debug("Using fallback label for %s_label"%entry)
                    #     label=self.titleLabels[entry]
                    # else:
                    #     label="%s"%entry.title()
                    #     logger.debug("No known label for %s, fallback to '%s'"%(entry,label))

                    retval = retval + ENTRY.substitute({'id':entry,
                                                        'label':label,
                                                        'value':val})
            else:
                # could be useful for introducing extra text, but
                # mostly it makes it easy to tell when you get the
                # keyword wrong.
                retval = retval + entry

        retval = retval + END.substitute(self.story.getAllMetadata())

        if self.getConfig('replace_hr'):
            # replacing a self-closing tag with a container tag in the
            # soup is more difficult than it first appears.  So cheat.
            retval = re.sub("
]*>","
* * *
",retval)

        return retval

    def writeStoryImpl(self, out):

        if self.story.oldcover and \
                ( (self.getConfig('use_old_cover') and
                   self.story.getMetadata('cover_image') != 'force' ) or not self.story.cover ):
            # logger.debug("use_old_cover:%s"%self.getConfig('use_old_cover'))
            self.use_oldcover = True
            self.story.setMetadata('cover_image','old')
        else:
            self.use_oldcover = False

        ## Python 2.5 ZipFile is rather more primative than later
        ## versions.  It can operate on a file, or on a BytesIO, but
        ## not on an open stream.  OTOH, I suspect we would have had
        ## problems with closing and opening again to change the
        ## compression type anyway.
        zipio = BytesIO()

        ## mimetype must be first file and uncompressed.  Python 2.5
        ## ZipFile can't change compression type file-by-file, so we
        ## have to close and re-open
        outputepub = ZipFile(zipio, 'w', compression=ZIP_STORED)
        outputepub.debug=3
        outputepub.writestr('mimetype','application/epub+zip')
        outputepub.close()

        ## Re-open file for content.
        outputepub = ZipFile(zipio, 'a', compression=ZIP_DEFLATED)
        outputepub.debug=3

        ## Create META-INF/container.xml file.  The only thing it does is
        ## point to content.opf
        containerdom = getDOMImplementation().createDocument(None, "container", None)
        containertop = containerdom.documentElement
        containertop.setAttribute("version","1.0")
        containertop.setAttribute("xmlns","urn:oasis:names:tc:opendocument:xmlns:container")
        rootfiles = containerdom.createElement("rootfiles")
        containertop.appendChild(rootfiles)
        rootfiles.appendChild(newTag(containerdom,"rootfile",{"full-path":"content.opf",
                                                              "media-type":"application/oebps-package+xml"}))
        outputepub.writestr("META-INF/container.xml",containerdom.toxml(encoding='utf-8'))
        containerdom.unlink()
        del containerdom

        ## Epub has two metadata files with real data.  We're putting
        ## them in content.opf (pointed to by META-INF/container.xml)
        ## and toc.ncx (pointed to by content.opf)

        ## content.opf contains metadata, a 'manifest' list of all
        ## other included files, and another 'spine' list of the items in the
        ## file

        uniqueid= 'fanficfare-uid:%s-u%s-s%s' % (
            self.getMetadata('site'),
            self.story.getList('authorId')[0],
            self.getMetadata('storyId'))

        contentdom = getDOMImplementation().createDocument(None, "package", None)
        package = contentdom.documentElement
        ## might want 3.1 or something in future.
        epub3 = self.getConfig("epub_version",default="2.0").startswith("3")
        if epub3:
            package.setAttribute("version","3.0")
        else:
            package.setAttribute("version","2.0")
        logger.info("Saving EPUB Version "+package.getAttribute("version"))
        package.setAttribute("xmlns","http://www.idpf.org/2007/opf")
        package.setAttribute("unique-identifier","fanficfare-uid")
        metadata=newTag(contentdom,"metadata",
                        attrs={"xmlns:dc":"http://purl.org/dc/elements/1.1/",
                               "xmlns:opf":"http://www.idpf.org/2007/opf"})
        package.appendChild(metadata)

        metadata.appendChild(newTag(contentdom,"dc:identifier",
                                    text=uniqueid,
                                    attrs={"id":"fanficfare-uid"}))

        if self.getMetadata('title'):
            metadata.appendChild(newTag(contentdom,"dc:title",text=self.getMetadata('title'),
                                        attrs={"id":"id"}))

        def creator_attrs(idnum):
            if epub3:
                return {"id":"id-%d"%idnum}
            else:
                return {"opf:role":"aut"}
        idnum = 1
        if self.getMetadata('author'):
            if self.story.isList('author'):
                for auth in self.story.getList('author'):
                    metadata.appendChild(newTag(contentdom,"dc:creator",
                                                attrs=creator_attrs(idnum),
                                                text=auth))
                    idnum += 1
            else:
                metadata.appendChild(newTag(contentdom,"dc:creator",
                                            attrs=creator_attrs(idnum),
                                            text=self.getMetadata('author')))
                idnum += 1

        metadata.appendChild(newTag(contentdom,"dc:contributor",text="FanFicFare [https://github.com/JimmXinu/FanFicFare]",
                                    attrs={"id":"id-%d"%idnum}))
        idnum += 1
        # metadata.appendChild(newTag(contentdom,"dc:rights",text=""))
        if self.story.getMetadata('langcode'):
            langcode=self.story.getMetadata('langcode')
        else:
            langcode='en'
        metadata.appendChild(newTag(contentdom,"dc:language",text=langcode))

        #  published, created, updated, calibre
        #  Leave calling self.story.getMetadataRaw directly in case date format changes.
        if epub3:
            ## epub3 requires an updated modified date on every change of
            ## any kind, not just *content* change.
            from datetime import datetime
            metadata.appendChild(newTag(contentdom,"meta",
                                        attrs={"property":"dcterms:modified"},
                                        text=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")))
        else:
            if self.story.getMetadataRaw('datePublished'):
                metadata.appendChild(newTag(contentdom,"dc:date",
                                            attrs={"opf:event":"publication"},
                                            text=self.story.getMetadataRaw('datePublished').strftime("%Y-%m-%d")))

            if self.story.getMetadataRaw('dateCreated'):
                metadata.appendChild(newTag(contentdom,"dc:date",
                                            attrs={"opf:event":"creation"},
                                            text=self.story.getMetadataRaw('dateCreated').strftime("%Y-%m-%d")))

            if self.story.getMetadataRaw('dateUpdated'):
                metadata.appendChild(newTag(contentdom,"dc:date",
                                            attrs={"opf:event":"modification"},
                                            text=self.story.getMetadataRaw('dateUpdated').strftime("%Y-%m-%d")))
                metadata.appendChild(newTag(contentdom,"meta",
                                            attrs={"name":"calibre:timestamp",
                                                   "content":self.story.getMetadataRaw('dateUpdated').strftime("%Y-%m-%dT%H:%M:%S")}))

        series = self.story.getMetadata('series')
        if series and self.getConfig('calibre_series_meta'):
            series_index = "0.0"
            if '[' in series:
                # logger.debug(series)
                ## assumed "series [series_index]"
                series_index = series[series.rindex(' [')+2:-1]
                series = series[:series.rindex(' [')]

                ## calibre always outputs a series_index and it's
                ## always a float with 1 or 2 decimals.  FFF usually
                ## has either an integer or no index. (injected
                ## calibre series is the only float at this time)
                series_index = "%.2f" % float(series_index)

            metadata.appendChild(newTag(contentdom,"meta",
                                        attrs={"name":"calibre:series",
                                               "content":series}))
            metadata.appendChild(newTag(contentdom,"meta",
                                        attrs={"name":"calibre:series_index",
                                               "content":series_index}))

        if self.getMetadata('description'):
            metadata.appendChild(newTag(contentdom,"dc:description",text=
                                        self.getMetadata('description')))

        for subject in self.story.getSubjectTags():
            metadata.appendChild(newTag(contentdom,"dc:subject",text=subject))

        if self.getMetadata('site'):
            metadata.appendChild(newTag(contentdom,"dc:publisher",
                                        text=self.getMetadata('site')))

        if self.getMetadata('storyUrl'):
            if epub3:
                metadata.appendChild(newTag(contentdom,"dc:identifier",
                                            text="URL:"+self.getMetadata('storyUrl')))
            else:
                metadata.appendChild(newTag(contentdom,"dc:identifier",
                                            attrs={"opf:scheme":"URL"},
                                            text=self.getMetadata('storyUrl')))
            metadata.appendChild(newTag(contentdom,"dc:source",
                                        text=self.getMetadata('storyUrl')))

        if epub3:
            # <meta refines="#id" property="title-type">main</meta>
            metadata.appendChild(newTag(contentdom,"meta",
                                        attrs={"property":"title-type",
                                               "refines":"#id",
                                               },
                                        text="main"))

            # epub3 removes attrs that identify dc:creator and
            # dc:contributor types and instead put them here.
            # 'aut' for 1-(idnum-1)
            for j in range(1,idnum-1):
                #<meta property="role" refines="#id-1" scheme="marc:relators">aut</meta>
                metadata.appendChild(newTag(contentdom,"meta",
                                            attrs={"property":"role",
                                                   "refines":"#id-%d"%j,
                                                   "scheme":"marc:relators",
                                                   },
                                            text="aut"))

            metadata.appendChild(newTag(contentdom,"meta",
                                        attrs={"property":"role",
                                               "refines":"#id-%d"%(idnum-1),
                                               "scheme":"marc:relators",
                                               },
                                        text="bkp"))

        ## end of metadata, create manifest.
        items = [] # list of (id, href, type, title) tuples(all strings)
        itemrefs = [] # list of strings -- idrefs from .opfs' spines
        items.append(("ncx","toc.ncx","application/x-dtbncx+xml",None)) ## we'll generate the toc.ncx file,
                                                                   ## but it needs to be in the items manifest.

        guide = None
        coverIO = None

        coverimgid = "image0000"
        if self.use_oldcover:
            logger.debug("using old cover")
            (oldcoverhtmlhref,
             oldcoverhtmltype,
             oldcoverhtmldata,
             oldcoverimghref,
             oldcoverimgtype,
             oldcoverimgdata) = self.story.oldcover
            outputepub.writestr(oldcoverhtmlhref,oldcoverhtmldata)
            outputepub.writestr(oldcoverimghref,oldcoverimgdata)

            coverimgid = "image0"
            items.append((coverimgid,
                          oldcoverimghref,
                          oldcoverimgtype,
                          None))
            items.append(("cover",oldcoverhtmlhref,oldcoverhtmltype,None))
            itemrefs.append("cover")
            metadata.appendChild(newTag(contentdom,"meta",{"content":"image0",
                                                           "name":"cover"}))
            guide = newTag(contentdom,"guide")
            guide.appendChild(newTag(contentdom,"reference",attrs={"type":"cover",
                                                                   "title":"Cover",
                                                                   "href":oldcoverhtmlhref}))

        if self.getConfig('include_images'):
            imgcount=0
            for imgmap in self.story.getImgUrls():
                imgfile = "OEBPS/"+imgmap['newsrc']
                # don't overwrite old cover.
                if not self.use_oldcover or imgfile != oldcoverimghref:
                    outputepub.writestr(imgfile,imgmap['data'])
                    items.append(("image%04d"%imgcount,
                                  imgfile,
                                  imgmap['mime'],
                                  None))
                    imgcount+=1
                    if 'cover' in imgfile:
                        # make sure coverimgid is set to the cover, not
                        # just the first image.
                        coverimgid = items[-1][0]

        items.append(("style","OEBPS/stylesheet.css","text/css",None))

        if self.story.cover and not self.use_oldcover:
            # Note that the id of the cover xhmtl *must* be 'cover'
            # for it to work on Nook.
            items.append(("cover","OEBPS/cover.xhtml","application/xhtml+xml",None))
            itemrefs.append("cover")
            #
            # <meta name="cover" content="cover.jpg"/>
            metadata.appendChild(newTag(contentdom,"meta",{"content":coverimgid,
                                                           "name":"cover"}))
            # cover stuff for later:
            # at end of <package>:
            # <guide>
            # <reference type="cover" title="Cover" href="Text/cover.xhtml"/>
            # </guide>
            guide = newTag(contentdom,"guide")
            guide.appendChild(newTag(contentdom,"reference",attrs={"type":"cover",
                                                       "title":"Cover",
                                                       "href":"OEBPS/cover.xhtml"}))

            if self.hasConfig("cover_content"):
                COVER = string.Template(self.getConfig("cover_content"))
            else:
                COVER = self.EPUB_COVER
            coverIO = BytesIO()
            self._write(coverIO,COVER.substitute(dict(list(self.story.getAllMetadata().items())+list({'coverimg':self.story.cover}.items()))))

        if self.getConfig("include_titlepage"):
            items.append(("title_page","OEBPS/title_page.xhtml","application/xhtml+xml","Title Page"))
            itemrefs.append("title_page")
        if self.story.getChapterCount() > 1 and self.getConfig("include_tocpage") and not self.metaonly :
            items.append(("toc_page","OEBPS/toc_page.xhtml","application/xhtml+xml","Table of Contents"))
            itemrefs.append("toc_page")

        ## save where to insert logpage.
        logpage_indices = (len(items),len(itemrefs))

        dologpage = ( self.getConfig("include_logpage") == "smart" and \
                          (self.story.logfile or self.story.getMetadataRaw("status") == "In-Progress") )  \
                     or self.getConfig("include_logpage") == "true"

        ## collect chapter urls and file names for internalize_text_links option.
        chapurlmap = {}
        for index, chap in enumerate(self.story.getChapters(fortoc=True)):
            if chap['html']:
                i=index+1
                items.append(("file%s"%chap['index04'],
                              "OEBPS/file%s.xhtml"%chap['index04'],
                              "application/xhtml+xml",
                              chap['title']))
                itemrefs.append("file%s"%chap['index04'])
                chapurlmap[chap['url']]="file%s.xhtml"%chap['index04'] # url -> relative epub file name.

        if dologpage:
            if self.getConfig("logpage_at_end") == "true":
                ## insert logpage after chapters.
                logpage_indices = (len(items),len(itemrefs))
            items.insert(logpage_indices[0],("log_page","OEBPS/log_page.xhtml","application/xhtml+xml","Update Log"))
            itemrefs.insert(logpage_indices[1],"log_page")

        manifest = contentdom.createElement("manifest")
        package.appendChild(manifest)
        for item in items:
            (id,href,type,title)=item
            manifest.appendChild(newTag(contentdom,"item",
                                        attrs={'id':id,
                                               'href':href,
                                               'media-type':type}))
        if epub3:
            # epub3 nav
            # <item href="nav.xhtml" id="nav" media-type="application/xhtml+xml" properties="nav"/>
            manifest.appendChild(newTag(contentdom,"item",
                                        attrs={'href':'nav.xhtml',
                                               'id':'nav',
                                               'media-type':'application/xhtml+xml',
                                               'properties':'nav'
                                               }))

        spine = newTag(contentdom,"spine",attrs={"toc":"ncx"})
        package.appendChild(spine)
        for itemref in itemrefs:
            spine.appendChild(newTag(contentdom,"itemref",
                                     attrs={"idref":itemref,
                                            "linear":"yes"}))
        # guide only exists if there's a cover.
        if guide:
            package.appendChild(guide)

        # write content.opf to zip.
        contentxml = contentdom.toxml(encoding='utf-8')
        # tweak for brain damaged Nook STR.  Nook insists on name before content.
        contentxml = contentxml.replace(ensure_binary('<meta content="%s" name="cover"/>'%coverimgid),
                                        ensure_binary('<meta name="cover" content="%s"/>'%coverimgid))

        outputepub.writestr("content.opf",contentxml)

        contentdom.unlink()
        del contentdom

        ## create toc.ncx file
        tocncxdom = getDOMImplementation().createDocument(None, "ncx", None)
        ncx = tocncxdom.documentElement
        ncx.setAttribute("version","2005-1")
        ncx.setAttribute("xmlns","http://www.daisy.org/z3986/2005/ncx/")
        head = tocncxdom.createElement("head")
        ncx.appendChild(head)
        head.appendChild(newTag(tocncxdom,"meta",
                                attrs={"name":"dtb:uid", "content":uniqueid}))
        head.appendChild(newTag(tocncxdom,"meta",
                                attrs={"name":"dtb:depth", "content":"1"}))
        head.appendChild(newTag(tocncxdom,"meta",
                                attrs={"name":"dtb:totalPageCount", "content":"0"}))
        head.appendChild(newTag(tocncxdom,"meta",
                                attrs={"name":"dtb:maxPageNumber", "content":"0"}))

        docTitle = tocncxdom.createElement("docTitle")
        docTitle.appendChild(newTag(tocncxdom,"text",text=self.getMetadata('title')))
        ncx.appendChild(docTitle)

        tocnavMap = tocncxdom.createElement("navMap")
        ncx.appendChild(tocnavMap)

        # <navPoint id="<id>" playOrder="<risingnumberfrom0>">
        #   <navLabel>
        #     <text><chapter title></text>
        #   </navLabel>
        #   <content src="<chapterfile>"/>
        # </navPoint>
        index=0
        for item in items:
            (id,href,type,title)=item
            # only items to be skipped, cover.xhtml, images, toc.ncx, stylesheet.css, should have no title.
            if title :
                navPoint = newTag(tocncxdom,"navPoint",
                                  attrs={'id':id,
                                         'playOrder':unicode(index)})
                tocnavMap.appendChild(navPoint)
                navLabel = newTag(tocncxdom,"navLabel")
                navPoint.appendChild(navLabel)
                ## the xml library will re-escape as needed.
                navLabel.appendChild(newTag(tocncxdom,"text",text=stripHTML(title)))
                navPoint.appendChild(newTag(tocncxdom,"content",attrs={"src":href}))
                index=index+1

        # write toc.ncx to zip file
        outputepub.writestr("toc.ncx",tocncxdom.toxml(encoding='utf-8'))
        tocncxdom.unlink()
        del tocncxdom

        if epub3:
            ##############################################################################################################
            ## create nav.xhtml file
            tocnavdom = getDOMImplementation().createDocument(None, "html", None)
            navxhtml = tocnavdom.documentElement
            navxhtml.setAttribute("xmlns","http://www.w3.org/1999/xhtml")
            navxhtml.setAttribute("xmlns:epub","http://www.idpf.org/2007/ops")
            navxhtml.setAttribute("lang",langcode)
            navxhtml.setAttribute("xml:lang",langcode)
            head = tocnavdom.createElement("head")
            navxhtml.appendChild(head)
            head.appendChild(newTag(tocnavdom,"title",text="Navigation"))
            # <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
            head.appendChild(newTag(tocnavdom,"meta",
                                    attrs={"http-equiv":"Content-Type",
                                           "content":"text/html; charset=utf-8"}))

            body = tocnavdom.createElement("body")
            navxhtml.appendChild(body)
            nav = newTag(tocnavdom,"nav",
                         attrs={"epub:type":"toc"})
            body.appendChild(nav)
            ol = newTag(tocnavdom,"ol")
            nav.appendChild(ol)

            for item in items:
                (id,href,type,title)=item
                # only items to be skipped, cover.xhtml, images, toc.nav,
                # stylesheet.css, should have no title.
                if title:
                    li = newTag(tocnavdom,"li")
                    ol.appendChild(li)
                    atag = newTag(tocnavdom,"a",
                                  attrs={"href":href},
                                  text=stripHTML(title))
                    li.appendChild(atag)

            if self.story.cover and not self.use_oldcover:
                #
                #

                #
Cover

                #

                #
                nav = newTag(tocnavdom,"nav",
                             attrs={"epub:type":"landmarks",
                                    "hidden":""})
                body.appendChild(nav)
                ol = newTag(tocnavdom,"ol")
                nav.appendChild(ol)
                li = newTag(tocnavdom,"li")
                ol.appendChild(li)
                atag = newTag(tocnavdom,"a",
                              attrs={"href":"OEBPS/cover.xhtml",
                                     "epub:type":"cover"},
                              text="Cover")
                li.appendChild(atag)

            # write nav.xhtml to zip file
            outputepub.writestr("nav.xhtml",tocnavdom.toxml(encoding='utf-8'))
            tocnavdom.unlink()
            del tocnavdom
            ##############################################################################################################

        # write stylesheet.css file.
        outputepub.writestr("OEBPS/stylesheet.css",self.EPUB_CSS.substitute(self.story.getAllMetadata()))

        # write title page.
        if self.getConfig("titlepage_use_table"):
            TITLE_PAGE_START  = self.EPUB_TABLE_TITLE_PAGE_START
            TITLE_ENTRY       = self.EPUB_TABLE_TITLE_ENTRY
            WIDE_TITLE_ENTRY  = self.EPUB_TABLE_TITLE_WIDE_ENTRY
            NO_TITLE_ENTRY    = self.EPUB_TABLE_NO_TITLE_ENTRY
            TITLE_PAGE_END    = self.EPUB_TABLE_TITLE_PAGE_END
        else:
            TITLE_PAGE_START  = self.EPUB_TITLE_PAGE_START
            TITLE_ENTRY       = self.EPUB_TITLE_ENTRY
            WIDE_TITLE_ENTRY  = self.EPUB_TITLE_ENTRY # same, only wide in tables.
            NO_TITLE_ENTRY    = self.EPUB_NO_TITLE_ENTRY
            TITLE_PAGE_END    = self.EPUB_TITLE_PAGE_END

        if coverIO:
            outputepub.writestr("OEBPS/cover.xhtml",coverIO.getvalue())
            coverIO.close()

        titlepageIO = BytesIO()
        self.writeTitlePage(out=titlepageIO,
                            START=TITLE_PAGE_START,
                            ENTRY=TITLE_ENTRY,
                            WIDE_ENTRY=WIDE_TITLE_ENTRY,
                            END=TITLE_PAGE_END,
                            NO_TITLE_ENTRY=NO_TITLE_ENTRY)
        if titlepageIO.getvalue(): # will be false if no title page.
            outputepub.writestr("OEBPS/title_page.xhtml",titlepageIO.getvalue())
        titlepageIO.close()

        # write toc page.
        tocpageIO = BytesIO()
        self.writeTOCPage(tocpageIO,
                          self.EPUB_TOC_PAGE_START,
                          self.EPUB_TOC_ENTRY,
                          self.EPUB_TOC_PAGE_END)
        if tocpageIO.getvalue(): # will be false if no toc page.
            outputepub.writestr("OEBPS/toc_page.xhtml",tocpageIO.getvalue())
        tocpageIO.close()

        if dologpage:
            # write log page.
            logpageIO = BytesIO()
            self.writeLogPage(logpageIO)
            outputepub.writestr("OEBPS/log_page.xhtml",logpageIO.getvalue())
            logpageIO.close()

        if self.hasConfig('chapter_start'):
            CHAPTER_START = string.Template(self.getConfig("chapter_start"))
        else:
            CHAPTER_START = self.EPUB_CHAPTER_START

        if self.hasConfig('chapter_end'):
            CHAPTER_END = string.Template(self.getConfig("chapter_end"))
        else:
            CHAPTER_END = self.EPUB_CHAPTER_END

        for index, chap in enumerate(self.story.getChapters()): # (url,title,html)
            # logger.debug("chapter:%s %s %s"%(len(chap['html']), chap['title'],chap['url']))
            if chap['html']:
                chap_data = chap['html']
                if self.getConfig('internalize_text_links'):
                    soup = bs4.BeautifulSoup(chap['html'],'html5lib')
                    changed=False
                    for alink in soup.find_all('a'):
                        ## Chapters can be inserted in the middle
                        ## which can break existing internal links.
                        ## So let's save the original href and update.
                        # logger.debug("found %s"%alink)
                        if alink.has_attr('data-orighref') and alink['data-orighref'] in chapurlmap:
                            alink['href']=chapurlmap[alink['data-orighref']]
                            # logger.debug("set1  %s"%alink)
                            changed=True
                        elif alink.has_attr('href') and alink['href'] in chapurlmap:
                            if not alink['href'].startswith('file'):
                                # only save orig href if not already internal.
                                alink['data-orighref']=alink['href']
                            alink['href']=chapurlmap[alink['href']]
                            # logger.debug("set2  %s"%alink)
                            changed=True
                    if changed:
                        chap_data = unicode(soup)
                        # Don't want html, head or body tags in
                        # chapter html--bs4 insists on adding them.
                        chap_data = re.sub(r"</?(html|head|body)[^>]*>\r?\n?","",chap_data)

                # logger.debug('Writing chapter text for: %s' % chap.title)
                chap['url']=removeEntities(chap['url'])
                chap['chapter']=removeEntities(chap['chapter'])
                chap['title']=removeEntities(chap['title'])
                chap['origchapter']=removeEntities(chap['origtitle'])
                chap['tocchapter']=removeEntities(chap['toctitle'])
                # escape double quotes in all vals.
                for k,v in chap.items():
                    if isinstance(v,basestring): chap[k]=v.replace('"','"')
                fullhtml = CHAPTER_START.substitute(chap) + \
                    chap_data.strip() + \
                    CHAPTER_END.substitute(chap)
                # strip to avoid ever growning numbers of newlines.
                # ffnet(& maybe others) gives the whole chapter text
                # as one line.  This causes problems for nook(at
                # least) when the chapter size starts getting big
                # (200k+)
                fullhtml = re.sub(r'(
|
)\n*',r'\1\n',fullhtml)

                # logger.debug("write OEBPS/file%s.xhtml"%chap['index04'])
                outputepub.writestr("OEBPS/file%s.xhtml"%chap['index04'],fullhtml.encode('utf-8'))
                del fullhtml

        if self.story.calibrebookmark:
            outputepub.writestr("META-INF/calibre_bookmarks.txt",self.story.calibrebookmark)

        # declares all the files created by Windows.  otherwise, when
        # it runs in appengine, windows unzips the files as 000 perms.
        for zf in outputepub.filelist:
            zf.create_system = 0
        outputepub.close()
        out.write(zipio.getvalue())
        zipio.close()

## Utility method for creating new tags.
def newTag(dom,name,attrs=None,text=None):
    tag = dom.createElement(name)
    if( attrs is not None ):
        for attr in attrs.keys():
            tag.setAttribute(attr,attrs[attr])
    if( text is not None ):
        tag.appendChild(dom.createTextNode(text))
    return tag


# iseseisev harjutus 1
# Kristofer Andres
# 08.03.2022

print("tere, maailm!")

aasta = 2020
liblikas = "teelehemosaiikliblikas"
lause_keskosa = ". aasta liblikas on "
lause = str(aasta)+lause_keskosa+liblikas
print(lause)

kõrgus = float(input("sisesta pilve kõrgus kilomeetrites: "))
if kõrgus >= 6:
    print("need on ülemised pilved")
else:
    print("need ei ole ülemised pilved")

inimesed = 20
kohad = 40
buss= inimesed // kohad +1
viimaneb = inimesed % kohad
if inimesed % kohad == 0:
    buss =  inimesed // kohad
    viimaneb = kohad

print(f"inimeste arv: {inimesed}")
print(f"kohtade arv: {kohad}")
print (f" busse vaja: {buss}")
print (f"viimases bussis inimesi: {viimaneb}")