""" controller and routes for question end-points """
import os
from flask import request, jsonify
from app_questions import app, mongo
from datetime import datetime
from bson.objectid import ObjectId
from logging.handlers import RotatingFileHandler
import logging
from urllib.parse import urlencode, parse_qs, urlsplit, urlunsplit
from time import strftime
import pymongo
from pymongo.errors import ConnectionFailure, WriteError, InvalidId, PyMongoError
from werkzeug.utils import secure_filename
from app_questions.csv_parser import CsvQuestions
import time
from flask import g
from random import shuffle

ROOT_PATH = os.environ.get('ROOT_PATH')
# maxBytes=20000000 20MB
handler = RotatingFileHandler('app_questions.log', maxBytes=20000000, backupCount=3)
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

logger.addHandler(handler)
ALLOWED_EXTENSIONS = ['csv']

@app.route('/questions', defaults={'question_id': ''}, methods=['GET', 'POST', 'PUT'])
@app.route('/questions/<question_id>', methods=['GET'])
def question(question_id):
	if request.method == 'GET':
		if question_id:
			try:
				result = mongo.db.questions.find_one({"_id": ObjectId(question_id)})
				if result:
					return get_json_result(False, "Record Found", 200, result)
				else:
					return get_json_result(True, "Data not found", 404)

			except ConnectionFailure as connection_failure:
				log_info("Exception ConnectionFailure,  Unable to Insert Query in DB: " + str(connection_failure))
				return get_json_result(True, "Connection Failure", 404)
			except InvalidId as invalid_id:
				log_info("Exception InvalidId,  Unable to fetch data: " + str(invalid_id))
				return get_json_result(True, "Session key is not valid", 404)

			except PyMongoError as e:
				log_info("Exception, Unable to fetch data: " + str(e))
				return get_json_result(True, "Unable to fetch data", 404)
		else:
			start = request.args.get('offset', default=0, type=int)
			limit = request.args.get('limit', default=20, type=int)
			if start < 0 or limit < 0:
				return get_json_result(True, "offset and limit must be any positive number", 404)
			topics = request.args.getlist('topic')
			difficulty_level = request.args.get('difficulty_level', '')
			pack_name = request.args.get('pack_name', '')
			pack_slug = request.args.get('pack_slug', '')
			order = request.args.get('order', default='desc')  # asc or desc
			order_by = request.args.get('order_by', default='id')  # id or question
			if order_by == 'id':
				order_by = '_id'
			search_by = request.args.get('search', default='')  # search from Question title

			try:
				if topics:
					questions = mongo.db.questions.find({'topics': {'$in': topics}}).limit(limit).skip(start).sort(order_by, 1 if order == 'asc' else -1)
				elif difficulty_level:
					questions = mongo.db.questions.find({'difficulty_level': str(difficulty_level)}).limit(limit).skip(start).sort(order_by, 1 if order == 'asc' else -1)
				elif pack_name:
					questions = mongo.db.questions.find({'pack.title': {'$regex': '.*' + pack_name + '.*', '$options': 'i'}}).limit(limit).skip(start).sort(order_by, 1 if order == 'asc' else -1)
				elif pack_slug:
					questions = mongo.db.questions.find({'pack.slug': pack_slug}).limit(limit).skip(start).sort(order_by, 1 if order == 'asc' else -1)
				else:
					questions = mongo.db.questions.find({"question": {'$regex': '.*' + search_by + '.*', '$options': 'i'}}).limit(limit).skip(start).sort(order_by, 1 if order == 'asc' else -1)

				collection_count = questions.count()
				data = [doc for doc in questions]
				shuffle(data)  # Shuffle the data list
				current_req_count = len(data)
				result_data = dict()
				result_data['base'] = request.url_root
				result_data['self'] = request.url
				result_data['limit'] = limit
				result_data["results"] = data
				result_data['size'] = current_req_count
				result_data['offset'] = start
				result_data['total_size'] = collection_count

				if start == 0:
					result_data["next"] = set_query_parameter(request.url, ['offset', 'limit'], [(start + limit), limit])
				elif start > 0 and (start+limit) < collection_count:
					result_data["prev"] = set_query_parameter(request.url, ['offset', 'limit'], [(start - limit), limit])
					result_data["next"] = set_query_parameter(request.url, ['offset', 'limit'], [(start + limit), limit])
				else:
					result_data["prev"] = set_query_parameter(request.url, ['offset', 'limit'], [(start - limit), limit])

			except Exception as e:
				log_info("Exception Uable to Seacrh Query in DB: " + str(e))
				return get_json_result(True, "DB Connection issue", 404)

			return get_json_result(False, "Success", 200, result_data)
	elif request.method == 'POST':

		if request.is_json:
			data = request.get_json()
			if data:
				choices_list = insert_question_choices(data['choices'])
				data['choices'] = choices_list
				#Todo
				# created_by need to be change in future
				data['created_by'] = 'Admin'
				data['created_date'] = datetime.now()
				data['modified_date'] = datetime.now()

				try:
					result = mongo.db.questions.insert_one(data)
					if result.acknowledged:
						data = dict()
						data['question_id'] = result.inserted_id
						return get_json_result(False, "Data Inserted Successfully", 200, data)
					else:
						return get_json_result(True, "Error in inserting data into DB", 404)

				except ConnectionFailure as connection_failure:
					log_info("Exception ConnectionFailure,  Unable to Insert Query in DB: " + str(connection_failure))
					return get_json_result(True, "connection Failure", 404)
				except WriteError as write_error:
					log_info("Exception, WriteError, Unable to Insert Query in DB: " + str(write_error))
					return get_json_result(True, "Write Error", 404)
				except PyMongoError as pymongo_error:
					log_info("Exception Unable to Insert Query in DB: " + str(pymongo_error))
					return get_json_result(True, "Error in inserting data into DB", 404)

			return get_json_result(True, "Json is empty", 404)
		return get_json_result(True, "Request is not a JSON", 404)
	return get_json_result(True, "Request method is not valid", 404)

def insert_question_choices(choice_data):
	to_return = list(choice_data)
	for data in to_return:
		data['is_correct'] = data['is_correct'] == 1
		data['created_date'] = datetime.now()

		try:
			result = mongo.db.question_choices.insert_one(data)
			if result.acknowledged:
				# data['choice_id'] = str(result.inserted_id)
				# del data['_id']
				log_info("Question Choice inserted successfully")
		except ConnectionFailure as connection_failure:
			log_info("Exception ConnectionFailure,  Unable to Insert Question Choice in DB: " + str(connection_failure))
		except WriteError as write_error:
			log_info("Exception, WriteError, Unable to Insert Question Choice in DB: " + str(write_error))
		except PyMongoError as pymongo_error:
			log_info("Exception Unable to Insert Question Choice in DB: " + str(pymongo_error))
	return to_return

@app.route('/upload/pack', methods=['POST', 'GET'])
def upload_csv():
	if request.method == 'POST':
		# check if the post request has the file part
		if 'file' not in request.files:
			return get_json_result(True, "No File Found", 400)
		file = request.files['file']
		# if user does not select file, browser also
		# submit a empty part without filename
		if file.filename == '':
			return get_json_result(True, "No selected file", 400)
		if file and allowed_file(file.filename):
			original_file_name = secure_filename(file.filename)
			f_name, f_ext = original_file_name.split('.')
			filename = f_name + '_' + date_to_string_file_name(datetime.now()) + '.' + f_ext
			file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
			# delete_file_if_exist(file_path)
			file.save(file_path)
			#TODO
			# Must be converted into Background Thread
			try:
				if os.path.exists(file_path):
					insert_into_db(file_path, filename)
				return get_json_result(False, "File successfully parse and inserted into DB", 200)
			except Exception as e:
				log_info("Exception, while parsing CSV file " + str(e))
				return get_json_result(True, "Error in parsing CSV file", 400)
	return get_json_result(True, "Request method is not valid, MUST be POST", 404)

@app.route('/question/pack/<pack_id>', methods=['GET'])
@app.route('/question/pack', defaults={'pack_id': ''}, methods=['POST', 'GET', 'PUT'])
def question_pack(pack_id):
	if request.method == 'POST':
		if request.is_json:
			'''
			Sample JSON data
			{
				'title': 'nxb pack',
				'slug':'nxb'
				'description': 'Xyz.......',
				'created_by': 'Admin',
				'created_date': ''
			}
			'''
			data = request.get_json()
			if data:
				data['created_by'] = 'Admin'
				data['created_date'] = datetime.now()

				try:
					result = mongo.db.question_pack.insert_one(data)
					if result.acknowledged:
						data = dict()
						data['pack_id'] = result.inserted_id
						return get_json_result(False, "Data Inserted Successfully", 200, data)
					else:
						return get_json_result(True, "Error in inserting data into DB", 404)

				except ConnectionFailure as connection_failure:
					log_info("Exception ConnectionFailure,  Unable to Insert Query in DB: " + str(connection_failure))
					return get_json_result(True, "connection Failure", 404)
				except WriteError as write_error:
					log_info("Exception, WriteError, Unable to Insert Query in DB: " + str(write_error))
					return get_json_result(True, "Write Error", 404)
				except PyMongoError as pymongo_error:
					log_info("Exception Unable to Insert Query in DB: " + str(pymongo_error))
					return get_json_result(True, "Error in inserting data into DB", 404)
				except Exception as e:
					log_info("Exception : " + str(e))
					return get_json_result(True, "Error in inserting data into DB", 404)

			return get_json_result(True, "Json is empty", 404)
		return get_json_result(True, "Request is not a JSON", 404)
	elif request.method == 'GET':
		if pack_id:
			try:
				result = mongo.db.question_pack.find_one({"_id": ObjectId(pack_id)})
				if result:
					return get_json_result(False, "Record Found", 200, result)
				else:
					return get_json_result(True, "Data not found", 404)

			except ConnectionFailure as connection_failure:
				log_info("Exception ConnectionFailure,  Unable to Insert Query in DB: " + str(connection_failure))
				return get_json_result(True, "Connection Failure", 404)
			except InvalidId as invalid_id:
				log_info("Exception InvalidId,  Unable to fetch data: " + str(invalid_id))
				return get_json_result(True, "Session key is not valid", 404)

			except PyMongoError as e:
				log_info("Exception, Unable to fetch data: " + str(e))
				return get_json_result(True, "Unable to fetch data", 404)
		else:
			try:
				topic_list = mongo.db.question_pack.find()
				data = [doc for doc in topic_list]
				return get_json_result(False, "Success", 200, data)
			except Exception as e:
				log_info("Exception Uable to Seacrh Query in DB: " + str(e))
				return get_json_result(True, "DB Connection issue", 404)
	elif request.method == 'PUT':

		'''
		{
			"_id": "5c654bb94654a503e5d53460",
			"data": {
				"title":"Airplanes And Aerodynamics",
				"description": "Study unit one"
			}
		}

		'''

		data = request.get_json()
		object_id = data['_id']  # Question Pack ID
		updated_data = data['data']  # Updated Object
		updated_data['slug'] = slugify(updated_data['title'])
		updated_data['modified_date'] = datetime.now()
		pack_obj = mongo.db.question_pack.find_one_and_update(
				{'_id': ObjectId(object_id)},
				{"$set": updated_data},
				return_document=pymongo.ReturnDocument.AFTER
		)
		if not pack_obj:
			return get_json_result(False, "record ID not found ", 200)
		log_info("Pack ID : " + object_id + " Question Pack record updated ")

		updated_dict = dict()
		updated_dict['pack.title'] = pack_obj['title']
		updated_dict['pack.description'] = pack_obj['description']
		updated_dict['pack.slug'] = pack_obj['slug']
		data_ = mongo.db.questions.update_many(
				{'pack._id': ObjectId(object_id)},
				{"$set": updated_dict}
		)
		if data_.acknowledged:
			log_info("Pack ID : " + object_id + "Match Count : " + str(data_.matched_count) + " Modified Count " + str(data_.modified_count))
		else:
			log_info("Pack ID : " + object_id + "Problem in updating Questions ")
		return get_json_result(False, "Question Pack record updated " + object_id + " Successfully ", 200)
	return get_json_result(True, "Request method is not valid, MUST be POST,GET,PUT", 404)

'''
@app.route('/question/pack/<pack_id>/questions', methods=['GET'])
def get_question_pack(pack_id):
	if request.method == 'GET':
		try:
			result = mongo.db.questions.find({"_id": ObjectId(topic_id)})
			if result:
				return get_json_result(False, "Record Found", 200, result)
			else:
				return get_json_result(True, "Data not found", 404)

		except ConnectionFailure as connection_failure:
			log_info("Exception ConnectionFailure,  Unable to Insert Query in DB: " + str(connection_failure))
			return get_json_result(True, "Connection Failure", 404)
		except InvalidId as invalid_id:
			log_info("Exception InvalidId,  Unable to fetch data: " + str(invalid_id))
			return get_json_result(True, "Session key is not valid", 404)

		except PyMongoError as e:
			log_info("Exception, Unable to fetch data: " + str(e))
			return get_json_result(True, "Unable to fetch data", 404)
'''

@app.route('/topics/<topic_id>', methods=['GET'])
@app.route('/topics', defaults={'topic_id': ''}, methods=['POST', 'GET', 'PUT'])
def topics(topic_id):
	if request.method == 'POST':
		if request.is_json:
			data = request.get_json()
			if data:
				data['created_by'] = 'Admin'
				data['created_date'] = datetime.now()
				data['is_active'] = True

				try:
					result = mongo.db.topics.insert_one(data)
					if result.acknowledged:
						data = dict()
						data['topic_id'] = result.inserted_id
						return get_json_result(False, "Data Inserted Successfully", 200, data)
					else:
						return get_json_result(True, "Error in inserting data into DB", 404)

				except ConnectionFailure as connection_failure:
					log_info("Exception ConnectionFailure,  Unable to Insert Query in DB: " + str(connection_failure))
					return get_json_result(True, "connection Failure", 404)
				except WriteError as write_error:
					log_info("Exception, WriteError, Unable to Insert Query in DB: " + str(write_error))
					return get_json_result(True, "Write Error", 404)
				except PyMongoError as pymongo_error:
					log_info("Exception Unable to Insert Query in DB: " + str(pymongo_error))
					return get_json_result(True, "Error in inserting data into DB", 404)

			return get_json_result(True, "Json is empty", 404)
		return get_json_result(True, "Request is not a JSON", 404)
	elif request.method == 'GET':
		if topic_id:
			try:
				result = mongo.db.topics.find_one({"_id": ObjectId(topic_id)})
				if result:
					return get_json_result(False, "Record Found", 200, result)
				else:
					return get_json_result(True, "Data not found", 404)

			except ConnectionFailure as connection_failure:
				log_info("Exception ConnectionFailure,  Unable to Insert Query in DB: " + str(connection_failure))
				return get_json_result(True, "Connection Failure", 404)
			except InvalidId as invalid_id:
				log_info("Exception InvalidId,  Unable to fetch data: " + str(invalid_id))
				return get_json_result(True, "Session key is not valid", 404)

			except PyMongoError as e:
				log_info("Exception, Unable to fetch data: " + str(e))
				return get_json_result(True, "Unable to fetch data", 404)
		else:
			try:
				topic_list = mongo.db.topics.find()
				data = [doc for doc in topic_list]
				return get_json_result(False, "Success", 200, data)
			except Exception as e:
				log_info("Exception Uable to Seacrh Query in DB: " + str(e))
				return get_json_result(True, "DB Connection issue", 404)
	elif request.method == 'PUT':
		'''
		{
			"_id": "5c654bb94654a503e5d53460",
			"data": {
				"topic":"Aerodynamic Forces",
			}
		}

		For De-activate the Topic

		{
			"_id": "5c654bb94654a503e5d53460",
			"data": {
				"is_active":false
			}
		}

		'''
		try:
			data = request.get_json()
			object_id = data['_id']  # Question Pack ID
			updated_data = data['data']  # Updated Object
			is_active = updated_data.get('is_active', True)
			if not is_active:
				"""
					If is_active is False, then remove all linked topics from question
					and set updated is_active value in topic document.
				"""
				updated_data['modified_date'] = datetime.now()
				topic_obj = mongo.db.topics.find_one_and_update(
						{'_id': ObjectId(object_id)},
						{"$set": updated_data},
						return_document=pymongo.ReturnDocument.AFTER
				)
				if not topic_obj:
					return get_json_result(False, "record ID not found ", 200)
				log_info("Topic ID : " + object_id + " Topic status updated Successfully ")
				updated_dict = dict()
				updated_dict['_id'] = topic_obj['_id']
				data_ = mongo.db.questions.update_many(
						{'topics._id': ObjectId(object_id)},
						{"$pull": {"topics": updated_dict}}
				)
				if data_.acknowledged:
					log_info("Topic ID : " + object_id + " Match Count : " + str(data_.matched_count) + " Removed Count " + str(data_.modified_count))
				else:
					log_info("Topic ID : " + object_id + " Problem in updating/removing topic from Questions ")
				return get_json_result(False, "Question Topic record updated " + object_id + " Successfully ", 200)
			else:
				topic_title = updated_data.get('topic', '')
				if topic_title:
					updated_data['slug'] = slugify(updated_data['topic'])
				updated_data['modified_date'] = datetime.now()
				topic_obj = mongo.db.topics.find_one_and_update(
						{'_id': ObjectId(object_id)},
						{"$set": updated_data},
						return_document=pymongo.ReturnDocument.AFTER
				)
				if not topic_obj:
					return get_json_result(False, "record ID not found ", 200)
				log_info("Topic ID : " + object_id + " Question Topic record updated ")
				updated_dict = dict()
				updated_dict['topics.$.topic'] = topic_obj['topic']
				updated_dict['topics.$.slug'] = topic_obj['slug']
				data_ = mongo.db.questions.update_many(
						{'topics._id': ObjectId(object_id)},
						{"$set": updated_dict}
				)
				if data_.acknowledged:
					log_info("Topic ID : " + object_id + " Match Count : " + str(data_.matched_count) + " Modified Count " + str(data_.modified_count))
				else:
					log_info("Topic ID : " + object_id + " Problem in updating Questions ")
				return get_json_result(False, "Question Topic record updated " + object_id + " Successfully ", 200)
		except PyMongoError as pymongo_error:
			log_info("Pymongo Error in updating topic record" + str(pymongo_error))
			return get_json_result(True, "Error in updating topic record", 404)
		except Exception as e:
			log_info("Exception while updating topic record " + str(e))
			return get_json_result(True, "Error in updating record", 404)

	return get_json_result(True, "Request method is not valid, MUST be POST", 404)

@app.route('/difficulty/category/<diff_id>', methods=['GET'])
@app.route('/difficulty/category', defaults={'diff_id': ''}, methods=['POST', 'GET'])
def difficulty_category(diff_id):
	if request.method == 'POST':
		if request.is_json:
			data = request.get_json()
			if data:
				data['created_by'] = 'Admin'
				data['created_date'] = datetime.now()

				try:
					result = mongo.db.difficulty_category.insert_one(data)
					if result.acknowledged:
						data = dict()
						data['diff_id'] = result.inserted_id
						return get_json_result(False, "Data Inserted Successfully", 200, data)
					else:
						return get_json_result(True, "Error in inserting data into DB", 404)

				except ConnectionFailure as connection_failure:
					log_info("Exception ConnectionFailure,  Unable to Insert Query in DB: " + str(connection_failure))
					return get_json_result(True, "connection Failure", 404)
				except WriteError as write_error:
					log_info("Exception, WriteError, Unable to Insert Query in DB: " + str(write_error))
					return get_json_result(True, "Write Error", 404)
				except PyMongoError as pymongo_error:
					log_info("Exception Unable to Insert Query in DB: " + str(pymongo_error))
					return get_json_result(True, "Error in inserting data into DB", 404)

			return get_json_result(True, "Json is empty", 404)
		return get_json_result(True, "Request is not a JSON", 404)
	elif request.method == 'GET':
		if diff_id:
			try:
				result = mongo.db.difficulty_category.find_one({"_id": ObjectId(diff_id)})
				if result:
					return get_json_result(False, "Record Found", 200, result)
				else:
					return get_json_result(True, "Data not found", 404)

			except ConnectionFailure as connection_failure:
				log_info("Exception ConnectionFailure,  Unable to Insert Query in DB: " + str(connection_failure))
				return get_json_result(True, "Connection Failure", 404)
			except InvalidId as invalid_id:
				log_info("Exception InvalidId,  Unable to fetch data: " + str(invalid_id))
				return get_json_result(True, "Session key is not valid", 404)
			except PyMongoError as e:
				log_info("Exception, Unable to fetch data: " + str(e))
				return get_json_result(True, "Unable to fetch data", 404)
		else:
			try:
				difficulty_category_list = mongo.db.difficulty_category.find()
				data = [doc for doc in difficulty_category_list]
				return get_json_result(False, "Success", 200, data)
			except Exception as e:
				log_info("Exception Uable to Seacrh Query in DB: " + str(e))
				return get_json_result(True, "DB Connection issue", 404)
	return get_json_result(True, "Request method is not valid, MUST be POST", 404)

def insert_into_db(file_path, file_name):
	parser = CsvQuestions(file_path, file_name)
	parser.insert_into_mongodb()

def delete_file_if_exist(file_path):
	try:
		if os.path.exists(file_path):
			os.remove(file_path)
			log_info("File Exists, and deleted Successfully: ")
	except Exception as e:
		log_info("Exception, trying to delete file: " + str(e))

def allowed_file(filename):
	return '.' in filename and \
		filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def slugify(text):
	text = text.strip()  # Removed starting and ending spaces
	words = text.split(' ')
	words = [word.lower() for word in words]
	return "-".join(words)

@app.before_request
def start_timer():
	g.start = time.time()

@app.after_request
def after_request(response):
	""" Logging after every request. """
	if response.status_code != 500:
		now = time.time()
		duration = str(round(now - g.start, 2))
		ts = strftime('[%Y-%b-%d %H:%M]')
		logger.info('%s %s %s %s %s %s %s',
					ts,
					duration,
					request.remote_addr,
					request.method,
					request.scheme,
					request.full_path,
					response.status
				)
	response.headers.add('Access-Control-Allow-Origin', '*')
	response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
	response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
	return response

def set_query_parameter(url, params_name, params_value):

	try:
		scheme, netloc, path, query_string, fragment = urlsplit(url)
		query_params = parse_qs(query_string)

		for param_name, param_value in zip(params_name, params_value):
			query_params[param_name] = [param_value]
		new_query_string = urlencode(query_params, doseq=True)

		return urlunsplit((scheme, netloc, path, new_query_string, fragment))
	except Exception as e:
		log_info("Exception in setting params: " + str(e))
		return url

def get_json_result(is_error, msg, status_code, data_=None):
	if data_ is None:
		data_ = dict()
	return jsonify(
			isError=is_error,
			message=msg,
			statusCode=status_code,
			data=data_), 200

def date_to_string(date):
	return date.strftime("%Y-%m-%d %H:%M:%S")

def date_to_string_file_name(date):
	return date.strftime("%Y%m%d_%H%M%S")

def log_info(message):
	ts = strftime('[%Y-%b-%d %H:%M]')
	logger.info(
			'%s %s ',
			ts,
			message
		)


# import sys
# sys.stdin = open('input/구명보트.txt', 'r')

from collections import deque

def solution(people, limit):
    boat = 0
    people.sort()

    q = deque(people)
    while q:
        if len(q) >= 2:
            if q[0] + q[-1] <= limit:
                q.pop()
                q.popleft()
                boat += 1
            elif q[0] + q[-1] > limit:
                q.pop()
                boat += 1
        else:
            if q[0] <= limit:
                q.pop()
                boat += 1
    return boat


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import requests

from .base import BaseChannel
from channels.exceptions import HttpError

class YoChannel(BaseChannel):
    url = "https://api.justyo.co/yo/"

    def __init__(self, api_token, username=None):
        self.api_token = api_token
        self.username = username

    def send(self, message, fail_silently=False, options=None):
        payload = {
            "api_token": self.api_token
        }

        if self.username is not None:
            payload["username"] = self.username

        if options is not None and "yo" in options:
            options = options["yo"]
            if "username" in options:
                payload["username"] = options["username"]
            if "link" in options:
                payload["link"] = options["link"]
            if "location" in options:
                payload["location"] = options["location"]

        try:
            response = requests.post(self.url, data=payload)
            if response.status_code != requests.codes.ok:
                raise HttpError(response.status_code, response.text)
        except:
            if not fail_silently:
                raise


import scipy.io.wavfile as wav
import os
import sys
import sounddevice as sd
import scipy.io.wavfile as wav
import numpy as np
import resampy

def trimSound(data, fs, start, end):
    start_frame = (int)(start * fs)
    end_frame = len(data)
    if end is not None:
        end_frame = (int)(end * fs)
    return data[start_frame:end_frame]

def play(data, fs=None, start=0, end=None, speed=1):
    if type(data) == type('Hello') and fs == None:
        data_load, fs = load(data)
        play(data_load, fs, start=start, end=end, speed=speed)
    else:
        data_trim = trimSound(data, fs, start, end)
        fs = (int)(fs * speed)
        # data_trim = data_trim * (5000 / np.std(data_trim))
        wav.write('.temp_aud.wav', fs, data_trim)
        if sys.platform == 'darwin':
            os.system('play .temp_aud.wav 2> /dev/null')
        else:
            os.system('aplay .temp_aud.wav 2> /dev/null')
        os.system('rm .temp_aud.wav')

def record(duration, fs=16000, channels=2):
    myrec = sd.rec(int(duration * fs), samplerate=fs, channels=1)
    sd.wait()
    return myrec, fs

def record_nowait(duration, fs=16000, channels=2):
    # Starts recording but returns instantly. The data will be written even after returning
    myrec = sd.rec(int(duration * fs), samplerate=fs, channels=1)
    return myrec, fs

def save(filename, data, fs):
    wav.write(filename, fs, data)

def load(filename, start=0, end=None):
    fs, data = wav.read(filename)
    data = trimSound(data, fs, start, end)
    return data, fs

def resample(data, old_fs, new_fs):
    return resampy.resample(data, old_fs, new_fs), new_fs

def stereo2mono(data, fs=None):
    if fs==None and type(data)==type('Hello'):
        data, fs = load(data)
    new_data = (data[:, 0] + data[:, 1]) / 2
    return new_data, fs


###############################################
print("Importing Libraries.......")
import numpy as np
import os
import six.moves.urllib as urllib
import sys
import tarfile
import tensorflow as tf
import zipfile
import cv2
import json
import csv
from distutils.version import StrictVersion
from collections import defaultdict
from io import StringIO
from matplotlib import pyplot as plt
from PIL import Image
import stage_predict
sys.path.append("..")
from object_detection.utils import ops as utils_ops
if StrictVersion(tf.__version__) < StrictVersion('1.12.0'):
  raise ImportError('Please upgrade your TensorFlow installation to v1.12.*.')
from object_detection.utils import label_map_util
from object_detection.utils import visualization_utils as vis_util
print("Libraries Imported....")
###############################################
flags=tf.app.flags
flags.DEFINE_string('model_dir','','Path to directory with frozen graph')
flags.DEFINE_string('nn_dir','','Path to directory with neural network weights. Weights should follow name convention.')
flags.DEFINE_bool('two_stage_classification',False,'Wether to do a single stage classification or two stage classification')
flags.DEFINE_string('roi_label_path','','Path to roi label map')
flags.DEFINE_string('stg_label_path','','Path to stage level label map')
flags.DEFINE_string('out_images_dir','','Path to Directory with analysed images')
flags.DEFINE_string('out_json_dir','','Path to directory where you want to dump the JSON files')
flags.DEFINE_string('csv_path','','Path to csv of format s.no,image_path,report_path')
flags.DEFINE_string('out_csv_dir','','Directory where compare_json.csv will be created. compare_json.csv is of format (analysed_json_path,actual_json_path)')
flags.DEFINE_float('roi_threshold',0.3,'Threshold for roi detections')
flags.DEFINE_float('stage_threshold',0.2,'Threshold for stage wise detections')
flags.DEFINE_bool('save_analysed_images',False,'Wether to save analysed images. Argument out_images_dir required if this is True')
FLAGS=flags.FLAGS

###############################################
def load_csv(CSV_PATH):
  """
  Loads CSV containing image_path and reports_path. Format (s.no,image_path,report_path)
  """
  print("Loading CSV file...")
  TEST_IMAGE_PATHS=[]
  ACTUAL_REPORTS_PATHS=[]
  with open(CSV_PATH) as csvfile:
    f_csv = csv.reader(csvfile)
    headers = next(f_csv)
    for row in f_csv:
      TEST_IMAGE_PATHS.append(row[1])
      ACTUAL_REPORTS_PATHS.append(row[2])

  return TEST_IMAGE_PATHS,ACTUAL_REPORTS_PATHS
###############################################
print("Loading detection graph and label maps...........")
MODEL_NAME=FLAGS.model_dir
PATH_TO_FROZEN_GRAPH = os.path.join(MODEL_NAME,'frozen_inference_graph.pb')
detection_graph = tf.Graph()
with detection_graph.as_default():
  od_graph_def = tf.GraphDef()
  with tf.gfile.GFile(PATH_TO_FROZEN_GRAPH, 'rb') as fid:
    serialized_graph = fid.read()
    od_graph_def.ParseFromString(serialized_graph)
    tf.import_graph_def(od_graph_def, name='')
PATH_TO_LABELS=FLAGS.roi_label_path
PATH_TO_STAGE_LABELS=FLAGS.stg_label_path
category_index = label_map_util.create_category_index_from_labelmap(PATH_TO_LABELS, use_display_name=True)
if FLAGS.two_stage_classification==True:
  category_index_stages=label_map_util.create_category_index_from_labelmap(PATH_TO_STAGE_LABELS, use_display_name=True)

TEST_IMAGE_PATHS,ACTUAL_REPORTS_PATHS = load_csv(FLAGS.csv_path)
print("Loading Done..........")
#############HELPER FUNCTIONS###################

def load_image_into_numpy_array(image):
  (im_width, im_height) = image.size
  return np.array(image.getdata()).reshape(
      (im_height, im_width, 3)).astype(np.uint8)

def run_inference_for_single_image(image, graph):
  with graph.as_default():
    with tf.Session() as sess:
      ops = tf.get_default_graph().get_operations()
      all_tensor_names = {output.name for op in ops for output in op.outputs}
      tensor_dict = {}
      for key in [
          'num_detections', 'detection_boxes', 'detection_scores',
          'detection_classes', 'detection_masks'
      ]:
        tensor_name = key + ':0'
        if tensor_name in all_tensor_names:
          tensor_dict[key] = tf.get_default_graph().get_tensor_by_name(
              tensor_name)
      if 'detection_masks' in tensor_dict:
        detection_boxes = tf.squeeze(tensor_dict['detection_boxes'], [0])
        detection_masks = tf.squeeze(tensor_dict['detection_masks'], [0])
        real_num_detection = tf.cast(tensor_dict['num_detections'][0], tf.int32)
        detection_boxes = tf.slice(detection_boxes, [0, 0], [real_num_detection, -1])
        detection_masks = tf.slice(detection_masks, [0, 0, 0], [real_num_detection, -1, -1])
        detection_masks_reframed = utils_ops.reframe_box_masks_to_image_masks(
            detection_masks, detection_boxes, image.shape[1], image.shape[2])
        detection_masks_reframed = tf.cast(
            tf.greater(detection_masks_reframed, 0.5), tf.uint8)
        tensor_dict['detection_masks'] = tf.expand_dims(
            detection_masks_reframed, 0)
      image_tensor = tf.get_default_graph().get_tensor_by_name('image_tensor:0')
      output_dict = sess.run(tensor_dict,
                             feed_dict={image_tensor: image})
      output_dict['num_detections'] = int(output_dict['num_detections'][0])
      output_dict['detection_classes'] = output_dict[
          'detection_classes'][0].astype(np.int64)
      output_dict['detection_boxes'] = output_dict['detection_boxes'][0]
      output_dict['detection_scores'] = output_dict['detection_scores'][0]
      if 'detection_masks' in output_dict:
        output_dict['detection_masks'] = output_dict['detection_masks'][0]
  return output_dict

def return_output_dict(image_path,detection_graph):
    image = Image.open(image_path)
    image_np = load_image_into_numpy_array(image)
    image_np_expanded = np.expand_dims(image_np, axis=0)
    output_dict = run_inference_for_single_image(image_np_expanded, detection_graph)
    return image_np,output_dict

def get_detections(image_path,detection_graph,thres):
    detection_count=1
    detections=[]
    scores=[]
    stages=[]
    image_np,output_dict=return_output_dict(image_path,detection_graph)
    #im_width, im_height = image_np.shape[1],image_np.shape[0]
    for i in range(0,100):
        if output_dict['detection_scores'][i]>=thres:
            detections.append(output_dict['detection_boxes'][i])
            scores.append(output_dict['detection_scores'][i])
            stages.append(output_dict['detection_classes'][i])
    return image_np,scores,stages,detections

def single_image_single_stage_detections(image_path,detection_graph,category_index,thres_stage):
    image_np,scores,stages,detections=get_detections(image_path,detection_graph,thres_stage)
    scores=np.array(scores)
    scores=scores.reshape(-1,)
    stages=np.array(stages)
    stages=stages.reshape(-1,)
    detections=np.array(detections)
    vis_util.visualize_boxes_and_labels_on_image_array(image_np,detections,stages,scores,category_index,use_normalized_coordinates=True,min_score_thresh=thres_stage,line_thickness=3)
    return image_np,scores,stages,detections

def single_image_two_stage_detections(image_path,detection_graph,resnet_model,nn_model,category_index_stages,thres_roi,thres_stage):
    scores=[]
    stages=[]
    detections=[]
    image_np,_,_,pre_detections=get_detections(image_path,detection_graph,thres_roi)
    im_width, im_height = image_np.shape[1],image_np.shape[0]
    for detection in pre_detections:
        ymin, xmin, ymax, xmax=detection
        (left, right, top, bottom) = (int(xmin * im_width), int(xmax * im_width),int(ymin * im_height), int(ymax * im_height))
        detection_box=image_np[top:bottom,left:right,:]
        box_features=stage_predict.extract_features(detection_box,resnet_model)
        score,stage=stage_predict.predict_stage(box_features,nn_model)
        if score>=thres_stage:
          detections.append(detection)
          scores.append(score)
          stages.append(stage)
    scores=np.array(scores)
    scores=scores.reshape(-1,)
    stages=np.array(stages)
    stages=stages.reshape(-1,)
    detections=np.array(detections)
    vis_util.visualize_boxes_and_labels_on_image_array(image_np,detections,stages,scores,category_index_stages,use_normalized_coordinates=True,min_score_thresh=thres_stage,line_thickness=3)
    return image_np,scores,stages,detections

def index_to_label_name(index_map,label_index):
  for index,index_dict in index_map.items():
      if index_dict['id']==label_index:
        return index_dict['name']

def result_to_json(image_path,analysed_output_dir,scores,stages,detections,index_map,json_dir):
    """
    Function that creates json file for the prediction. Also returns analysed_json_path for furthur use
    """
    out_dict={}
    image_name=image_path.rsplit('/', 1)[-1]
    out_dict["name"]=image_name
    out_dict["number_of_detections"]=detections.shape[0]
    out_dict["author"]='MachineLearning : Logy.AI'
    detection_list=[]
    for i,detection in enumerate(detections):
        detection_dict={}
        ymin, xmin, ymax, xmax=detection
        detection_dict["xmin"]=str(xmin)
        detection_dict["xmax"]=str(xmax)
        detection_dict["ymin"]=str(ymin)
        detection_dict["ymax"]=str(ymax)
        detection_dict["pretemp_diction"]=index_to_label_name(index_map,stages[i])
        detection_dict["probability"]=str(scores[i])
        detection_list.append(detection_dict)
    out_dict["detections"]=detection_list
    out_dict["original_image"]=image_path
    out_dict["analysed_image"]=os.path.join(analysed_output_dir,image_name)
    analysed_json_path=os.path.join(json_dir,image_name.rsplit('.', 1)[0]+'.json')
    with open(analysed_json_path, 'w') as fp:
        json.dump(out_dict, fp)

    return analysed_json_path

#########################FUNCTION FOR SINGLE STAGE CLASSIFICATION####################
def single_stage_class():
  #Loading the resnet model
  print("STARTING SINGLE STAGE CLASSIFICATION....")
  OUT_CSV_DATA=[["analysed_json_path","actual_json_path"]]
  for i,image_path in enumerate(TEST_IMAGE_PATHS):
      image_name=image_path.rsplit('/', 1)[-1]
      analysed_image,scores,stages,detections=single_image_single_stage_detections(image_path,detection_graph,category_index,thres_stage=FLAGS.stage_threshold)
      print(image_name+"-->Number of detections="+str(detections.shape[0])+"------> Done")
      if FLAGS.save_analysed_images:
        output_dir=FLAGS.out_images_dir
        cv2.imwrite(os.path.join(output_dir,image_name),analysed_image)
      analysed_json_path=result_to_json(image_path,FLAGS.out_images_dir,scores,stages,detections,category_index,FLAGS.out_json_dir)
      data=[]
      data.append(analysed_json_path)
      data.append(ACTUAL_REPORTS_PATHS[i])
      OUT_CSV_DATA.append(data)

  print("Detections Done")
  print("Writing compare_json.csv file")
  with open(os.path.join(FLAGS.out_csv_dir,'compare_json.csv'), 'w', newline='') as file:
    writer = csv.writer(file)
    writer.writerows(OUT_CSV_DATA)

#########################FUNCTION FOR TWO STAGE CLASSIFICATION####################
def two_stage_class():
  #Loading the resnet model
  print("STARTING TWO STAGE CLASSIFICATION....")
  print("Loading the resnet model for feature extraction........")
  resnet_model=stage_predict.load_resnet()
  print("Loaded the resnet model")
  #Loading the NN model
  print("Loading the Neural Network model......")
  NN_PATH=FLAGS.nn_dir
  nn_model=stage_predict.load_NN(NN_PATH)
  print("Loaded the Neural Network model.......")
  OUT_CSV_DATA=[["analysed_json_path","actual_json_path"]]
  print("Starting image analysis...............")
  for i,image_path in enumerate(TEST_IMAGE_PATHS):
      image_name=image_path.rsplit('/', 1)[-1]
      analysed_image,scores,stages,detections=single_image_two_stage_detections(image_path,detection_graph,resnet_model,nn_model,category_index_stages,thres_roi=FLAGS.roi_threshold,thres_stage=FLAGS.stage_threshold)
      print(image_name+"-->Number of detections="+str(detections.shape[0])+"------> Done")
      if FLAGS.save_analysed_images:
        output_dir=FLAGS.out_images_dir
        cv2.imwrite(os.path.join(output_dir,image_name),analysed_image)
      analysed_json_path=result_to_json(image_path,FLAGS.out_images_dir,scores,stages,detections,category_index_stages,FLAGS.out_json_dir)
      data=[]
      data.append(analysed_json_path)
      data.append(ACTUAL_REPORTS_PATHS[i])
      OUT_CSV_DATA.append(data)
  print("Detections Done")
  print("Writing compare_json.csv file")
  with open(os.path.join(FLAGS.out_csv_dir,'compare_json.csv'), 'w', newline='') as file:
    writer = csv.writer(file)
    writer.writerows(OUT_CSV_DATA)

def main(_):
  if FLAGS.two_stage_classification==True:
    two_stage_class()
  else:
    single_stage_class()

if __name__ == "__main__":
      tf.app.run()
#############################################################


from collections import defaultdict
n = int(input())
d = list(map(int, input().split()))
m = int(input())
t = list(map(int, input().split()))
cnt_d, cnt_t = defaultdict(int), defaultdict(int)
for dif in d:
    cnt_d[dif] += 1

for t_dif in t:
    cnt_t[t_dif] += 1

for k, v in cnt_t.items():
    if v > cnt_d[k]:
        print("NO")
        exit()

print("YES")


from django.shortcuts import render, HttpResponse, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.contrib.auth.models import User
from . import models
# Create your views here.

def home(request):
    posts = models.Post.objects.all().order_by('-date_posted')
    return render(request, 'blog/blogs_headings.html', {'posts': posts})

def displayBlogContent(request):
    id_val = int(request.POST['id_value'])
    post = models.Post.objects.get(id=id_val)
    return render(request, 'blog/blog_content.html', {'post': post})

@login_required
def createNewBlog(request):
    if(request.method == "POST"):
        title_value = request.POST['title_value']
        content_value = request.POST['content_value']
        user_id = User.objects.get(username=request.user.username).id
        post_obj = models.Post(
            title=title_value, content=content_value, author_id=user_id)  # , user=user_value)
        post_obj.save()
        messages.success(request, 'Blog successfully posted !')
        return redirect('create-blog')
    else:
        return render(request, 'blog/create_blog.html')


#! /usr/bin/python3

import fcntl
import inspect
import os
import subprocess as sproc
import sys
import tkinter as tk
from time import sleep, time

last_time = time()

def play(hrac, oponent, stdout1, stdin1, stdout2, stdin2, score, root, color):
    score_o = score
    stdin1.write(b"get\n")
    stdin1.flush()
    last_time = time()
    line = ""
    while not line:
        if time() - last_time > 6 and hrac != "you": break
        try:
            line = stdout1.readline()
        except IOError:
            sleep(0.1)
    if time() - last_time > 5 and hrac != "you":
        print("  Hrac '%s' to nestihl v casovem limite." % hrac)
        print("  Vyhral hrac '%s'." % oponent)
        return [True, score, False]
    x, y = [int(item) for item in line.split()]
    stdin2.write(b"post %d %d\n" % (x, y))
    stdin2.flush()
    if x < 0 or x >= 2 * n + 1 or y < 0 or y >= n + x % 2 or root.plan[x][y]["bg"] in ["red", "blue"]:
        print("  Neplatny tah hrace '%s' - [%d, %d]" % (hrac, x, y))
        print("  Vyhrava hrac '%s'." % oponent)
        return [True, score, False]
    if x % 2:
        try:
            if root.plan[x + 1][y]["bg"] in ["red", "blue"] and \
                            root.plan[x - 1][y]["bg"] in ["red", "blue"] and \
                            root.plan[x][y + 1]["bg"] in ["red", "blue"] and \
                            x > 0 and y > -1:
                score += 1
        except IndexError:
            pass
        try:
            if root.plan[x + 1][y - 1]["bg"] in ["red", "blue"] and \
                            root.plan[x - 1][y - 1]["bg"] in ["red", "blue"] and \
                            root.plan[x][y - 1]["bg"] in ["red", "blue"] and \
                            x > 0 and y > 0:
                score += 1
        except IndexError:
            pass
    else:
        try:
            if root.plan[x - 1][y]["bg"] in ["red", "blue"] and \
                            root.plan[x - 1][y + 1]["bg"] in ["red", "blue"] and \
                            root.plan[x - 2][y]["bg"] in ["red", "blue"] and \
                            x > 1 and y > -1:
                score += 1
        except IndexError:
            pass
        try:
            if root.plan[x + 1][y]["bg"] in ["red", "blue"] and \
                            root.plan[x + 1][y + 1]["bg"] in ["red", "blue"] and \
                            root.plan[x + 2][y]["bg"] in ["red", "blue"] and \
                            x > -2 and y > -1:
                score += 1
        except IndexError:
            pass
    root.plan[x][y].configure(bg=color)

    return [False, score, score != score_o]

command = []
while True:
    while not command: command = input(" <- :").split()
    if command[0] == "?":
        if len(command) > 1:
            try:
                print("Napoveda k prikazu '" + command[1] + "':\n\t" +
                      {"?": "Vypise seznam vsech prikazu.\n\tPokud je zadan parametr vypise informace o danem prikazu.",
                       "l": "Vypise seznam vsech hracu.\n\tPokud je zadan parametr, tak budou vypsani hraci jejichz "
                            "jmeno obsahuje dany retezec.",
                       "p": "Spusti hru.",
                       "e": "Skonci " + sys.argv[0] + ".",
                       "q": "Skonci " + sys.argv[0] + "."}[command[1]] + "\n")
            except KeyError:
                print("Prikaz '" + command[1] + "' neexistuje.\n")
        else:
            print("Mozne prikazy:\n"
                  "\t? : Zobrazi tuto napovedu.\n"
                  "\tl : Vypise seznam hracu.\n"
                  "\tp : Spusti hru.\n"
                  "\te : Skonci " + sys.argv[0] + ".\n"
                                                  "\tq : Skonci " + sys.argv[0] + ".\n")
    elif command[0] in ["e", "q"]:
        break
    elif command[0] == "l":
        current = os.path.dirname(inspect.getfile(inspect.currentframe()))
        sproc.Popen(["ls", current + "/../Players"]).communicate()
    elif command[0] == "p":
        try:
            score1 = 0
            score2 = 0
            current = os.path.dirname(inspect.getfile(inspect.currentframe()))
            hraci = sproc.Popen(["ls", current + "/../Players"], stdout=sproc.PIPE).communicate()[0].decode().split(
                "\n")
            hrac1 = input("Vlozte jmeno prvniho hrace: ")
            while True:
                while not hrac1:
                    hrac1 = input("Jmeno hrace je povinne: ")
                if hrac1 in ["ja", "me", "human"]:
                    hrac1 = "you"
                if hrac1 not in hraci:
                    print(hrac1 + " nebyl nalezen.\n")
                    continue
                break
            while True:
                hrac2 = input("Vlozte jmeno druheho hrace: ")
                while not hrac2:
                    hrac2 = input("Jmeno hrace je povinne: ")
                if hrac2 in ["ja", "me", "human"]:
                    hrac2 = "you"
                if hrac2 not in hraci:
                    print(hrac2 + " nebyl nalezen.\n")
                    continue
                break
            n = int(input("zadejte rozmer: "))
            root = tk.Tk()
            root.title("%s vs %s" % (hrac1, hrac2))
            root.plan = []
            for i in range(2 * n + 1):
                root.plan.append([])
                if i % 2:
                    for j in range(n + 1):
                        root.plan[-1].append(tk.Button(master=root, bg="white", height=5))
                        root.plan[-1][-1].grid(column=j * 2, row=i)
                else:
                    for j in range(n):
                        root.plan[-1].append(tk.Button(master=root, bg="white", width=5))
                        root.plan[-1][-1].grid(column=j * 2 + 1, row=i)
            proc1 = sproc.Popen(current + "/../Players/" + hrac1, stdout=sproc.PIPE, stdin=sproc.PIPE,
                                stderr=sys.stderr, shell=True)
            proc1.stdin.write(b"init %d\n" % n)
            proc1.stdin.flush()
            proc2 = sproc.Popen(current + "/../Players/" + hrac2, stdout=sproc.PIPE, stdin=sproc.PIPE,
                                stderr=sys.stderr, shell=True)
            proc2.stdin.write(b"init %d\n" % n)
            proc2.stdin.flush()
            root.update()
            fl1 = fcntl.fcntl(proc1.stdout, fcntl.F_GETFL)
            fcntl.fcntl(proc1.stdout, fcntl.F_SETFL, fl1 | os.O_NONBLOCK)
            fl2 = fcntl.fcntl(proc2.stdout, fcntl.F_GETFL)
            fcntl.fcntl(proc2.stdout, fcntl.F_SETFL, fl2 | os.O_NONBLOCK)
            while True:
                out = [False, score1, True]
                while out[2]:
                    out = play(hrac1, hrac2, proc1.stdout, proc1.stdin, proc2.stdout, proc2.stdin, score1, root, "red")
                    score1 = out[1]
                    if out[2]:
                        done = True
                        for line in root.plan:
                            for side in line:
                                done = done and (side["bg"] in ["red", "blue"])
                        if done:
                            if score1 == score2:
                                print("Byla to ramiza, oba hraci skoncili s %d body." % score1)
                            else:
                                if score1 > score2:
                                    hrac = hrac1
                                    score = score1
                                elif score1 < score2:
                                    hrac = hrac2
                                    score = score2

                                print("Vyhral hrac '%s' s %d body." % (hrac, score))
                            out[0] = True
                            out[2] = False
                    if "you" not in [hrac1, hrac2]:
                        input()
                if out[0]:
                    break
                try:
                    root.update()
                except tk.TclError:
                    break
                out = [False, score2, True]
                while out[2]:
                    out = play(hrac2, hrac1, proc2.stdout, proc2.stdin, proc1.stdout, proc1.stdin, score2, root, "blue")
                    score2 = out[1]
                    if out[2]:
                        done = True
                        for line in root.plan:
                            for side in line:
                                done = done and (side["bg"] in ["red", "blue"])
                        if done:
                            if score1 == score2:
                                print("Byla to ramiza, oba hraci skoncili s %d body." % score1)
                            else:
                                if score1 > score2:
                                    hrac = hrac1
                                    score = score1
                                elif score1 < score2:
                                    hrac = hrac2
                                    score = score2

                                print("Vyhral hrac '%s' s %d body." % (hrac, score))
                            out[0] = True
                            out[2] = False

                    if "you" not in [hrac1, hrac2]:
                        input()
                if out[0]:
                    break
                try:
                    root.update()
                except tk.TclError:
                    break

            proc1.stdin.close()
            proc1.stdout.close()
            proc1.wait()
            proc2.stdin.close()
            proc2.stdout.close()
            proc2.wait()
            try:
                root.destroy()
            except tk.TclError:
                pass
        except KeyboardInterrupt:
            try:
                root.destroy()
            except NameError:
                pass
            except tk.TclError:
                pass
        print("Vysledne skore: %d:%d" % (score1, score2))

    else:
        print("Prikaz '" + command[0] + "' neexistuje.\n")
    command = []


# -*- coding: utf-8 -*-

'''
DELLINGRwa: DELLINGR work around to use VDR as PVR without XINE.
            Until the VNSI interface between KODI & VDR works.

Programming: Andreas Ulrich
             <http://erasand.jimdo.com/>, <ulrich3110@gmail.com>

This file is part of DELLINGRwa.

DELLINGRwa is free software: You can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.

DELLINGRwa is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with the TradeControl. If not, see
<http://www.gnu.org/licenses/>.
'''

import datetime

class VdrCheck():
    '''
    VdrCheck(): Objet to check the timers of VDR.
    '''

    def __init__(self):
        '''
        Initialization
        timers_file = path to the timers.conf file, default = ''
        timers_list = list with timer informations: [(start, end),  ]
                      start, end = datetime format
                      default = []
        '''
        self.timers_file = ''
        self.timers_list = []

    def get_start_time(self):
        '''
        Get next timer start, return (year, month, day,
        hour, minute) or None.
        '''
        now = datetime.datetime.today()
        start_time = None
        for timer in self.timers_list:
            if timer[0] >= now and not start_time:
                # 1st timer found
                start_time = timer[0]
            elif timer[0] >= now and timer[0] <= start_time:
                # new next timer found
                start_time = timer[0]
        if start_time:
            # get integers
            year = int(start_time.year)
            month = int(start_time.month)
            day = int(start_time.day)
            hour = int(start_time.hour)
            minute = int(start_time.minute)
            return((year, month, day, hour, minute))
        else:
            # No start timer found
            return(None)

    def get_status(self):
        '''
        Get the status of VDR, return 'idle' or 'record'.
        '''
        now = datetime.datetime.today()
        status = 'idle'
        for timer in self.timers_list:
            if timer[0] <= now and timer[1] >= now:
                # now is between a start and a end time
                status = 'record'
        return(status)

    def get_timers(self, minutes):
        '''
        Get timers from self.timers_file,
        fill self.timers_list with found timers.
        Add/subtract reserve minutes to found timers.
        minutes = integer
        '''
        # Read file
        try:
            file_ = open(self.timers_file, 'r')
            timers = []
            for row in file_:
                timers.append(row)
            file_.close()
        except Exception as err_txt:
            print()
            print(err_txt)
            timers = []
        # Reserve time at start and end.
        reserve = datetime.timedelta(0, minutes * 60)
        # fill timers_list
        try:
            for row in timers:
                # split row
                split_list = row.split(':')
                # yyyy-mm-dd
                start_yyyy_mm_dd = split_list[2]
                # hhmm
                start_hh_mm = split_list[3]
                # hhmm
                end_hh_mm = split_list[4]
                # get start values, integers
                start_year = int(start_yyyy_mm_dd[:4])
                start_month = int(start_yyyy_mm_dd[5:7])
                start_day = int(start_yyyy_mm_dd[8:10])
                start_hour = int(start_hh_mm[:2])
                start_minute = int(start_hh_mm[2:4])
                # start in datetime format
                start_datetime = datetime.datetime(
                    start_year,
                    start_month,
                    start_day,
                    start_hour,
                    start_minute,
                )
                start_datetime = start_datetime - reserve
                # get end values, integers
                end_hour = int(end_hh_mm[:2])
                end_minute = int(end_hh_mm[2:4])
                # end in datetime format
                end_datetime = datetime.datetime(
                    start_year,
                    start_month,
                    start_day,
                    end_hour,
                    end_minute,
                    0,
                )
                end_datetime = end_datetime + reserve
                # control is end date one day later
                delta = end_datetime - start_datetime
                if delta.days == -1:
                    # end date ist 1 day later
                    end_datetime = end_datetime + datetime.timedelta(1)
                self.timers_list.append((start_datetime, end_datetime))
        except ValueError as err_txt:
            print()
            print(err_txt)


from django.conf import settings
import re
import hashlib
import json
import hmac
import requests
from xml.etree import ElementTree as et

def get_nonce_str():
    base_str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    random_str = ''.join(random.sample(base_str, 32))
    return random_str

class WeChatJSAPIPay(object):
    def __init__(self, order_num, body, total_fee, nonce_str, openid):
        """
        :param order_num: 订单编号 32字符内
        :param body: 订单信息
        :param total_fee: 订单金额 #单位为分
        :param nonce_str: 32位内随机字符串
        :param spbill_create_ip: 客户端请求IP地址
        :param openid: 客户openid
        appid = '微信appid'
        AppSecret = '微信AppSecret'
        mch_id = '微信商户id'
        api_key = '微信支付密钥'
        spbill_create_ip = '8.8.8.8' # 用户请求地址 终端IP 调用微信支付API的机器IP
        """
        self.api_key = settings.WEICHAT_PAY_API_KEY
        self.params = {
            'appid': settings.WEICHAT_PAY_APPID,
            'mch_id': settings.WEICHAT_PAY_MCHID,
            'nonce_str': nonce_str,
            'openid': openid,
            'body': str(body),
            'out_trade_no': str(order_num),
            'total_fee': str(int(total_fee)),
            'spbill_create_ip': '8.8.8.8',
            'trade_type': 'JSAPI',
            'notify_url': 'https://www.your.com/wxnotifyurl',# 支付回调地址
        }

        self.WxPay_request_url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'  # 微信请求url
        self.error = None

    def key_value_url(self, value):
        """
        将将键值对转为 key1=value1&key2=value2
        """
        string_sign = ''
        for k in sorted(value.keys()):
            string_sign += "{0}={1}&".format(k, value[k])
        # print('键值对：',string_sign)
        return string_sign

    def get_sign(self, params):
        """
        生成sign 签名
        """
        stringA = self.key_value_url(params)
        stringSignTemp = stringA + 'key=' + self.api_key  # APIKEY, API密钥，需要在商户后台设置
        # print('最终键值对：',stringSignTemp)
        # sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper()
        sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper()
        # k = hashlib.sha256()
        # k.update(sign.encode())
        # j = hmac.new(sign.encode(), digestmod=hashlib.sha256)
        # print(j.hexdigest().upper())
        # print(k.hexdigest().upper())
        # sign = j.hexdigest().upper()
        # print('生成签名sign：',sign)
        params['sign'] = sign

    def get_req_xml(self):
        """
        拼接XML
        """
        self.get_sign(self.params)
        xml = "<xml>"
        for k in sorted(self.params.keys()):
            xml += '<{0}>{1}</{0}>'.format(k, self.params[k])
        xml += "</xml>"
        print('得到XML：',xml)
        return xml.encode('utf-8')

    def get_prepay_id(self):
        """
        请求获取prepay_id
        """
        import requests
        from xml.etree import ElementTree as et
        xml = self.get_req_xml()
        # unifiedorderXML = requests.post('https://api.mch.weixin.qq.com/pay/unifiedorder', data=xml)
        unifiedorderXML = requests.post(self.WxPay_request_url, data=xml)
        unifiedorderXML.encoding ='utf-8'
        unifiedorderXML = unifiedorderXML.text
        print('统一下单接口的返回数据：',unifiedorderXML)
        # tree = et.parse(unifiedorderXML)
        # root = tree.getroot()
        root = et.fromstring(unifiedorderXML)
        if root.find("result_code").text != 'SUCCESS':
            self.error = "连接微信出错啦！"
            print(self.error)
        prepay_id = root.find("prepay_id").text
        self.params['prepay_id'] = prepay_id
        # self.params['package'] = 'Sign=WXPay' # APP支付场景
        self.params['package'] = 'prepay_id=%s'%prepay_id   # JSAPI支付场景
        self.params['timestamp'] = str(int(time.time()))
        self.params['signType'] = 'MD5'

    def re_finall(self):
        """
        得到prepay_id后再次签名，返回给终端参数
        """
        self.get_prepay_id()
        if self.error:
            print('有错误发生')
            return
        sign_again_params = {
            'appId': self.params['appid'],
            'timeStamp': self.params['timestamp'],
            'nonceStr': self.params['nonce_str'],
            'package': self.params['package'],
            'signType': self.params['signType']
        }
        self.get_sign(sign_again_params)
        self.params['sign'] = sign_again_params['sign']

        # 移除其他不需要返回参数
        # print('最后一步:', self.params)
        parms_keys = []
        for i in self.params.keys():
            parms_keys.append(i)
        for i in parms_keys:
            if i not in ['appid', 'mch_id', 'nonce_str', 'timestamp', 'sign', 'package', 'signType']:
                self.params.pop(i)
        # print('传给前端的parms:', self.params)
        return self.params


# uncompyle6 version 3.7.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.6.9 (default, Apr 18 2020, 01:56:04)
# [GCC 8.4.0]
# Embedded file name: /Users/ali/ownCloud/Project/python/django-aparnik-framework-project/testandbuildprojectframework/aparnik/contrib/settings/migrations/0014_auto_20190128_1350.py
# Compiled at: 2020-01-05 09:49:45
# Size of source mod 2**32: 1527 bytes
from django.db import migrations

def add_keys(apps, schema_editor):
    """
    We can't import the Post model directly as it may be a newer
    version than this migration expects. We use the historical version.
    """
    Setting = apps.get_model('settings', 'Setting')
    key = ''
    try:
        key = 'MANAGER_PRODUCT_ID'
        Setting.objects.get(key=key)
    except Exception:
        Product = apps.get_model('products', 'Product')
        ContentType = apps.get_model('contenttypes', 'ContentType')
        product = Product.objects.create(price_fabric=1,
          title='محصول مدیریتی',
          is_show_only_for_super_user=True,
          aparnik_bon_return_value=1,
          maximum_use_aparnik_bon_value=1,
          aparnik_bon_return_expire_value=0)
        new_ct = ContentType.objects.get_for_model(Product)
        Product.objects.filter(polymorphic_ctype__isnull=True).update(polymorphic_ctype=new_ct)
        Setting.objects.create(title='آی دی محصول مدیریتی',
          key=key,
          value=(str(product.id)),
          value_type='i',
          is_show=False,
          is_variable_in_home=False)

class Migration(migrations.Migration):
    dependencies = [
     ('settings', '0013_auto_20190123_1447')]
    operations = [
     migrations.RunPython(add_keys, reverse_code=(migrations.RunPython.noop))]


#!/usr/bin/env python
#encoding: utf-8
import sys
sys.path.append("../../")
from dictionary_tree.dictionary.dictTrees.trees.treeArrayList \
        import TreeArrayListNode

class TreeNodeMod(TreeArrayListNode):
    def __init__(self, info):
        super().__init__(info)
        self.realFather = None


"""cache_to_disk: Cache the results of functions persistently on disk

Original Work, Copyright (c) 2018 Stewart Renehan, MIT License
    Author: https://github.com/sarenehan
    Project: https://github.com/sarenehan/cache_to_disk

Modifications:
    Author: https://github.com/mzpqnxow
    Project: https://github.com/mzpqnxow/cache_to_disk/tree/feature/nocache

    This modified version adds the following:
        - Accounting of hits, misses and nocache events
        - cache_info(), cache_clear(), cache_size(), cache_get_raw() interfaces accessible
          via the function itself for convenience
        - NoCacheCondition exception, simple interface for a user to prevent a
          specific function result to not be cached, while still passing a return
          value to the caller
        - Minor refactoring of the decorator, for easier reading
        - Minor refactoring of delete_old_disk_caches(), to reduce logical blocks
          and depth of indentation
        - Default cache age value (DEFAULT_CACHE_AGE)
        - Special unlimited age value (UNLIMITED_CACHE_AGE)
        - Use of logging module (but defaulting to NullAdapter)
        - Minor PEP8 / cosmetic changes
        - Minor cosmetic changes to file path generation (use of os.path.join, a constant
          for the directory/file path)
        - Support getting cache directory or filename from environment:
            Cache metadata filename: $DISK_CACHE_FILENAME
            Base directory for cache files: $DISK_CACHE_DIR
        - Expansion of shell variables and tilde-user values for directories/files
"""
# Standard Library
import json
import logging
import os
import pickle
import warnings
from collections import namedtuple
from copy import deepcopy
from datetime import datetime
from os import getenv
from os.path import (
    dirname,
    exists as file_exists,
    expanduser, expandvars, getmtime,
    isfile,
    join as join_path,
    realpath)

logger = logging.getLogger(__name__)

if logger.handlers is None:
    # Don't log unless user explicitly adds a handler
    logger.addHandler(logging.NullHandler())

MAX_PICKLE_BYTES = 2 ** 31 - 1
DISK_CACHE_DIR = expanduser(expandvars(
    getenv('DISK_CACHE_DIR', join_path(dirname(realpath(__file__)), 'disk_cache'))))
DISK_CACHE_FILE = expanduser(expandvars(join_path(
    DISK_CACHE_DIR, getenv('DISK_CACHE_FILENAME', 'cache_to_disk_caches.json'))))

# Specify 0 for cache age days to keep forever; not recommended for obvious reasons
UNLIMITED_CACHE_AGE = 0
DEFAULT_CACHE_AGE = 7

_TOTAL_NUMCACHE_KEY = 'total_number_of_cache_to_disks'

# Run-time cache data, stolen from Python functools.lru_cache implementation
# Events resulting in nocache are cache misses that complete, but instruct cache_to_disk to
# not store the result. Useful, for example, in a function that makes a network request and
# experiences a failure that is considered likely to be temporary. This is accomplished in
# the user function by raising NoCacheCondition
_CacheInfo = namedtuple('CacheInfo', ['hits', 'misses', 'nocache'])

# This is probably unnecessary ...
# logger.debug('cache_to_disk package loaded; using DISK_CACHE_DIR=%s',
#             os.path.relpath(DISK_CACHE_DIR, '.'))

class NoCacheCondition(Exception):
    """Custom exception for user function to raise to prevent caching on a per-call basis

    The function_value kwarg can be set as a kwarg to return a value other than None to the
    original caller

    Example
    -------
    The following contrived example will return a value to the caller but avoids it being
    cached. In this example, a socket exception is considered a failure, but there is some
    value in returning a partial response to the caller in cases such as SIGPIPE/EPIPE in
    the read loop

    On a socket exception, the function will effectively return either an empty bytes
    buffer or a bytes buffer with partial response data, depending on where the network
    exception occurred

    @cache_to_disk(7)
    def network_query(hostname, port, query):
        response = b''
        try:
            socket = tcp_connect(hostname)
            socket.send(query)
            while True:
                # Build the response incrementally
                buf = read_bytes(socket, 1024)
                if buf is None:
                    break
                response += buf
        except socket.error:
            raise NoCacheCondition(function_value=buf)

        return response
    """
    __slots__ = ['function_value']

    def __init__(self, function_value=None):
        self.function_value = function_value
        logger.info('NoCacheCondition caught in cache_to_disk')

def write_cache_file(cache_metadata_dict):
    """Dump an object as JSON to a file"""
    with open(DISK_CACHE_FILE, 'w') as f:
        return json.dump(cache_metadata_dict, f)

def load_cache_metadata_json():
    """Load a JSON file, create it with empty cache structure if it doesn't exist"""
    try:
        with open(DISK_CACHE_FILE, 'r') as f:
            return json.load(f)
    except FileNotFoundError:
        write_cache_file({_TOTAL_NUMCACHE_KEY: 0})
        return {_TOTAL_NUMCACHE_KEY: 0}

def ensure_dir(directory):
    """Create a directory tree if it doesn't already exist"""
    if not file_exists(directory):
        os.makedirs(directory)
        write_cache_file({_TOTAL_NUMCACHE_KEY: 0})

def pickle_big_data(data, file_path):
    """Write a pickled Python object to a file in chunks"""
    bytes_out = pickle.dumps(data, protocol=4)
    with open(file_path, 'wb') as f_out:
        for idx in range(0, len(bytes_out), MAX_PICKLE_BYTES):
            f_out.write(bytes_out[idx:idx + MAX_PICKLE_BYTES])

def unpickle_big_data(file_path):
    """Return a Python object from a file containing pickled data in chunks"""
    try:
        with open(file_path, 'rb') as f:
            return pickle.load(f)
    except Exception:  # noqa, pylint: disable=broad-except
        bytes_in = bytearray(0)
        input_size = os.path.getsize(file_path)
        with open(file_path, 'rb') as f_in:
            for _ in range(0, input_size, MAX_PICKLE_BYTES):
                bytes_in += f_in.read(MAX_PICKLE_BYTES)
        return pickle.loads(bytes_in)

def get_age_of_file(filename, unit='days'):
    """Return relative age of a file as a datetime.timedelta"""
    age = (datetime.today() - datetime.fromtimestamp(getmtime(filename)))
    return getattr(age, unit)

def get_files_in_directory(directory):
    """Return all files in a directory, non-recursive"""
    return [
        f for f in os.listdir(directory) if
        isfile(join_path(directory, f))
    ]

def delete_old_disk_caches():
    cache_metadata = load_cache_metadata_json()
    new_cache_metadata = deepcopy(cache_metadata)
    cache_changed = False
    for function_name, function_caches in cache_metadata.items():
        if function_name == _TOTAL_NUMCACHE_KEY:
            continue
        to_keep = []
        for function_cache in function_caches:
            max_age_days = int(function_cache['max_age_days'])
            file_name = join_path(DISK_CACHE_DIR, function_cache['file_name'])
            if not file_exists(file_name):
                cache_changed = True
                continue
            if not get_age_of_file(file_name) > max_age_days != UNLIMITED_CACHE_AGE:
                to_keep.append(function_cache)
                continue
            logger.info('Removing stale cache file %s, > %d days', file_name, max_age_days)
            cache_changed = True
            os.remove(file_name)
        if to_keep:
            new_cache_metadata[function_name] = to_keep
    if cache_changed:
        write_cache_file(new_cache_metadata)

def get_disk_cache_for_function(function_name):
    cache_metadata = load_cache_metadata_json()
    return cache_metadata.get(function_name, None)

def get_disk_cache_size_for_function(function_name):
    """Return the current number of entries in the cache for a function by name"""
    function_cache = get_disk_cache_for_function(function_name)
    return None if function_cache is None else len(function_cache)

def delete_disk_caches_for_function(function_name):
    logger.debug('Removing cache entries for %s', function_name)
    n_deleted = 0
    cache_metadata = load_cache_metadata_json()
    if function_name not in cache_metadata:
        return

    functions_to_delete_cache_for = cache_metadata.pop(function_name)
    for function_cache in functions_to_delete_cache_for:
        file_name = join_path(DISK_CACHE_DIR, function_cache['file_name'])
        os.remove(file_name)
        n_deleted += 1
    logger.debug('Removed %s cache entries for %s', n_deleted, function_name)
    write_cache_file(cache_metadata)

def cache_exists(cache_metadata, function_name, *args, **kwargs):
    if function_name not in cache_metadata:
        return False, None
    new_caches_for_function = []
    cache_changed = False
    for function_cache in cache_metadata[function_name]:
        if function_cache['args'] == str(args) and (
                function_cache['kwargs'] == str(kwargs)):
            max_age_days = int(function_cache['max_age_days'])
            file_name = join_path(DISK_CACHE_DIR, function_cache['file_name'])
            if file_exists(file_name):
                if get_age_of_file(file_name) > max_age_days != UNLIMITED_CACHE_AGE:
                    os.remove(file_name)
                    cache_changed = True
                else:
                    function_value = unpickle_big_data(file_name)
                    return True, function_value
            else:
                cache_changed = True
        else:
            new_caches_for_function.append(function_cache)
    if cache_changed:
        if new_caches_for_function:
            cache_metadata[function_name] = new_caches_for_function
        else:
            cache_metadata.pop(function_name)
        write_cache_file(cache_metadata)
    return False, None

def cache_function_value(
        function_value,
        n_days_to_cache,
        cache_metadata,
        function_name,
        *args,
        **kwargs):
    if function_name == _TOTAL_NUMCACHE_KEY:
        raise Exception(
            'Cant cache function named %s' % _TOTAL_NUMCACHE_KEY)
    function_caches = cache_metadata.get(function_name, [])
    new_file_name = str(int(cache_metadata[_TOTAL_NUMCACHE_KEY]) + 1) + '.pkl'
    new_cache = {
        'args': str(args),
        'kwargs': str(kwargs),
        'file_name': new_file_name,
        'max_age_days': n_days_to_cache
    }
    pickle_big_data(function_value, join_path(DISK_CACHE_DIR, new_file_name))
    function_caches.append(new_cache)
    cache_metadata[function_name] = function_caches
    cache_metadata[_TOTAL_NUMCACHE_KEY] = int(cache_metadata[_TOTAL_NUMCACHE_KEY]) + 1
    write_cache_file(cache_metadata)

def cache_to_disk(n_days_to_cache=DEFAULT_CACHE_AGE):
    """Cache to disk"""
    if n_days_to_cache == UNLIMITED_CACHE_AGE:
        warnings.warn('Using an unlimited age cache is not recommended', stacklevel=3)
    if isinstance(n_days_to_cache, int):
        if n_days_to_cache < 0:
            n_days_to_cache = 0
    elif n_days_to_cache is not None:
        raise TypeError('Expected n_days_to_cache to be an integer or None')

    def decorating_function(original_function):
        wrapper = _cache_to_disk_wrapper(original_function, n_days_to_cache, _CacheInfo)
        return wrapper

    return decorating_function

def _cache_to_disk_wrapper(original_func, n_days_to_cache, _CacheInfo):  # noqa, pylint: disable=invalid-name
    hits = misses = nocache = 0

    def wrapper(*args, **kwargs):
        nonlocal hits, misses, nocache
        cache_metadata = load_cache_metadata_json()
        already_cached, function_value = cache_exists(
            cache_metadata, original_func.__name__, *args, **kwargs)
        if already_cached:
            logger.debug('Cache HIT on %s (hits=%s, misses=%s, nocache=%s)',
                         original_func.__name__, hits, misses, nocache)
            hits += 1
            return function_value

        logger.debug('Cache MISS on %s (hits=%s, misses=%s, nocache=%s)',
                     original_func.__name__, hits, misses, nocache)
        logger.debug(' -- MISS ARGS:    (%s)', ','.join(
            [str(arg) for arg in args]))
        logger.debug(' -- MISS KWARGS:  (%s)', ','.join(
            ['{}={}'.format(str(k), str(v)) for k, v in kwargs.items()]))
        misses += 1

        try:
            function_value = original_func(*args, **kwargs)
        except NoCacheCondition as err:
            nocache += 1
            logger.debug('%s() threw NoCacheCondition exception; no new cache entry', original_func.__name__)
            function_value = err.function_value
        else:
            logger.debug('%s() returned, adding cache entry', original_func.__name__)
            cache_function_value(
                function_value,
                n_days_to_cache,
                cache_metadata,
                original_func.__name__,
                *args,
                **kwargs)
        return function_value

    def cache_info():
        """Report runtime cache statistics"""
        return _CacheInfo(hits, misses, nocache)

    def cache_clear():
        """Clear the cache permanently from disk for this function"""
        logger.info('Cache clear requested for %s(); %s items in cache ...', original_func.__name__, )
        delete_disk_caches_for_function(original_func.__name__)

    def cache_size():
        """Return the number of cached entries for this function"""
        return get_disk_cache_size_for_function(original_func.__name__)

    def cache_get_raw():
        """Return the raw cache object for this function as a list of dicts"""
        warnings.warn('This is an internal interface and should not be used lightly', stacklevel=3)
        return get_disk_cache_for_function(original_func.__name__)

    wrapper.cache_info = cache_info
    wrapper.cache_clear = cache_clear
    wrapper.cache_size = cache_size
    wrapper.cache_get_raw = cache_get_raw
    return wrapper

ensure_dir(DISK_CACHE_DIR)
delete_old_disk_caches()


# modusite
# Copyright (c) 2006-2010 Phil Christensen
# http://modu.bubblehouse.org
#
#

from modu.editable import define
from modu.editable.datatypes import string, relational

__itemdef__ = define.itemdef(
	__config			= dict(
		name			= 'role',
		label			= 'roles',
		category		= 'accounts',
		acl				= 'access admin',
		weight			= 1
	),

	id					= string.LabelField(
		label			= 'id:',
		size			= 10,
		weight			= -10,
		listing			= True
	),

	name			= string.StringField(
		label			= 'name:',
		size			= 60,
		maxlength 		= 255,
		weight			= 1,
		listing			= True,
		link			= True
	),

	permissions			= relational.ForeignMultipleSelectField(
		label			= 'permissions:',
		fvalue			= 'id',
		flabel			= 'name',
		ftable			= 'permission',
		ntof			= 'role_permission',
		ntof_f_id		= 'permission_id',
		ntof_n_id		= 'role_id'
	)
)


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import datetime
from danjuan import Danjuan
from fof import Fof
from indexs import index_list

class Policy(Fof):
    """ 从东方基金获取基金价格，从蛋卷获取指数估值，并加载基金净值和基金购买日志。
    """

    def __init__(self, fid):
        Fof.__init__(self, fid)
        self.buylog = {}
        self.selllog = {}
        self.index = index_list[fid]

    def init_index_pbe(self, time='all'):
        """ 获取pe/pb的通用接口，time可以为1y, 3y """
        self.trade_days = set(self.price_list.keys())
        if self.index['code'] == '':
            return None
        self.dj = Danjuan(self.index['code'], self.index['vq'])
        self.index_pbe = self.dj.init_pbe(time)
        self.trade_days = set(self.index_pbe.keys()) & self.trade_days
        return self.index_pbe

    def get_weight_pe(self, cur_pe, pe30, n=2):
        """ 获取 pe 权重，以30水位线做基准，超过30水位线则不买。否则越低越买。
            经回测上证50，此参数对购买影响不大，故取n=2
        """
        # if cur_pe > pe30 or cur_pe <= 0:
        #     return 0
        # 加强 pe 的权重，越低越买
        if cur_pe == 0:
            return 0
        return (pe30/cur_pe) ** n

    def get_weight_price(self, cur_price, avg_price, n=4):
        """ 获取 price 权重，实际采用前365天的平均价格。
            超过该价格，则不买，否则多买。
        """
        # if cur_price[1] > avg_price[1] or cur_price[1] <= 0:
        #     return 0
        # 加强 price 的权重，越低越买
        if cur_price[1] == 0:
            return 0
        return ((avg_price[1] - (cur_price[1] - cur_price[0])) / cur_price[0]) ** n

    def get_dt_price(self, dt, avgdays):
        """ 获取基金指定日期的价格
            dt 为 None，返回当天估值。
        """
        # 非今天申购，且非交易日，则不予购买。
        res = {}
        if dt is not None and dt not in self.price_list.keys():
            res['price'] = (0, 0)
            res['avg_price'] = (0, 0)
            res['max_avg_price'] = (0, 0)
            return res
        # 如果当天购买，则采用实时最新估值。
        if dt is None:
            dt = datetime.datetime.combine(datetime.date.today(), datetime.datetime.min.time())
            res['price'] = self.get_gz()
            # 如果取估值有问题，可能是假日，不申购。
            if res['price'][0] * 10 <= 0:
                res['price'] = (0, 0)
                res['avg_price'] = (0, 0)
                res['max_avg_price'] = (0, 0)
                return res
        # 否则采用当天的净值来计算
        else:
            res['price'] = self.price_list.get(dt)
        res['avg_price'] = self.get_avg_price(dt, avgdays)
        res['max_avg_price'] = res['avg_price']
        # 计算年内最大平均净值，避免由于净值逐步下跌导致申购金额减少，回测显示意义不大。
        # for i in range(1, 365):
        #     d = dt - datetime.timedelta(days=1)
        #     cur_avg_price = self.get_avg_price(d, avgdays)
        #     if res['max_avg_price'][1] < cur_avg_price[1]:
        #         res['max_avg_price'] = cur_avg_price
        return res

    def get_dt_revert(self, dt, price):
        """ 获取基金指定日期的回撤
            dt 为 None，则使用当天估值。
        """
        res = {}
        reverts = []
        edt = dt
        if dt is None:
            n = datetime.datetime.now()
            edt = datetime.datetime(n.year, n.month, n.day, 0, 0, 0)
            res['revert'] = self.get_revert(dt, price)
        else:
            res['revert'] = self.revert_list.get(dt, 0)
        for i in range(1, 360*6):
            d = edt - datetime.timedelta(days=i)
            r = self.revert_list.get(d, 0)
            if r > 0:
                reverts.append(r)
        if res['revert'] > 0:
            reverts.append(res['revert'])
            reverts.sort()
            res['revert_length'] = len(reverts)
            res['revert_water'] = round((reverts.index(res['revert']) + 1) * 1.0 / len(reverts), 4)
        else:
            res['revert_length'] = len(reverts)
            res['revert_water'] = 0
        return res

    def fetch_price_info(self, dt, avgdays):
        """ 获取基金价格在 avgdays 中的排名
            dt 为 None，使用当天估值。
        """
        res = {
            'capital': 0,
            'amount': 0,
        }
        price_info = self.get_dt_price(dt, avgdays)
        revert_info = self.get_dt_revert(dt, price_info['price'][1])
        res.update(price_info)
        res.update(revert_info)
        if dt is None:
            dt = datetime.datetime.combine(datetime.date.today(), datetime.datetime.min.time())
        price60 = []
        for i in range(1, avgdays):
            d = dt - datetime.timedelta(days=i)
            if d in self.price_list:
                price60.append(self.price_list.get(d)[1])
        price60.append(res['price'][1])
        price60.sort(reverse=True)
        res['rank'] = (round(1 - (price60.index(res['price'][1]) + 1) * 1.0 / len(price60), 4), len(price60))
        res['price60'] = price60
        return res

    def save_buylog(self, newlog):
        """ 保存新购买的日志，用以衡量本次购买的水位线。
        """
        buylog = {}
        try:
            fr = open(self.buylog_path, 'r')
            for line in fr.readlines():
                arr = line.strip().split(',')
                d = datetime.datetime.strptime(arr[1], '%Y-%m-%d')
                buylog[d] = {
                    'action': str(arr[2]),
                    'capital': int(arr[3]),
                    'amount': float(arr[4]),
                }
            fr.close()
            buylog.update(newlog)
            with open(self.buylog_path, 'w') as fw:
                for d in sorted(buylog.keys()):
                    line = '{},{},{},{},{}'.format(
                        self.fid,
                        d.strftime('%Y-%m-%d'),
                        buylog[d]['action'],
                        str(buylog[d]['capital']),
                        str(buylog[d]['amount'])
                    )
                    fw.write(line)
                    fw.write('\n')
            return buylog
        except Exception:
            print('First create buylog')
            with open(self.buylog_path, 'w') as fw:
                for d in sorted(newlog.keys()):
                    line = '{},{},{},{},{}'.format(
                        self.fid,
                        d.strftime('%Y-%m-%d'),
                        newlog[d]['action'],
                        str(newlog[d]['capital']),
                        str(newlog[d]['amount'])
                    )
                    fw.write(line)
                    fw.write('\n')
            return newlog

    def load_buylog(self, buyfunc, avgdays, begin_date, end_date, n, days=365*6, base=100):
        """ 加载 6 年购买的日志，用以衡量本次购买的水位线。
        """
        bslog = {}
        if end_date is None:
            now = datetime.datetime.now() - datetime.timedelta(days=1)
            end_date = datetime.datetime(now.year, now.month, now.day, 0, 0, 0)
        if begin_date is None:
            begin_date = end_date - datetime.timedelta(days=days)
        max_dt = datetime.datetime(1970, 1, 1)
        # 加载买卖记录，如不存在，则创建。
        try:
            fr = open(self.buylog_path, 'r')
            for line in fr.readlines():
                arr = line.strip().split(',')
                d = datetime.datetime.strptime(arr[1], '%Y-%m-%d')
                max_dt = max(d, max_dt)
                bslog[d] = {
                    'action': str(arr[2]),
                    'capital': float(arr[3]),
                    'amount': float(arr[4]),
                }
            fr.close()
        except Exception as e:
            print('First fetch buylog')
            max_dt = end_date
            newlog = {}
            for i in range((end_date - begin_date).days + 1):
                dt = begin_date + datetime.timedelta(days=i)
                if dt not in self.price_list.keys():
                    newlog[dt] = {'capital': 0, 'amount': 0, 'action': 'n'}
                else:
                    res = getattr(self, buyfunc)(dt, avgdays, n, base)
                    newlog[dt] = {'capital': res['capital'], 'amount': res['amount'], 'action': res['action']}
            bslog = self.save_buylog(newlog)
        # 是否需要继续计算购买记录
        if end_date > max_dt:
            print('Need fetch new buylog')
            newlog = {}
            for i in range((end_date - max_dt).days + 1):
                dt = max_dt + datetime.timedelta(days=i)
                res = getattr(self, buyfunc)(dt, avgdays, n, base)
                newlog[dt] = {
                    'capital': res['capital'],
                    'amount': res['amount'],
                    'action': res['action']
                }
            bslog.update(newlog)
            bslog = self.save_buylog(bslog)
        for lg in bslog.keys():
            if bslog[lg]['action'] == 'b':
                self.buylog[lg] = {
                    'capital': bslog[lg]['capital'],
                    'amount': bslog[lg]['amount'],
                }
            elif bslog[lg]['action'] == 's':
                self.selllog[lg] = {
                    'capital': bslog[lg]['capital'],
                    'amount': bslog[lg]['amount'],
                }

    def fetch_bslog_water(self, fprice, action, end_date=None, days=365*6):
        """ 长期购买一段时间，计算 fprice 的水位线，返回购买历史记录长度。
            action: n/b/s，分别表示无操作，buylog，selllog
            根据 fprice 的所处水位线的不同，大致可以决定是否大量加仓或者减仓。
        """
        bslist = []
        if end_date is None:
            end_date = datetime.datetime.combine(
                datetime.date.today(), datetime.datetime.min.time()) - datetime.timedelta(days=1)
        begin_date = end_date - datetime.timedelta(days=days)
        dt = begin_date
        for i in range(days + 1):
            dt = begin_date + datetime.timedelta(days=i)
            if dt not in self.trade_days:
                continue
            if dt not in self.buylog and dt not in self.selllog:
                continue
            if action == 'b' and dt in self.buylog and self.buylog[dt]['capital'] > 0:
                bslist.append(self.buylog[dt]['capital'])
            elif action == 's' and dt in self.selllog and self.selllog[dt]['capital'] > 0:
                bslist.append(self.selllog[dt]['capital'])
        if len(bslist) <= 20:
            return (0, len(bslist))
        if fprice == 0 or action == 'n':
            return (0, len(bslist))
        bslist.append(fprice)
        if action == 'b':
            sorted_log = sorted(bslist)
        if action == 's':
            sorted_log = sorted(bslist, reverse=True)
        weight = 1.0 * sorted_log.index(fprice) / len(sorted_log)
        return (round(weight, 4), len(bslist))

    def buy_1day1(self, dt, avgdays, n, base=100):
        """ 对指定的某一天进行购买，用于测试，默认买100块钱。
            n_price 为幂。本次主要考虑基金净值与60天均值的比，未考虑净值在60天的排位。
            n_pe 为幂。本策略中无用。
            dt is None，表示今天购买，否则校验是否为交易日。
        """
        res = self.fetch_price_info(dt, avgdays)
        (cur_dw, cur_lj) = res['price']
        if cur_lj == 0:
            res['action'] = 'n'
            return res
        if cur_lj > res['avg_price'][1]:
            res['action'] = 's'
        else:
            res['action'] = 'b'
        res['capital'] = round(
            ((res['avg_price'][1] - (cur_lj - cur_dw)) / cur_dw) ** n['price'] * base, 2
        )
        res['amount'] = round(res['capital'] / cur_lj, 2)
        return res

    def buy_1day2(self, dt, avgdays, n, base=100):
        """ 对指定的某一天进行购买，用于测试，默认买100块钱。
            主要考虑净值在60天的排位，未考虑价格。
            n_price 为幂。本策略中无用。
            n_pe 为幂。本策略中无用。
            对于 000215 这种波动不大的基金，考虑当前排位收益更高。
            dt is None，表示今天购买，否则校验是否为交易日。
        """
        res = self.fetch_price_info(dt, avgdays)
        (cur_price, price60) = (res['price'][1], res['price60'])
        weight = ((price60.index(cur_price) + 1) * 1.0 / len(price60)) / 0.5
        if int(weight) < 1:
            weight = 0
        if cur_price > 0:
            res['capital'] = round(base * weight ** 2, 2)
            res['amount'] = round(res['capital'] / cur_price, 2)
        return res

    def buy_1day3(self, dt, avgdays, n, base=100):
        """ 对指定的某一天进行购买，用于测试，默认买100块钱。
            dt is None，表示今天购买，否则校验是否为交易日。
        """
        res = self.fetch_price_info(dt, avgdays)
        if dt is None:
            dt = datetime.datetime.combine(datetime.date.today(), datetime.datetime.min.time())
        # 计算 pe 权重，由于 pe 无法预估，因此采用前一天的 pe 计算
        # 考虑到波动周期性，pe 标准改用最近5年的30水位线（好买评估依据），也可以采用10年，保证指数经历一轮牛熊（蛋卷评估依据）
        # 不宜采用1-2年方案，受行业周期影响较大。
        res['pe30'] = self.dj.get_pbe_nwater(dt, 30, 365*5)
        res['pe50'] = self.dj.get_pbe_nwater(dt, 50, 365*5)
        res['pe70'] = self.dj.get_pbe_nwater(dt, 70, 365*5)
        res['pe90'] = self.dj.get_pbe_nwater(dt, 90, 365*5)
        for i in range(30):
            res['pe'] = self.index_pbe.get(dt - datetime.timedelta(days=i), -1)
            if res['pe'] > 0:
                break
        weight_pe = self.get_weight_pe(res['pe'], res['pe30'], n['pe'])
        # 为更安全，price 标准采用最近1年的均值，时间过长，可能无法申购。也可以考虑采用最近1年，2年均值的最小值。
        weight_price = self.get_weight_price(res['price'], res['avg_price'], n['price'])
        weight = weight_pe * weight_price
        # 确定是买，卖，还是不操作。
        if res['price'][1] == 0 or weight == 0:
            res['action'] = 'n'
            return res
        res['action'] = 's'
        res['capital'] = round(base * weight, 2)
        if res['pe'] < res['pe30'] and res['price'][1] < res['avg_price'][1]:
            res['action'] = 'b'
            # 以累计净值计算购买数量，不准确。
            res['amount'] = round(res['capital'] / res['price'][1], 2)
        return res

    def buy_1day4(self, dt, avgdays, n, base=100):
        """ 对指定的某一天进行购买，用于测试，默认买100块钱。
            主要考虑净值回撤的排位，未考虑价格。
            n_price 为幂。本策略中无用。
            n_pe 为幂。本策略中无用。
            dt is None，表示今天购买，否则校验是否为交易日。
        """
        res = self.fetch_price_info(dt, avgdays)
        (revert, water, cur_price) = (res['revert'], res['revert_water'], res['price'][1])
        if revert == 0:
            res['capital'] = 0
            res['amount'] = 0
        else:
            if int(res['revert_water'] * 100) < 50:
                weight = 0
            else:
                weight = (res['revert_water'] + 1) ** 6 / 10
            if cur_price > 0:
                res['capital'] = round(base * weight, 2)
                res['amount'] = round(res['capital'] / cur_price, 2)
        return res

    def buy_longtime(self, buyfunc, avgdays, begin_date, end_date, n, base=100):
        """ 长期购买一段时间，用于测试。默认买100块钱。以最后一天累计净值为基准计算盈利。
        """
        self.load_buylog(buyfunc, avgdays, begin_date, end_date, n, 365*6, base)
        b_capital = 0
        b_amount = 0
        # 获取最高盈利点
        for i in range((end_date - begin_date).days + 1):
            dt = begin_date + datetime.timedelta(days=i)
            if dt not in self.buylog.keys():
                continue
            b_capital = b_capital + self.buylog[dt]['capital']
            b_amount = b_amount + self.buylog[dt]['amount']
            fprice = float(self.price_list[dt][1])
        if b_capital == 0:
            win = 0
        else:
            win = (b_amount * fprice - b_capital) * 100 / b_capital
        win = str(round(win, 2)) + '%'
        avg_price = 0 if b_amount == 0 else (b_capital / b_amount)
        return (round(b_capital, 2), round(b_amount, 2), win, round(avg_price, 4))

if __name__ == '__main__':
    index_code = 'njbcz'
    index_code = '000083'
    index_code = '166002'
    index_code = '163402'
    index_code = '161005'
    index_code = '100038'
    index_code = '001338'
    index_code = '519915'
    index_code = '011608'
    index_code = '163406'
    p = Policy(index_code)
    p.load_fundprice()
    p.load_revert()
    p.init_index_pbe()
    params = p.index['params']
    begin_date = datetime.datetime(2015, 8, 31, 0, 0, 0)
    end_date = datetime.datetime(2022, 10, 23, 0, 0, 0)
    print(p.buy_longtime(params['buyfunc'], params['avgdays'], begin_date, end_date, params['n'], 100))