from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.contrib.auth.models import User
from django.urls import reverse
from django.contrib.auth.decorators import login_required
from .models import Session_Model, Choice_Model
from .forms import Session_Form, Choice_Form

# Custom view to list all sessions - mainly for testing
@login_required
def list_all_view(request, ts=0):
    # update the number of seats for each sessions
    seshs = Session_Model.objects.all()
    for sesh in seshs:
        sesh.seats = Choice_Model.objects.filter(session_id=sesh.id).count()
        sesh.save()
    session_times = {
        '1015': {'title': '', 'room': '', 'session_id': 0},
        '1130': {'title': '', 'room': '', 'session_id': 0},
        '1245': {'title': '', 'room': '', 'session_id': 0},
        '200':  {'title': '', 'room': '', 'session_id': 0}
    }
    times = ['10:15 A.M.', '11:30 A.M.', '12:45 P.M.', '2:00 P.M']
    if ts == 0:
        try:
            all_sesh = Session_Model.objects.all().filter(time_slot=request.session['last_view']).order_by('title')
        except:
            all_sesh = Session_Model.objects.all().order_by('title')
    elif ts=='0:00':
        all_sesh = Session_Model.objects.all().order_by('title')
        request.session['last_view'] = 0
    else:
        all_sesh = Session_Model.objects.all().filter(time_slot=ts).order_by('title')
        request.session['last_view'] = ts
    for time_val in times:
        try:
            choi = get_object_or_404(Choice_Model, time_slot=time_val, user_id = request.user.id)
            sesh = get_object_or_404(Session_Model, id=choi.session_id)
            session_times[time_val.split(' ')[0].replace(':','')]['title']      = sesh.title
            session_times[time_val.split(' ')[0].replace(':','')]['room']       = sesh.room
            session_times[time_val.split(' ')[0].replace(':','')]['session_id'] = sesh.id
        except:
            pass
    print(session_times)
    context = {
        'all_sesh': all_sesh,
        'session_times': session_times,
    }
    return render(request, 'list_all.html', context)

@login_required
def delete_choice_view(request, pk):
    sesh = get_object_or_404(Choice_Model, session_id=pk, user_id = request.user.id)
    sesh.delete()
    return HttpResponseRedirect(reverse('list_all_view'))

# Show the details of an individual session
@login_required
def details_view(request, pk=0):
    current_user = request.user
    if pk == 0:
        pk = request.session['sesh_id']
    else:
        request.session['sesh_id'] = pk
    sesh = Session_Model.objects.get(id=pk)
    print(sesh.id)
    try:
        registrants = Choice_Model.objects.all().filter(session_id=sesh.id)
        attendees = []
        for registrant in registrants:
            user = User.objects.get(id = registrant.user_id)
            attendees.append({
                'first_name':   user.first_name,
                'last_name':    user.last_name,
                'email':        user.email})
    except:
        attendees = {}
        pass
    is_owner = request.user.username == sesh.email
    print(is_owner)
    try:
        this_time = get_object_or_404(Choice_Model, time_slot=sesh.time_slot, user_id = current_user.id)
        has_choice = True
        if this_time.session_id == sesh.id:
            same_sesh = True
        else:
            same_sesh = False
    except:
        has_choice = False
        same_sesh = False
    form = Choice_Form(request.POST or None)
    if form.is_valid():
        if has_choice:
            this_time.delete()
        form.save()
        return HttpResponseRedirect(reverse('list_all_view'))
    else:
        context = {
            'sesh': sesh,
            'has_choice': has_choice,
            'same_sesh': same_sesh,
            'is_owner': is_owner,
            'attendees': attendees
        }
        return render(request, 'details.html', context)

# Junk view assignment until I can replace it with a proper menu
@login_required
def home_view(request):
    return render(request, "home.html", {})

@login_required
def create_session_view(request):
    form = Session_Form(request.POST or None)
    if form.is_valid():
        form.save()
        return HttpResponseRedirect(reverse('list_all_view'))
    else:
        context = {
            'form': form
        }
    return render(request, 'create_session.html', context)


from django.shortcuts import render
from . import forms
# Create your views here.
'''
def studentinputview(request):
    form=forms.StudentForm()
    my_dict={'form':form}
    return render(request,'formApp/input.html',context=my_dict)
'''
def studentinputview(request):
    form=forms.StudentForm()
    if request.method=='POST':
        form=forms.StudentForm(request.POST)
        if form.is_valid():
            print('Form validation success and printing data')
            print('Name:',form.cleaned_data['name'])
            print('Marks:',form.cleaned_data['marks'])
    return render(request,'formApp/input.html',{'form':form})


from os import path
from bs4 import BeautifulSoup
from ast import literal_eval

from config.constants.state_info import StateAcronyms, StateName
from models.state_html_models import ZipCode, StateInfo, State
from models.zip_html_models import Communities
from models import ZipManager
from db_storage.db_interface import DbModule

def generate_state_obj_list():
    state_name_list = list(StateName.__members__)
    state_acronym_list = list(StateAcronyms.__members__)
    state_list = {}

    for idx, val in enumerate(state_acronym_list):
        state_list[val] = State(state_name_list[idx],state_acronym_list[idx])
    return state_list

state_dic = generate_state_obj_list()

def state_file_parser(file, file_data):
    zip_manager = ZipManager()
    state_acronym = StateAcronyms(path.basename(file).split(".")[0].upper())
    soup = BeautifulSoup(file_data, 'html5lib')
    state = state_dic[state_acronym.value]
    state_info = StateInfo(state)
    zip_rows = soup.select("div.panel.panel-default.panel-prefixes")[0].find_all("div", class_="list-group-item")

    for row in zip_rows:
        row_list = []
        is_zip = False;
        for col in row.find_all("div", class_="col-xs-12"):
            if col.find("a") and is_zip is False:
                row_list.append({
                    "zip": int(col.a.string),
                    "url": col.a['href']
                })
                is_zip = True
            else:
                for text in col.stripped_strings:
                    row_list.append(text)

        zip_code = ZipCode(row_list[0]["zip"], row_list[0]["url"], str(state), row_list[1], row_list[2],
                           row_list[3] if len(row_list) > 3 else "")
        zip_manager.add_zip(zip_code)
        state_info.add_zip(zip_code)

    zip_manager.add_state_info(state_info)

def zip_file_parser(file,file_data,**kwargs):
    db_interface = DbModule()

    zip_code = int(path.basename(file).split(".")[0])

    table_name = None
    zip_obj = None

    # Checks to See if the params have been populated
    for key, value in kwargs.items():
        if key is "table_name":
            table_name = value
        if key is "zip_list":
            filtered_zip = list(filter(lambda item: int(item["zip_code"]) == zip_code, value))
            zip_obj = filtered_zip[0] if len(filtered_zip) == 1 else None

    if table_name is None or zip_obj is None:
        db_interface.insert("failed_zips", {"zip_code": zip_code, "issue": "html not part of zip_list"})
        return

    # Checks to ensure that this is a valid html file
    soup = BeautifulSoup(file_data, 'html5lib')

    # Checks to see if this is a failed zip retrieval
    if soup.find("meta") is None:
        db_interface.insert("failed_zips", {"zip_code": zip_code, "issue": "no html"})
        return

    # The html file contains a script tag that holds the zip coordinates
    script_list = soup.select("script")[1].string.strip().split(" ")

    geo_json = literal_eval((script_list[2][:len(script_list[2]) - 12]))

    polygon_type = geo_json['features'][0]['geometry']["type"]
    if polygon_type == "MultiPolygon":
        db_interface.insert("failed_zips", {"zip_code": zip_code, "issue": "MultiPolygon"})
        return

    zip_coords = geo_json['features'][0]['geometry']['coordinates'][0]

    coord_boundaries = script_list[4]

    # Gets Neighboring Zips
    neighboring_zips = []
    if soup.find(".nearby-zips-list"):
        neighboring_zips = list(map(lambda item: item.text.split(" ")[2], soup.select(".nearby-zips-list")[0].select("ul li div a")))

    communities_obj = Communities(zip_code,zip_coords);

    # Batch inserts communites
    db_interface.insert("communities",communities_obj.get_json())

    db_interface.insert("zip_codes",{
        "zip_code": zip_code,
        "state": zip_obj["state"].split(" ")[0],
        "state_abrv": zip_obj["state"].split(" ")[1],
        "city": zip_obj["city"],
        "county": zip_obj["county"],
        "zip_coords": zip_coords,
        "bordering_zips": neighboring_zips,
        "bounding_coords": coord_boundaries
    })


import os
import threading
import time

import requests
from flask import jsonify, Response, request
from requests.exceptions import ConnectionError

from file_system import File, DataNode
from master_utils import (
    app,
    create_log,
    data_nodes,
    request_datanode,
    choose_datanodes,
    choose_datanodes_for_replication,
    drop_datanode,
    fs,
    REPLICATION_FACTOR,
)

DEBUG = True if os.getenv("DEBUG", "false") == "true" else False
PORT = 3030
MAX_DATANODE_CAPACITY = 5 * 1024 * 1024 * 1024  # max available memory on each Data Node
free_memory = 0  # free storage memory in bytes

@app.route("/ping")
def ping():
    return "Hello, Master Node is Alive!"

@app.route("/status")
def status():
    app.logger.info(f"Sent filesystem data to a client")
    return jsonify(
        {
            "Free Space": f"{free_memory / 1024 / 1024:.3f} MB",
            "Master Node Address": f"{request.remote_addr}:{PORT}",
            "Available Data Nodes": [node.serialize() for node in data_nodes],
        }
    )

@app.route("/datanode", methods=["POST"])
def datanode():
    host, port = f"http://{request.remote_addr}", request.args["port"]
    new_datanode = DataNode(host, port)
    if new_datanode not in data_nodes:
        data_nodes.append(new_datanode)
        app.logger.info(f"Add a new Data Node {new_datanode.ip}:{new_datanode.port}")
        return Response(status=201)
    else:
        return Response(status=400)

@app.route("/filesystem", methods=["DELETE"])
def filesystem():
    if request.method == "DELETE":
        fs.__init__()
        for d in data_nodes:
            request_datanode(d, "filesystem", request.method)
        if len(data_nodes) > 0:
            message = (
                f"Storage is initialized and ready, available disk space is "
                f"{MAX_DATANODE_CAPACITY * len(data_nodes) / 1024 / 1024:.3f} MB."
            )
            app.logger.info(message)
            return Response(message, 200)
        else:
            app.logger.info("Storage is unavailable")
            return Response("Storage is unavailable", 400)

@app.route("/file", methods=["POST", "GET", "PUT", "DELETE"])
def file():
    filename = request.args["filename"]
    file: File = fs.get_file(filename)

    if request.method == "GET":
        if not file:
            app.logger.info(f"File '{filename}' data was requested but file not found")
            return Response(f"file '{filename}' not found", status=404)
        else:
            app.logger.info(f"Sent the file '{filename}' data to a client")
            file.file_info["last_accessed"] = time.ctime()
            return jsonify({"file": file.serialize()})

    elif request.method == "POST":
        file: File = fs.add_file(filename)
        app.logger.info(f"Added file '{filename}' to the filesystem")
        return jsonify({"datanodes": choose_datanodes(), "file": file.serialize()})

    elif request.method == "PUT":
        destination = request.args["destination"]
        fs.move_file(filename, destination)
        app.logger.info(f"File '{filename}' was moved to '{destination}'")
        return Response(f"file '{filename}' was moved to '{destination}'", 200)

    elif request.method == "DELETE":
        if not file:
            app.logger.info(
                f"File '{filename}' was requested to delete but file not found"
            )
            return Response(f"file '{filename}' not found", status=404)
        else:
            for dnode in file.nodes:
                request_datanode(dnode, f"file?filename={file.id}", "DELETE")
            fs.remove_file(filename)
            app.logger.info(f"File '{filename}' was deleted")
            return Response(f"file '{filename}' was deleted", 200)

@app.route("/directory", methods=["GET", "POST", "DELETE"])
def directory():
    dirname = request.args["name"]

    if dirname[-1] == "/":
        dirname = dirname[:-1]

    if dirname == "" or dirname[0] != "/":
        dirname = "/" + dirname

    if request.method == "POST":
        fs.add_directory(dirname)
        app.logger.info(f"Directory '{dirname}' created")
        return Response(f"directory '{dirname}' created", 201)

    elif request.method == "GET":
        if not fs.dir_exists(dirname):
            app.logger.info(
                f"Directory '{dirname}' data was requested but it does not exist"
            )
            return Response(f"directory '{dirname}' does not exist", 404)
        app.logger.info(f"Sent the directory '{dirname}' data to a client")
        return jsonify(
            {
                "files": list(map(File.serialize, fs.get_files(dirname))),
                "dirs": list(fs.get_subdirs(dirname)),
            }
        )
    elif request.method == "DELETE":
        if not fs.dir_exists(dirname):
            app.logger.info(
                f"Directory '{dirname}' was requested to delete but it does not exist"
            )
            return Response(f"directory '{dirname}' does not exist", 404)
        if dirname == "/":
            app.logger.info(
                f"Directory '{dirname}' was requested to delete but root directory cannot be deleted"
            )
            return Response("cannot remove root directory", 400)
        rm_list = fs.remove_dir(dirname)
        for file in rm_list:
            for dnode in file.nodes:
                request_datanode(dnode, f"file?filename={file.id}", "DELETE")
            fs.remove_file(file.name)
        app.logger.info(
            f"Directory '{dirname}' and all its sub-folders and files were deleted"
        )
        return Response(
            f"directory '{dirname}' and all its sub-folders and files were deleted",
            status=200,
        )

def ping_data_nodes():
    while True:

        total_occupied = 0
        for cur_node in data_nodes:
            files = fs.get_all_files()
            file_ids = {"files": fs.get_all_ids()}

            node_address = f"{cur_node.ip}:{cur_node.port}"
            app.logger.debug(f"Synchronisation with datanode {node_address}")
            try:
                resp = requests.get(
                    os.path.join(node_address, "filesystem"), json=file_ids
                )
                app.logger.debug(f"Success - datanode {node_address} is alive")
                data_file_ids = resp.json()["files"]
                # app.logger.info(f"Datanode {node_address} files: {data_file_ids}")
                file_sizes = resp.json()["file_sizes"]
                total_occupied += sum(file_sizes)

                for (file_id, file_size) in zip(data_file_ids, file_sizes):
                    file = fs.get_file_by_id(int(file_id))
                    # update info that data node has some new file
                    if file is None:
                        app.logger.info(
                            f"Sent request to delete unknown file {file_id} from the Data Node {node_address}"
                        )
                        request_datanode(cur_node, f"file?filename={file_id}", "DELETE")
                        continue
                    if not cur_node in file.nodes:
                        app.logger.info(
                            f"New file found on the Data Node {node_address} - {file.name}"
                        )
                        file.nodes.append(cur_node)
                    file.file_info["size"] = file_size
                for file in files:
                    # update info that data node does not have some file
                    if cur_node in file.nodes and file.id not in data_file_ids:
                        app.logger.info(
                            f"File {file.name} not found on the Data Node {node_address}, deleting from database"
                        )
                        file.nodes.remove(cur_node)

            except ConnectionError as e:
                app.logger.info(f"Data Node '{node_address}' synchronisation failed")
                drop_datanode(cur_node)

        global free_memory
        free_memory = len(data_nodes) * MAX_DATANODE_CAPACITY - total_occupied
        time.sleep(1)

def replication_check():
    while True:
        for file in fs.get_all_files():
            if (
                    0 < len(file.nodes) < REPLICATION_FACTOR
            ):
                nodes = choose_datanodes_for_replication(file.nodes)
                for i in range(len(nodes)):
                    target_node, source_node = nodes[i], file.nodes[i % len(file.nodes)]
                    source_address = f"{source_node.ip}:{source_node.port}"
                    resp = request_datanode(
                        target_node,
                        f"file?source_node={source_address}&filename={file.id}",
                        "PUT",
                    )
                    if resp is not None and resp.status_code // 200 == 1:
                        app.logger.info(
                            f"File {file.name} has been replicated to {target_node.ip}:{target_node.port}"
                        )
        time.sleep(1)

if __name__ == "__main__":
    create_log(app, "master_node", debug=DEBUG)
    ping_thread = threading.Thread(target=ping_data_nodes)
    ping_thread.start()
    repl_thread = threading.Thread(target=replication_check)
    repl_thread.start()
    app.run(host="0.0.0.0", port=PORT, debug=DEBUG)
    ping_thread.join()
    repl_thread.join()


import copy

import rdkit.Chem as Chem
from rdkit.Chem import Draw
from rdkit.Chem import rdMMPA

from MolUtils.RDKUtils.Utils import RDKUtils
from MolUtils.RDKUtils.Frag.RDKFragUtil import RDKFragUtil

class RDKFragMMPA:
    def decompose(mol,
                break_ex        = False,  # do ex-action besides basic BRICS algorithm
                break_long_link = False,  # non-ring and non-ring
                break_r_bridge  = False,  # ring-ring bridge
                ):
        #RDKUtils.show_mol_with_atommap(mol, atommap = True)

        n_atoms = mol.GetNumAtoms()
        if n_atoms == 1:
            return [[0]], []

        cliques = []
        breaks = []

        atom_cliques = {}
        for i in range(n_atoms):
            atom_cliques[i] = set()  # atom-cliques map

        try:
            mol_bonds = mol.GetBonds()
            for bond in mol.GetBonds():
                a1 = bond.GetBeginAtom().GetIdx()
                a2 = bond.GetEndAtom().GetIdx()
                cliques.append([a1, a2])

            single_cliq = []

            break_bonds = RDKFragMMPA.find_break_bonds(mol)

            if len(break_bonds) == 0:
                return [list(range(n_atoms))], []
            else:
                for bond in break_bonds:
                    #bond = bond[0]  #This is not need here, but need for RDK bond
                    if [bond[0], bond[1]] in cliques:
                        cliques.remove([bond[0], bond[1]])
                    else:
                        cliques.remove([bond[1], bond[0]])

            if break_ex:
                for c in cliques:
                    if len(c) > 1:
                        if mol.GetAtomWithIdx(c[0]).IsInRing() and not mol.GetAtomWithIdx(c[1]).IsInRing():
                            breaks.append(c)

                        if mol.GetAtomWithIdx(c[1]).IsInRing() and not mol.GetAtomWithIdx(c[0]).IsInRing():
                            breaks.append(c)

                        if break_long_link:  # non-ring and non-ring
                            if not mol.GetAtomWithIdx(c[1]).IsInRing() and not mol.GetAtomWithIdx(c[0]).IsInRing():
                                breaks.append(c)

                        if break_r_bridge:  # ring-ring bridge
                            if mol.GetAtomWithIdx(c[0]).IsInRing() and mol.GetAtomWithIdx(c[1]).IsInRing():
                                if not RDKFragUtil.bond_in_ring(mol_bonds, c[0], c[1]):
                                    breaks.append(c)

                for b in breaks:
                    if b in cliques:
                        cliques.remove(b)
            # end for break_ex

            cliques = RDKFragUtil.merge_cliques(cliques)

            for b in break_bonds:
                #b = b[0]  #This is not need here, but need for RDK bond
                breaks.append([b[0], b[1]])

            # --------------------
            break_atom_bonds = {}
            for i, b in enumerate(breaks):
                b0 = b[0]
                b1 = b[1]
                if b0 in break_atom_bonds:
                    break_atom_bonds[b0].append(b)
                else:
                    break_atom_bonds[b0] = [b]

                if b1 in break_atom_bonds:
                    break_atom_bonds[b1].append(b)
                else:
                    break_atom_bonds[b1] = [b]

            # ---------------------
            single_cliq = []

            for key, value in break_atom_bonds.items():
                aid = key
                atom = mol.GetAtomWithIdx(aid)

                if len(value) > 2:
                    # the shared point as a center clique
                    cliques.append([key])
                    single_cliq.append([key])
                elif len(value) == 2 and mol.GetAtomWithIdx(key).IsInRing():
                    cliques.append([key])
                    single_cliq.append([key])

                # if len(value) == 1 or len(value) == 2:
                for i in range(len(value)):
                    b = value[i]
                    if [b[0], b[1]] not in cliques and [b[1], b[0]] not in cliques:
                        cliques.append(b)

            # -------------------
            # find exteral single_cliq when it is created by breaks and no breaks
            # could be tested using BRICS_Base algorithm
            # smls = 'CC(=O)Nc1c2C(=O)N(C3CCCCC3)[C@@](C)(C(=O)NC3CCCCC3)Cn2c2ccccc12'

            for i in range(n_atoms):
                atom_cliques[i] = set()

            for i, cliq in enumerate(cliques):
                for a in cliq:
                    atom_cliques[a].add(i)

            for key, value in atom_cliques.items():
                if len(value) >= 3:
                    if [key] not in cliques:
                        cliques.append([key])
                        single_cliq.append([key])

            # --------------------
            # edges
            edges = []
            singles = set()
            for s in range(len(cliques)):
                s_cliq = cliques[s]
                if len(s_cliq) == 1:
                    singles.add(s)
                    continue
                for e in range(s + 1, len(cliques)):
                    e_cliq = cliques[e]
                    if len(e_cliq) == 1:
                        singles.add(e)
                        continue
                    share = list(set(s_cliq) & set(e_cliq))
                    if len(share) > 0 and share not in single_cliq:
                        edges.append((s, e))

            for i in singles:
                s_cliq = cliques[i]
                for cid in range(len(cliques)):
                    if i == cid:
                        continue
                    share = list(set(cliques[i]) & set(cliques[cid]))
                    if len(share) > 0:
                        if i < cid:
                            edges.append((i, cid))
                        else:
                            edges.append((cid, i))

        except Exception as e:
            print(e.args)
            print('RDKFragMMPA Exception: ', Chem.MolToSmiles(mol))
            cliques = [list(range(n_atoms))]
            edges = []

        return cliques, edges

    def find_break_bonds(mol_in, maxCuts = 1):
        mol = copy.deepcopy(mol_in)
        RDKUtils.add_atom_index(mol, prop = 'atomNote')
        #RDKUtils.show_mol_with_atommap(mol, atommap = False)

        break_bonds = []

        mol_bonds = mol.GetBonds()
        n_bonds = len(mol_bonds)

        #RDKUtils.show_mol_with_label(mol, prop = 'atomNote')

        sub_mols = []

        sub_mols.append(mol)
        sub_mols.append(Chem.MolFromSmiles('C'))

        fr = rdMMPA.FragmentMol(mol, maxCuts = maxCuts, resultsAsMols = True, maxCutBonds = n_bonds)
        for f in fr:
            ff = Chem.GetMolFrags(f[1], asMols=True)
            sub_mols.append(ff[0])
            sub_mols.append(ff[1])
            #print(Chem.MolToSmiles(ff[0]))
            #print(Chem.MolToSmiles(ff[1]))

            (nba, nbb) = RDKFragUtil.get_dummy_bond_pair(ff[0], ff[1])
            break_bonds.append((nba, nbb))

            #RDKUtils.show_mol_with_atommap([ff[0], ff[1]], atommap = False)
            #n_mol = RDKFragMMPA.__get_context_env(ff[1], radius = 1)
            #RDKUtils.show_mol_with_atommap([n_mol], atommap = False)

            #smol1 = RDKUtils.remove_dummy_atom(ff[0])
            #sub_mols.append(smol1)

            #smol2 =  RDKUtils.remove_dummy_atom(ff[1])
            #sub_mols.append(smol2)

            #RDKUtils.show_mol_with_atommap([smol1, smol2], atommap = False)

        #Draw.MolsToGridImage(sub_mols, molsPerRow = 4, subImgSize=(400, 400)).show()

        return break_bonds

def test_sml():
    #sml = 'C[NH+](C/C=C/c1ccco1)CCC(F)(F)F'
    sml = 'CC1OC(OCC=C2CCC3C4CCC5Cc6nc7c(nc6CC5(C)C4C(O)CC23C)CC2CCC3C4CCC(=CCOC5OC(C)C(O)C(O)C5O)C4(C)CC(O)C3C2(C)C7)C(O)C(O)C1O'
    mol = Chem.MolFromSmiles(sml)

    break_bonds = RDKFragMMPA.find_break_bonds(mol, maxCuts = 1)

    return

def test():
    #https://zhuanlan.zhihu.com/p/389763022
    #http://rdkit.org/docs/source/rdkit.Chem.rdMMPA.html?highlight=rdmmpa
    #http://rdkit.org/docs/source/rdkit.Chem.rdMolDescriptors.html?highlight=atompairsparameters#rdkit.Chem.rdMolDescriptors.AtomPairsParameters
    #FragmentMol( (Mol)mol [ , (int)maxCuts=3 [ , (int)maxCutBonds=20 [ , (str)pattern='[#6+0;!$(*= , #[!#6])]! @!=!#[*]' [ , (bool)resultsAsMols=True ] ] ] ] )
    #FragmentMol( (Mol)mol, (int)minCuts, (int)maxCuts, (int)maxCutBonds [, (str)pattern='[#6+0;!$( =,#[!#6])]!@ !=!#[ ]' [, (bool)resultsAsMols=True]])
    #FragmentMol( (Mol)mol, (AtomPairsParameters)bondsToCut [, (int)minCuts=1 [, (int)maxCuts=3 [, (bool)resultsAsMols=True]]])

    #sml = 'CC(=O)Nc1c2C(=O)N(C3CCCCC3)[C@@](C)(C(=O)NC3CCCCC3)Cn2c2ccccc12'
    #sml = 'C(C(F)F)(C(C1CCCCC1)CC1CCCC1)CN1C2SC(Cl)=CC=2S(=O)(=O)NC1'
    #sml = 'NC(=O)C1CCC(CNc2cc(-c3ccccc3)nc3ccnn23)CC1'
    #sml = 'C[NH+](C/C=C/c1ccco1)CCC(F)(F)F'
    sml = 'c1ccccc1C'

    mols = []
    mmm = Chem.MolFromSmiles(sml)
    mols.append(mmm)

    mols.append(Chem.MolFromSmiles('P'))

    #mmm = Chem.AddHs(mmm)
    #mols.append(mmm)

    maxCuts = 2
    #maxCuts = 3

    fr = rdMMPA.FragmentMol(mmm, maxCuts = maxCuts, resultsAsMols = True, maxCutBonds = 50)
    #fr = rdMMPA.FragmentMol(mmm, pattern="[*]!@!=!#[!#1]", maxCuts=1, resultsAsMols=True, maxCutBonds=50)

    for f in fr:
        ff = Chem.GetMolFrags(f[1], asMols=True)
        #print(Chem.MolToSmiles(ff[0]), Chem.MolToSmiles(Chem.RemoveHs(ff[0])))
        #print(Chem.MolToSmiles(ff[1]), Chem.MolToSmiles(Chem.RemoveHs(ff[1])))
        mols.append(ff[0])
        mols.append(ff[1])

    mols.append(Chem.MolFromSmiles('P'))
    mols.append(Chem.MolFromSmiles('P'))

    #fr = rdMMPA.FragmentMol(mmm, pattern="[*]!@!=!#[!#1]", maxCuts=1, resultsAsMols=True, maxCutBonds=50)
    #fr = rdMMPA.FragmentMol(mmm, maxCuts=maxCuts, pattern='[#6+0;!$(*= , #[!#6])]! @!=!#[*]', resultsAsMols=True, maxCutBonds=50)
    #for f in fr:
    #    ff = Chem.GetMolFrags(f[1], asMols=True)
    #    #print(Chem.MolToSmiles(ff[0]), Chem.MolToSmiles(Chem.RemoveHs(ff[0])))
    #    #print(Chem.MolToSmiles(ff[1]), Chem.MolToSmiles(Chem.RemoveHs(ff[1])))
    #    mols.append(ff[0])
    #    mols.append(ff[1])

    Draw.MolsToGridImage(mols, molsPerRow = 8,).show()

    return

if __name__ == '__main__':
    #test()

    test_sml()


import os
from torch.utils.data import Dataset
from transformers import BertTokenizer
import tqdm
import torch

extra = [
    "[PAD]",
    "[SEP]",
    "[CLS]",
    "[UNK]",
    "[MASK]"]

class TextDatasetHugStatic:
    max_len = None

class TextDatasetHug(Dataset):
    def __init__(self, root, split, src, tgt, vocab_share=False, max_len=80, **kwargs):
        self.root = root
        self.split = split
        self.samples = self.make_samples(root, split, src, tgt)
        src_vocab_file = os.path.join(self.root, 'vocab_src.txt')
        tgt_vocab_file = os.path.join(self.root, 'vocab_tgt.txt')

        if split == 'train':
            src_vocab = self.get_vocab(map(lambda x: x[0], self.samples))
            tgt_vocab = self.get_vocab(map(lambda x: x[1], self.samples))

            open(src_vocab_file, "w").write("\n".join(str(word) for word in extra + src_vocab))
            open(tgt_vocab_file, "w").write("\n".join(str(word) for word in extra + tgt_vocab))

            TextDatasetHugStatic.max_len = max_len
            self.src_len, self.tgt_len = max_len, 999

            print("Vocab(#vocab={}, #extra={}, src_train_len={})".format(len(src_vocab), len(extra), self.src_len))
            print("Vocab(#vocab={}, #extra={}, tgt_train_len_len={})".format(len(tgt_vocab), len(extra), self.tgt_len))

        else:
            self.src_len, self.tgt_len = TextDatasetHugStatic.max_len, 999

        self.src_tokenizer = BertTokenizer(vocab_file=src_vocab_file, do_basic_tokenize=False)
        self.tgt_tokenizer = BertTokenizer(vocab_file=tgt_vocab_file, do_basic_tokenize=False)

        # self.processed_samples = []
        # for idx in tqdm.tqdm(range(len(self.samples))):
        #     src, tgt = self.samples[idx]
        #     self.processed_samples.append((self.src_tokenizer(' '.join(src)), self.tgt_tokenizer(' '.join(tgt))))

    def __getitem__(self, index):
        # src, tgt = self.processed_samples[index]
        # print(self.samples[index])
        src, tgt = self.samples[index]
        return {
            'src': src,
            'tgt': tgt,
        }

    def get_collate_fn(self):
        def collate_fn(batch):
            # src = self.src_tokenizer([s['src'] for s in batch], padding=True, truncation=True, return_tensors="pt",
            #                          max_length=self.src_len)
            # tgt = self.tgt_tokenizer([s['tgt'] for s in batch], padding=True, truncation=True, return_tensors="pt",
            #                          max_length=self.tgt_len)
            # v = batch[0]["tgt"]
            # print(len(v))
            # print(v)
            src = self.src_tokenizer([s['src'] for s in batch], padding=True, return_tensors="pt")
            tgt = self.tgt_tokenizer([s['tgt'] for s in batch], padding=True, return_tensors="pt")
            # print(src)
            # print(tgt)
            # sys.exit()
            # print("####")
            # print(tgt["input_ids"].shape)
            # print(tgt["input_ids"][0])
            # print(len(tgt["input_ids"][0]))
            # print(self.tgt_tokenizer.decode(tgt["input_ids"][0], skip_special_tokens=False, clean_up_tokenization_spaces=False))
            collated = {'input_ids': src.input_ids,
                        'attention_mask': src.attention_mask,
                        'decoder_input_ids': tgt.input_ids,
                        'decoder_attention_mask': tgt.attention_mask}

            return collated

        return collate_fn

    def make_samples(self, root, split, src, tgt):
        src = self.load_file(os.path.join(root, split + '.' + src))
        tgt = self.load_file(os.path.join(root, split + '.' + tgt))
        return list(zip(src, tgt))

    def load_file(self, path):
        """Default loading function, which loads nth sentence at line n.
        """
        with open(path, 'r') as f:
            content = f.read().strip()
        return [s.strip() for s in content.split('\n')]

    def get_vocab(self, sentences):
        vocab = []
        for sentence in sentences:
            vocab += sentence.split()
        return sorted(set(vocab))

    def __len__(self):
        return len(self.samples)


#-*- coding=utf-8 -*-
from flask import render_template,redirect,abort,make_response,jsonify,request,url_for,Response,send_from_directory,stream_with_context,send_file
from flask_sqlalchemy import Pagination
from ..utils import *
from ..extend import *
from . import front

################################################################################
###################################前台函数#####################################
################################################################################

@front.before_request
def before_request():
    bad_ua=['Googlebot-Image','FeedDemon ','BOT/0.1 (BOT for JCE)','CrawlDaddy ','Java','Feedly','UniversalFeedParser','ApacheBench','Swiftbot','ZmEu','Indy Library','oBot','jaunty','YandexBot','AhrefsBot','MJ12bot','WinHttp','EasouSpider','HttpClient','Microsoft URL Control','YYSpider','jaunty','Python-urllib','lightDeckReports Bot','PHP','vxiaotou-spider','spider']
    global referrer
    try:
        ip = request.headers['X-Forwarded-For'].split(',')[0]
    except:
        ip = request.remote_addr
    try:
        ua = request.headers.get('User-Agent')
    except:
        ua="null"
    if sum([i.lower() in ua.lower() for i in bad_ua])>0:
        return redirect('http://www.baidu.com')
    # print '{}:{}:{}'.format(request.endpoint,ip,ua)
    referrer=request.referrer if request.referrer is not None else 'no-referrer'

@front.errorhandler(500)
def page_not_found(e):
    # note that we set the 500 status explicitly
    msg,status=CheckServer()
    return render_template('error.html',msg=msg,code=500), 500

@front.errorhandler(404)
def page_not_found(e):
    # note that we set the 404 status explicitly
    msg,status=CheckServer()
    return render_template('error.html',msg=msg,code=404), 404

@front.route('/favicon.ico')
def favicon():
    resp=MakeResponse(send_from_directory(os.path.join(config_dir, 'app/static/img'),'favicon.ico',mimetype='image/vnd.microsoft.icon'))
    return resp

@front.route('/<path:path>',methods=['POST','GET'])
@front.route('/',methods=['POST','GET'])
@limiter.limit("200/minute;50/second")
def index(path=None):
    balance=eval(GetConfig('balance'))
    if path is None:
        path='{}:/'.format(GetConfig('default_pan'))
    path=urllib.parse.unquote(path).split('?')[0]
    if not os.path.exists(os.path.join(config_dir,'.install')):
        resp=redirect(url_for('admin.install',step=0,user=GetConfig('default_pan')))
        return resp
    try:
        user,n_path=path.split(':')
    except:
        if path==GetConfig('admin_prefix'):
            return redirect(url_for('admin.setting'))
        else:
            return MakeResponse(abort(404))
    if balance:
        if user!=GetConfig('default_pan'):
            return MakeResponse(abort(404))
    if n_path=='':
        path=':'.join([user,'/'])
    page=request.args.get('page',1,type=int)
    image_mode=GetCookie(key='image_mode',default=0)
    sortby=GetCookie(key='sortby',default=GetConfig('default_sort'))
    order=GetCookie(key='order',default=GetConfig('order_m'))
    action=request.args.get('action','download')
    token=request.args.get('token')
    data,total = FetchData(path=path,page=page,per_page=50,sortby=sortby,order=order,action=action,dismiss=True)
    #是否有密码
    password,_,cur=has_item(path,'.password')
    md5_p=md5(path)
    has_verify_=has_verify(path)
    if request.method=="POST":
        password1=request.form.get('password')
        if password1==password:
            resp=MakeResponse(redirect(url_for('.index',path=path)))
            resp.delete_cookie(md5_p)
            resp.set_cookie(md5_p,password)
            return resp
    if password!=False:
        if (not request.cookies.get(md5_p) or request.cookies.get(md5_p)!=password) and has_verify_==False:
            if total=='files' and GetConfig('encrypt_file')=="no":
                if GetConfig("verify_url")=="True" and action not in ['share','iframe']:
                    if token is None:
                        return abort(403)
                    elif VerifyToken(token,path):
                        return show(data['id'],data['user'],action,token=token)
                    else:
                        return abort(403)
                else:
                    return show(data['id'],data['user'],action,token=token)
            resp=MakeResponse(render_template('theme/{}/password.html'.format(GetConfig('theme')),path=path,cur_user=user))
            return resp
    if total=='files':
        if GetConfig("verify_url")=="True" and action not in ['share','iframe']:
            if token is None:
                return abort(403)
            elif VerifyToken(token,path):
                return show(data['id'],data['user'],action,token=token)
            else:
                return abort(403)
        else:
            return show(data['id'],data['user'],action,token=token)
    readme,ext_r=GetReadMe(path)
    head,ext_d=GetHead(path)
    #参数
    all_image=False if sum([file_ico(i)!='image' for i in data])>0 else True
    pagination=Pagination(query=None,page=page, per_page=50, total=total, items=None)
    if path.split(':',1)[-1]=='/':
        path=':'.join([path.split(':',1)[0],''])
    resp=MakeResponse(render_template('theme/{}/index.html'.format(GetConfig('theme'))
                    ,pagination=pagination
                    ,items=data
                    ,path=path
                    ,image_mode=image_mode
                    ,readme=readme
                    ,ext_r=ext_r
                    ,head=head
                    ,ext_d=ext_d
                    ,sortby=sortby
                    ,order=order
                    ,cur_user=user
                    ,all_image=all_image
                    ,endpoint='.index'))
    resp.set_cookie('image_mode',str(image_mode))
    resp.set_cookie('sortby',str(sortby))
    resp.set_cookie('order',str(order))
    return resp

@front.route('/file/<user>/<fileid>/<action>/<token>')
def show(fileid,user,action='download',token=None):
    if token is None:
        token=request.args.get('token')
    path=convert2str(GetPath(fileid))
    name=convert2str(GetName(fileid))
    ext=name.split('.')[-1].lower()
    url=request.url.replace(':80','').replace(':443','').split('?')[0]
    url='/'.join(url.split('/')[:3])+'/'+urllib.parse.quote('/'.join(url.split('/')[3:]))
    if GetConfig("verify_url")=="True":
        url=url+'?token='+GenerateToken(path)
        if action not in ['share','iframe']:
            if token is None:
                return abort(403)
            elif VerifyToken(token,path)==False:
                return abort(403)
    if request.method=='POST' or action in ['share','iframe']:
        InfoLogger().print_r(u'share page:{}'.format(path))
        if ext in GetConfig('show_redirect').split(','):
            downloadUrl,play_url=GetDownloadUrl(fileid,user)
            resp=MakeResponse(redirect(downloadUrl))
        elif ext=='pdf':
            if action=='share':
                resp=MakeResponse(render_template('theme/{}/show/pdf.html'.format(GetConfig('theme')),url=url,path=path,cur_user=user,name=name))
            else:
                resp=MakeResponse(render_template('show/pdf.html'.format(GetConfig('theme')),url=url,path=path,cur_user=user,name=name))
        elif ext in GetConfig('show_doc').split(','):
            downloadUrl,play_url=GetDownloadUrl(fileid,user)
            url = 'https://view.officeapps.live.com/op/view.aspx?src='+urllib.parse.quote(downloadUrl)
            resp=MakeResponse(redirect(url))
        elif ext in GetConfig('show_image').split(','):
            if action=='share':
                resp=MakeResponse(render_template('theme/{}/show/image.html'.format(GetConfig('theme')),url=url,path=path,cur_user=user,name=name))
            else:
                resp=MakeResponse(render_template('show/image.html'.format(GetConfig('theme')),url=url,path=path,cur_user=user,name=name))

        elif ext in GetConfig('show_video').split(','):
            if action=='share':
                resp=MakeResponse(render_template('theme/{}/show/video.html'.format(GetConfig('theme')),url=url,path=path,cur_user=user,name=name))
            else:
                resp=MakeResponse(render_template('show/video.html'.format(GetConfig('theme')),url=url,path=path,cur_user=user,name=name))
        elif ext in GetConfig('show_dash').split(','):
            if action=='share':
                resp=MakeResponse(render_template('theme/{}/show/video2.html'.format(GetConfig('theme')),url=url,path=path,cur_user=user,name=name))
            else:
                resp=MakeResponse(render_template('show/video2.html'.format(GetConfig('theme')),url=url,path=path,cur_user=user,name=name))
        elif ext in GetConfig('show_audio').split(','):
            if action=='share':
                resp=MakeResponse(render_template('theme/{}/show/audio.html'.format(GetConfig('theme')),url=url,path=path,cur_user=user,name=name))
            else:
                resp=MakeResponse(render_template('show/audio.html'.format(GetConfig('theme')),url=url,path=path,cur_user=user,name=name))
        elif ext in GetConfig('show_code').split(','):
            content=common._remote_content(fileid,user)
            if action=="share":
                resp=MakeResponse(render_template('theme/{}/show/code.html'.format(GetConfig('theme')),content=content,url=url,language=CodeType(ext),path=path,cur_user=user,name=name))
            else:
                resp=MakeResponse(render_template('show/code.html'.format(GetConfig('theme')),content=content,url=url,language=CodeType(ext),path=path,cur_user=user,name=name))
        elif name=='.password':
            resp=MakeResponse(abort(404))
        else:
            downloadUrl,play_url=GetDownloadUrl(fileid,user)
            resp=MakeResponse(redirect(downloadUrl))
        return resp
    if name=='.password':
        resp=MakeResponse(abort(404))
    if 'no-referrer' in GetConfig('allow_site').split(',') or sum([i in referrer for i in GetConfig('allow_site').split(',')])>0:
        downloadUrl,play_url=GetDownloadUrl(fileid,user)
        if not downloadUrl.startswith('http'):
            return MakeResponse(downloadUrl)
        else:
            if GetConfig('redirect_file')=='True' or ext=='pdf':
                resp=redirect_file(user,fileid)
            else:
                resp=MakeResponse(redirect(play_url))
    else:
        resp=MakeResponse(abort(404))
    return resp

# @front.route('/py_redirect/<user>/<fileid>')
def redirect_file(user,fileid):
    filename=GetName(fileid)
    downloadUrl,play_url=GetDownloadUrl(fileid,user)
    req = browser.get(play_url, stream = True)
    headers = dict([(name, value) for (name, value) in req.raw.headers.items()])
    cache_root=os.path.join(GetConfig('config_dir'),'cache')
    if not os.path.exists(cache_root):
        os.mkdir(cache_root)
    filepath=os.path.join(cache_root,filename)
    if not os.path.exists(filepath):
        with open(filepath,'wb') as f:
            for chunk in req.iter_content(1024):
                if chunk:
                    f.write(chunk)
                    f.flush()
    resp=send_file(filepath,conditional=True)
    return resp

@front.route('/py_find/<key_word>')
def find(key_word):
    page=request.args.get('page',1,type=int)
    ajax=request.args.get('ajax','no')
    image_mode=request.args.get('image_mode')
    sortby=request.args.get('sortby')
    order=request.args.get('order')
    action=request.args.get('action','download')
    data,total=FetchData(path=key_word,page=page,per_page=50,sortby=sortby,order=order,dismiss=True,search_mode=True)
    pagination=Pagination(query=None,page=page, per_page=50, total=total, items=None)
    if ajax=='yes':
        retdata={}
        retdata['code']=0
        retdata['msg']=""
        retdata['total']=total
        retdata['data']=[]
        for d in data:
            info={}
            if d['type']=='folder':
                info['name']=''+d['name']+''
            else:
                info['name']=''+d['name']+''
            info['type']=d['type']
            info['lastModtime']=d['lastModtime']
            info['size']=d['size']
            info['path']=d['path']
            info['id']=d['id']
            retdata['data'].append(info)
        return jsonify(retdata)
    resp=MakeResponse(render_template('theme/{}/find.html'.format(GetConfig('theme'))
                    ,pagination=pagination
                    ,items=data
                    ,path='/'
                    ,sortby=sortby
                    ,order=order
                    ,key_word=key_word
                    ,cur_user='搜索:"{}"'.format(key_word)
                    ,endpoint='.find'))
    resp.set_cookie('image_mode',str(image_mode))
    resp.set_cookie('sortby',str(sortby))
    resp.set_cookie('order',str(order))
    return resp

@front.route('/robots.txt')
def robot():
    resp=GetConfig('robots')
    resp=MakeResponse(resp)
    resp.headers['Content-Type'] = 'text/javascript; charset=utf-8'
    return resp


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

"""
This module handles custom testing library generating.
"""

from robot.libdoc import libdoc
from robot.testdoc import testdoc

from sqa_engine.utilities.workspace_vars import inter_path
from sqa_engine.utilities.test_cases_management.test_suite_retriever import\
    get_module_children

def generate_docs(test_module: str, test_design: str) -> None:
    """Generate documentation.

    This function uses robot libdoc and robot testdoc to generates
    documentation of custom keyword library and test cases documentations,
    base on module and test domain.

    Parameters
    ----------
    test_module : str
        Test module {mobile_app, web_portal, rest_api}
    test_design : str
        Test domain specification

    Returns
    -------
    None
    """
    packages = get_module_children(module_name=test_module)
    test_suite = f'{packages["test_design"]}/{test_design}'
    robot_file = f'{test_suite}/{test_design}.robot'
    keywords_file = f'{packages["lib"]}/common_keywords.py'

    doc_out_file = f'{inter_path().current_doc_folder}/\
{test_design}_document.html'
    lib_out_file = f'{inter_path().current_doc_folder}/keywords_document.html'

    libdoc(library_or_resource=keywords_file, outfile=lib_out_file,
           name='', version='', format=None, docformat='HTML')
    testdoc(robot_file, doc_out_file, title='',
            name='', version='', format=None, docformat='HTML')
    return


import numpy as np
import pandas as pd
import time
from sklearn.linear_model import LogisticRegression
from save_grid_search_results import save_grid_search_results
import joblib

def log_regression(X_train, y_train, X_test, y_test, metrics=['accuracy'], class_weight=None, save_model = False, predict_rating=True, using_tfidf=True):

    #class weights
    class_weight = None if not class_weight else 'balanced'
    weighted = 'False' if not class_weight else 'True'
    predict = 'rating' if predict_rating else 'category'

    #create model and fit
    model = LogisticRegression(C=1e5, tol=1e-5, solver='liblinear', multi_class = 'ovr',\
                            random_state=3, max_iter=1000, class_weight=class_weight)
    model.fit(X_train,y_train)
    #evaluate the model
    accuracy = model.score(X_test, y_test)

    #save model and results
    path = r'./grid_search_results/' if using_tfidf else r'./doc2vec_grid_search_results/'
    file_name = ''
    if save_model:
        file_name = str(int(time.time())) + '.pkl'
        joblib.dump(model, path + file_name)
    results = save_grid_search_results(path, dict(file_name=file_name, predict=predict, predictor_type='log_regression', weighted=weighted, accuracy=accuracy))

    return results


from enum import Enum # for api enumeration
import hashlib

class ApiError(Exception):
    '''
        for all class using models
    '''
    def __init__(self, msg):
        super(ApiError, self).__init__(msg)

class _APIFactoryError(Exception):
    '''
        @ Exception : FactoryError
        --------------------------
        low level api instantiation error
    '''
    def __init__(self, msg):
        super(_APIFactoryError, self).__init__(msg)

class _APIs(Enum):
    '''
        @ enum : _APIs
        --------------
        for mapping the api object for instantiation
    '''
    Coinbase, Cexio, Rocktrade, Poloniex, Bitstamp, Gdax = range(6)

class _APImeta(type):
    '''
        @ metaclass : _APImeta
        -----------------------
        @ reqs : api_key
        @ reqs : api_secret
        @ reqs : api type
        @ reqs : api base endpoint
        @ suff : api_pass => optional for 3rd arg
    '''

    def __new__(meta, name, bases, clsdct):
        '''
            @ meta   => the `this` instance for metaclass
            @ name   => the name of concrete class
            @ bases  => the class meta
            @ clsdct => cls's `static` vars and method prototypes dict
        '''
        if not '_api_enumid' in clsdct:
            raise _APIFactoryError('The api type was not defined')
        e = clsdct['_api_enumid']
        if not _APImeta._verify_enumid(e):
            raise _APIFactoryError('The api enum id is invalid')
        return super(_APImeta, meta).__new__(meta, name, bases, clsdct)

    def __init__(cls, name, bases, dct):
        '''
            @ cls   : an api class
            @ name  : api class name
            @ bases : cls's namespace (prototype)
            @ dct   : cls's __dict__
        '''
        eid = dct['_api_enumid']
        if eid == _APIs.Rocktrade or eid == _APIs.Poloniex:
            dct['_digestmod'] = hashlib.sha512
        elif eid == _APIs.CoinBase or eid == _APIs.Cexio or \
            eid == _APIs.Bitstamp or eid == _APIs.Gdax:
            dct['_digestmod'] = hashlib.sha256
        super(_APImeta, cls).__init__(name, bases, dct)

    def _verify_enumid(eid):
        if eid != _APIs.CoinBase or \
            eid != _APIs.Cexio or \
            eid != _APIs.Rocktrade or \
            eid != _APIs.Bitstamp or \
            eid != _APIs.Gdax or \
            eid != _APIs.Poloniex:
            return False
        return True


from collections import Counter

def part_1():
    grid_counter = Counter()
    with open("inputs/day5", "r") as file:
        for line in file:
            start, end = line.strip().split("->")
            x1, y1 = map(int, start.split(","))
            x2, y2 = map(int, end.split(","))

            if x1 == x2 or y1 == y2:
                for x in range(min(x1, x2), max(x1, x2) + 1):
                    for y in range(min(y1, y2), max(y1, y2) + 1):
                        grid_counter[(x, y)] += 1

    return len([x for _, count in grid_counter.items() if count > 1])

def part_2():
    grid_counter = Counter()
    with open("inputs/day5", "r") as file:
        for line in file:
            start, end = line.strip().split("->")
            x1, y1 = map(int, start.split(","))
            x2, y2 = map(int, end.split(","))

            slope_x = 1 if x2 > x1 else -1
            slope_y = 1 if y2 > y1 else -1
            if x1 == x2:
                slope_x = 0
            if y1 == y2:
                slope_y = 0

            grid_counter[(x1, y1)] += 1
            while x1 != x2 or y1 != y2:
                x1 += slope_x
                y1 += slope_y
                grid_counter[(x1, y1)] += 1

    return len([_ for _, count in grid_counter.items() if count > 1])

print(part_1())
print(part_2())


import time
import json
from decimal import Decimal
from datetime import datetime

from sqlobject import DecimalCol, SQLObject
from sqlobject.col import pushKey

import beerlog

class JSONable(object):
    """ Mixin for SQLObject classes. Allows you to export the object
    as a JSON or dict representation, controlling the data returned
    from the call.

    You can either provide a list that enumerates the fields that
    are visible (meaning that all not listed are hidden) or enumerating
    fields that are hidden (meaning that all not listed are visible)

    ex:
        private = ['password', 'last_login']

    would mean all fields that AREN'T password or last_login will be in the
    json representation

        exports = ['email', 'first_name', 'last_name']

    would mean all that all fields that AREN'T email, first_name nor last_name
    will be hidden from the json respresetation

    TODO: set up roles and allow views specific to users with a specified role

    """

    def dict(self, view=None):
        return_dict = {}
        cls = type(self)
        props = [p for p in vars(cls) if isinstance(getattr(cls, p), property)]
        columns = type(self).sqlmeta.columns
        for prop in props:
            if hasattr(self, 'no_recurse'):
                no_recurse = self.no_recurse
            else:
                no_recurse = []
            if prop not in no_recurse:
                attr = getattr(self, prop)
                try:
                    # this means it's not a piece of derrived data nor is it a
                    # related object
                    column = columns[prop]
                except KeyError:
                    # this means it's a related object or a piece of derrived data
                    # if it's derrived data, we'll worry about it then, if it's
                    # a related object, we'll use it's `dict` method. if it's a list
                    # we'll iterate over the items and use their `dict` methods
                    if isinstance(attr, list):
                        value = [o.dict() for o in attr]
                    elif SQLObject in type(attr).__bases__ and hasattr(attr, "dict"):
                        value = attr.dict()
                    else:
                        value = attr
                else:
                    try:
                        c_type = column.type
                    except AttributeError:
                        value = None
                    else:
                        # this is just plain old data to convert.  we only care about
                        # the datatypes that `json.dumps` can't convert itself
                        if c_type == 'datetime':
                            value = time.mktime(attr.timetuple())*1000
                        elif c_type == 'sqlobject' and hasattr(attr, "dict"):
                            value = attr.dict()
                        elif c_type == 'decimal':
                            value = float(attr)
                        else:
                            value = attr
                if value:
                    return_dict[prop] = value
        return return_dict

    def json(self, view=None):
        return json.dumps(self.dict(view))

class SGCol(DecimalCol):
    """ Stores Specific Gravity in a decimal column
    Size is fixed at 4, and the precision is set to 3

    ex: 1.045

    """

    def __init__(self, **kw):
        pushKey(kw, 'size', 4)
        pushKey(kw, 'precision', 3)
        super(DecimalCol, self).__init__(**kw)

class PercentCol(DecimalCol):
    """Stores percentages in a decimal column
    Size is fixed at 5, and the precision is set to 2.

    *nb* size is fixed at 5 to allow for 100.00%

    """

    def __init__(self, **kw):
        pushKey(kw, 'size', 5)
        pushKey(kw, 'precision', 2)
        super(DecimalCol, self).__init__(**kw)

class SRMCol(DecimalCol):
    """ Stores the Standard Reference Method color value in a decimal column
    Size is fixed at 5, precision is set to 1

    ex: 2.0, 300.5

    """

    def __init__(self, **kw):
        pushKey(kw, 'size', 5)
        pushKey(kw, 'precision', 1)
        super(DecimalCol, self).__init__(**kw)

class IBUCol(DecimalCol):
    """ Stores the International Bitterness Units value in a decimal column
    Size is fixed at 4, precision is set to 1

    ex: 2.0, 300.5

    """

    def __init__(self, **kw):
        pushKey(kw, 'size', 4)
        pushKey(kw, 'precision', 1)
        super(DecimalCol, self).__init__(**kw)

class BatchIsNotMaster(Exception):
    def __init__(self,value):
        self.value = value
    def __unicode__(self,value):
        return repr(self.value)

class AmountSetError(Exception):
    def __init__(self, value):
        self.value = value
    def __unicode__(self, value):
        return repr(self.value)


# -*- coding: utf-8 -*-
import urllib.parse
from Common.Db import Db
from scrapy import Selector
from Config.Config import RUNTIME_DIR
from scrapy_redis.spiders import RedisSpider
from FirstChapter.FirstChapterItems import FirstChapterLoader
#从redis待采集链接队列中获取链接并采集
class FirstChapterspider(RedisSpider):
    name = 'fcSpider'
    redis_key = 'fcSpider_urls'
    currKey = 1
    workerFileName = 'fc_worker.txt'

    def __init__(self, *args, **kwargs):
        domain = kwargs.pop('domain', '')
        self.allowed_domains = filter(None, domain.split(','))
        super(FirstChapterspider, self).__init__(*args, **kwargs)

    def parse(self, response):
        #记录检测爬虫状态
        self.writeCurrNum()
        el = FirstChapterLoader(response=response)
        #根据链接域名查询数据库中的对应规则信息
        db = Db()
        sql = "SELECT * from zs_mirror WHERE m_domain='%s'" % self.extractDomainFromURL(response.url)
        row = db.query(sql, 'one')
        db.close()
        if row:
            #采集获取书籍章节等数据
            return self.contactItemInfo(response,row,el)

    # 获取Url中的域名
    def extractDomainFromURL(self, url):
        parsed_uri = urllib.parse.urlparse(url)
        domain = '{uri.netloc}'.format(uri=parsed_uri)
        return domain

    def contactItemInfo(self,response,row,el):
        # 检查书名是否正确
        bookName = self.getBookName(response, row['m_cp_title_rule'])
        if bookName != False:
            db = Db()
            sql = "SELECT id from zs_book where b_name='%s'" % bookName
            find = db.query(sql, 'one')
            db.close()
            if find:
                el = self.getItemInfo(response, row,el)
                if el != None:
                    el.add_value('b_id', str(find['id']))
                    return el.load_item()

    #根据数据库中配置的规则获取书籍名称
    def getBookName(self,response,tags):
         selector = Selector(response)
         try:
             path = selector.xpath(tags)
             if path:
                 bookName = path.xpath('./text()').extract_first('').strip()
                 if bookName != '':
                     return bookName
         except Exception:
             return False
         return False

    #获取源站数据与最新章节信息
    def getItemInfo(self,response,row,el):
        # 获取书籍对应源站链接标识
        bookInfoPath = ((urllib.parse.urlparse(response.url)).path).strip('/')
        if row['m_cp_last_rule'] != '':
            # 获取书籍最新章节信息
            lastChapter = self.getLastChapter(response, row['m_cp_last_rule'])
            if lastChapter:
                el.add_value('m_id',str(row['id']))
                el.add_value('m_name',row['m_name'] + '(' + row['m_domain'] + ')')
                el.add_value('b_no',bookInfoPath)
                el.add_value('last_title',lastChapter['title'])
                el.add_value('last_url',lastChapter['url'])
                return el

    #获取最新章节信息
    def getLastChapter(self,response,tags):
        lastChapter = Selector(response).xpath(tags)
        lastInfo = {}
        lastInfo['title'] = lastChapter.xpath('./text()').extract_first('').strip()
        lastInfo['url'] = lastChapter.xpath('.//@href').extract_first('').strip()
        if lastInfo['title'] != '' and lastInfo['url'] != '' :
            return lastInfo

    def writeCurrNum(self):
        self.currKey = self.currKey+1
        file = open(RUNTIME_DIR+self.workerFileName, 'w')
        file.write(str(self.currKey))
        file.close()


import os, sys
import pandas as pd
import numpy as np
from keras.models import Sequential, load_model
from keras.layers import Dense, Dropout, Embedding, Reshape, Merge
from keras.callbacks import EarlyStopping, ModelCheckpoint

# path
data_train = 'train.csv'
data_test = 'test.csv'
data_users = 'users.csv'
data_movies = 'movies.csv'
data_directory = sys.argv[1] + os.sep if len(sys.argv) > 1 else './data/'
prediction_file = sys.argv[2] if len(sys.argv) > 2 else 'predict.csv'

# user parameter
K_FACTORS = 120
best_model_weight_path = './model/mf_weight_best.h5'

# load data
df_train = pd.read_csv(data_directory + data_train)
df_test = pd.read_csv(data_directory + data_test)
df_users = pd.read_csv(data_directory + data_users, sep='::')
df_movies = pd.read_csv(data_directory + data_movies, sep='::')

# get numpy array for train and test
train_UserID = df_train['UserID'].values
train_MovieID = df_train['MovieID'].values
train_Rating = df_train['Rating'].values.astype('float32')

test_DataID = df_test['TestDataID'].values
test_UserID = df_test['UserID'].values
test_MovieID = df_test['MovieID'].values

# max id
max_userid = np.max(train_UserID).astype('int')
max_movieid = np.max(train_MovieID).astype('int')

# shuffle train data
indices = np.arange(train_UserID.shape[0])
np.random.shuffle(indices)
train_UserID = train_UserID[indices]
train_MovieID = train_MovieID[indices]
train_Rating = train_Rating[indices]

# split validation
valid_size = 5000
valid_UserID = train_UserID[:valid_size]
valid_MovieID = train_MovieID[:valid_size]
valid_Rating = train_Rating[:valid_size]
train_UserID  = train_UserID[valid_size:]
train_MovieID  = train_MovieID[valid_size:]
train_Rating  = train_Rating[valid_size:]

# data info
print('-'*30)
print('valid_UserID:', valid_UserID.shape)
print('valid_MovieID:', valid_MovieID.shape)
print('valid_Rating:', valid_Rating.shape)
print('train_UserID:', train_UserID.shape)
print('train_MovieID:', train_MovieID.shape)
print('train_Rating:', train_Rating.shape)
print('test_UserID:', test_UserID.shape)
print('test_MovieID:', test_MovieID.shape)
print('-'*30)

# build model
def build_model():
	model = Sequential()
	P = Sequential()
	P.add(Embedding(max_userid + 1, K_FACTORS, input_length=1))
	P.add(Reshape((K_FACTORS,)))
	Q = Sequential()
	Q.add(Embedding(max_movieid + 1, K_FACTORS, input_length=1))
	Q.add(Reshape((K_FACTORS,)))
	model.add(Merge([P, Q], mode='dot', dot_axes=1))
	model.compile(loss='mean_squared_error', optimizer='adam', metrics=[])
	model.summary()
	return model

model = build_model()

# train
earlystopping = EarlyStopping(monitor='val_loss', patience=10, verbose=0, mode='min')
checkpoint = ModelCheckpoint(
				filepath=best_model_weight_path,
				verbose=1,
				save_best_only=True,
				save_weights_only=True,
				monitor='val_loss',
				mode='min'
			)
model.fit(
		[train_UserID, train_MovieID], train_Rating,
		batch_size=512, epochs=1000,
		validation_data=([valid_UserID,valid_MovieID],valid_Rating),
		callbacks=[earlystopping, checkpoint]
	)

# load best model
model = build_model()
model.load_weights(best_model_weight_path)

# predict
y_preds = model.predict([test_UserID, test_MovieID]).reshape(-1)
df_pred = pd.DataFrame({'TestDataID': test_DataID, 'Rating': y_preds})
df_pred.to_csv(prediction_file, columns=['TestDataID','Rating'], index=False)


"""Services for the Pod Point integration."""
from typing import List

from homeassistant.core import HomeAssistant, ServiceCall
import homeassistant.helpers.config_validation as cv
from podpointclient.client import PodPointClient
from podpointclient.pod import Pod
import voluptuous as vol

from .const import (
    ATTR_CONFIG_ENTRY_ID,
    ATTR_HOURS,
    ATTR_MINUTES,
    ATTR_SECONDS,
    DOMAIN,
    SERVICE_CHARGE_NOW,
)
from .coordinator import PodPointDataUpdateCoordinator

class PodPointServiceException(Exception):
    """Exception for Pod Point services."""

async def async_register_services(hass: HomeAssistant) -> None:
    """Register services for the Pod Point integration, if not registered yet."""

    if hass.services.has_service(DOMAIN, SERVICE_CHARGE_NOW):
        return

    async def async_charge_now_service(call: ServiceCall):
        coordinator = await get_coordinator(hass, call.data[ATTR_CONFIG_ENTRY_ID])
        await handle_charge_now(hass, coordinator, call)

    hass.services.async_register(
        DOMAIN,
        SERVICE_CHARGE_NOW,
        async_charge_now_service,
        schema=vol.Schema(
            {
                vol.Required(ATTR_CONFIG_ENTRY_ID): cv.string,
                vol.Optional(ATTR_HOURS): vol.All(vol.Coerce(int), vol.Range(min=0,max=24)),
                vol.Optional(ATTR_MINUTES): vol.All(vol.Coerce(int), vol.Range(min=0,max=59)),
                vol.Optional(ATTR_SECONDS): vol.All(vol.Coerce(int), vol.Range(min=0,max=59))
            }
        ),
    )

async def async_deregister_services(hass: HomeAssistant) -> None:
    """Deregister services for the Pod Point integration"""

    hass.services.async_remove(
        DOMAIN,
        SERVICE_CHARGE_NOW
    )

async def get_coordinator(hass: HomeAssistant, config_entry_id: str) -> PodPointDataUpdateCoordinator:
    """Get the right Pod Point Data Coordinator based on the device id, else get the default one."""
    if config_entry_id not in hass.data[DOMAIN]:
        raise ValueError(f"Config entry with id {config_entry_id} not found!")

    coordinator: PodPointDataUpdateCoordinator = hass.data[DOMAIN][config_entry_id]
    return coordinator

async def handle_charge_now(
    hass: HomeAssistant, coordinator: PodPointDataUpdateCoordinator, call: ServiceCall
) -> None:
    """Handle the call for the add_product service."""
    api: PodPointClient = coordinator.api
    pods: List[Pod] = coordinator.pods
    pod: Pod
    if len(pods) == 1:
        pod = pods[0]
    else:
        PodPointServiceException(f"Service only supports accounts with 1 Pod attached, found {len(pods)} Pods!")

    hours = call.data.get(ATTR_HOURS, 0)
    minutes = call.data.get(ATTR_MINUTES, 0)
    seconds = call.data.get(ATTR_SECONDS, 0)

    hours_set = 0 < hours <= 24
    minutes_set = 0 < minutes <= 59
    seconds_set = 0 < seconds <= 59
    valid_time_passed = (
        hours_set
        or minutes_set
        or seconds_set
    )

    if valid_time_passed is False:
        raise PodPointServiceException("Please pass an hours, minutes or seconds value. Cannot set 'charge now' with 0 values.")

    await api.async_set_charge_override(pod=pod, hours=hours, minutes=minutes, seconds=seconds)

    await coordinator.async_request_refresh()


import datetime

from django.contrib.auth.decorators import login_required
from django.http import Http404
from django.shortcuts import render, redirect

import diploms
from diploms.models import Message, Comment, Vacansia, City, Interview, News
from diploms.forms import CommentForm, InterviewForm, MessageForm  # , VacansiaForm
from django.contrib import messages as mes

# Create your views here.
def index(request):
    news = News.objects.order_by("date_published")[:3]
    context = {'news': news}
    return render(request, 'diploms/index.html', context)

def about(request):
    comments = Comment.objects.order_by('-date_added')
    for comment in comments:
        if compare_owner_user(comment.owner, request.user):
            comment.is_editing = True
        else:
            comment.is_editing = False
    if request.method != 'POST':
        form = CommentForm()
    else:
        form = CommentForm(data=request.POST)
        if form.is_valid():
            new_comment = form.save(commit=False)
            new_comment.owner = request.user
            new_comment.save()
            return redirect('about')

    context = {
        'form': form,
        'comments': comments,
    }
    return render(request, 'diploms/about.html', context)

def partner(request):
    return render(request, 'diploms/partner.html')

def connect(request):
    return render(request, 'diploms/connect.html')

@login_required
def messages(request):
    messages = Message.objects.filter(owner=request.user)
    context = {'messages': messages}
    return render(request, 'diploms/messages.html', context)

def compare_owner_user(owner, user):
    if owner == user:
        return True
    else:
        return False

@login_required
def delete(request, comment_id):
    comment = Comment.objects.get(id=comment_id)
    if not compare_owner_user(comment.owner, request.user):
        raise Http404
    comment.delete()
    mes.success(request, 'Ваш комментарий был успешно удален!')
    return redirect('about')

@login_required
def edit(request, comment_id):
    comment = Comment.objects.get(id=comment_id)
    if not compare_owner_user(comment.owner, request.user):
        raise Http404
    if request.method != 'POST':
        form = CommentForm(instance=comment)
    else:
        form = CommentForm(instance=comment, data=request.POST)
        if form.is_valid():
            form.save()
            mes.success(request, 'Комментарий успешно изменен.')
            return redirect('about')
    context = {
        'comment': comment,
        'form': form,
    }
    return render(request, 'diploms/edit.html', context)

def vacansies(request):
    if request.POST.get('city') and request.POST.get('city') != 'Любой':
        city_name = request.POST.get('city')
        try:
            picked_city = City.objects.get(city=city_name)
            vacs = Vacansia.objects.filter(is_active=True, city=picked_city)
        except diploms.models.City.DoesNotExist:
            vacs = None

    else:
        vacs = Vacansia.objects.filter(is_active=True)

    cities = City.objects.all()
    context = {
        'vacs': vacs,
        'cities': cities,

    }
    return render(request, 'diploms/vacansies.html', context)

@login_required
def vacansia(request, vacansia_id):
    vacansia_h = Vacansia.objects.get(id=vacansia_id)
    form = InterviewForm()

    available_times = [datetime.time(i, 0, 0).isoformat(timespec='minutes') for i in range(9, 16)]
    context = {
        'today': str(datetime.datetime.date(datetime.datetime.today() + datetime.timedelta(1))),
        'max_date': str(datetime.datetime.date(datetime.datetime.today() + datetime.timedelta(8))),
        'available_times': available_times,
        'vacansia': vacansia_h,
        'form': form,
    }
    if request.method == 'POST':
        if Interview.objects.filter(date=request.POST.get('date'), time=request.POST.get('time')):
            mes.error(request, 'Выбранное время уже занято!Выберите другое время!')
            return render(request, 'diploms/vacansia.html', context)
        else:
            form = InterviewForm(data=request.POST)
            if form.is_valid():
                new_сandidate = form.save(commit=False)
                new_сandidate.сandidate = request.user
                new_сandidate.save()
                mes.success(request, 'Запись на собеседование прошла успешно.Ожидайте звонка.')
                return render(request, 'diploms/vacansia.html', context)
    return render(request, 'diploms/vacansia.html', context)

def news(request):
    newsSet = News.objects.order_by("date_published")
    context = {'news': newsSet}
    return render(request, 'diploms/news.html', context)

def new(request, new_id):
    new = News.objects.get(id=new_id)
    context = {'new': new}
    return render(request, 'diploms/new.html', context)

@login_required
def support(request):
    if request.method != 'POST':
        form = MessageForm()
    else:
        form = MessageForm(request.POST,request.FILES)
        if form.is_valid():
            new_mes = form.save(commit=False)
            new_mes.owner = request.user

            new_mes.save()
            mes.success(request,'Ваше обращение успешно отправлено')
            return redirect('support')
    context = {
        'form': form
    }
    return render(request, 'diploms/support.html', context)


# coding=utf-8
# @Author: 莫冉
# @Date: 2021-02-04
from typing import List, Tuple, Dict
from typing import Union, Optional, Any
from dataclasses import dataclass

from novela import logger
from novela.novel.person import Person
from novela.novel.outline import Outline
from novela.novel.story_line import StoryLine

logger = logger.getChild("comic")

@dataclass
class Sentence:
    """
    表示一句话，可能是人物的对话、内心独白，或者是旁白
    """
    speaker: str = None           # 表示说话人的名字
    sentence: str = None          # 表示说话的内容

@dataclass
class Section:
    """
    表示漫画/小说中的一个章节
    """
    section_id: int = -1                 # 表示该章节属于哪一话
    title: str = None                    # 表示章节的标题
    sentences: List[Sentence] = None     # 表示该章节中的语句

class Comic:
    def __init__(self, name: str = None):
        self._name = name                                   # 表示小说/漫画的名称
        self.script_writer: str = None                      # 表示漫画编剧
        self.sections: List[Section] = []                   # 表示该小说/漫画的所有章节
        self._first_role: Person = None                      # 表示该小说的第一主角
        self._second_role: Person = None                     # 表示该小说的第二主角
        self.other_roles: List[Person] = []                 # 表示小说的其他角色
        self.relations: List[Tuple[str, str, str]] = []     # 表示小说中存在的人物关系，每一个元素是一个三元组(head, relation, tail)

        self.outline: Outline = None                        # 表示漫画的大纲（novel_author对应`原作`，comic_author对应`主笔`）
        self.storyline: StoryLine = None                    # 表示整个故事的起承转合

        # 将所有角色放到一个列表中便于索引
        self.roles = [self._first_role, self._second_role] + self.other_roles
        # 保存所有角色的名字
        self.roles_names = []
        for role in self.roles:
            if role is not None:
                self.roles_names.append(role.name)
            else:
                self.roles_names.append(None)

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, n: str):
        self._name = n

    @property
    def first_role(self):
        return self._first_role

    @first_role.setter
    def first_role(self, r: Person):
        self._first_role = r
        self.roles[0] = self._first_role
        self.roles_names[0] = self._first_role.name

    @property
    def second_role(self):
        return self._second_role

    @second_role.setter
    def second_role(self, r: Person):
        self._second_role = r
        self.roles[1] = self._second_role
        self.roles_names[1] = self._second_role.name

    def _inspect_section(self, section_id: int):
        """
        表示根据section_id检查当前sections的数量，如果没有则添加对应的section
        :param section_id:
        :return:
        """
        if section_id < 0:
            raise ValueError(f"Section ID must be equal or greater than 0, while get `{section_id}`")
        section_length = len(self.sections)
        if section_id < section_length:
            pass
        elif section_id == section_length:
            self.sections.append(None)
        else:
            none_section_num = section_id - section_length + 1
            self.sections += ([None] * none_section_num)

    def _inspect_relation_triplets(self, relation: Tuple[str, str, str]):
        """检查relation triplets的类型"""
        if not isinstance(relation, tuple):
            raise TypeError(f"The relation triplets should be `tuple` type, while get `{type(relation)}`!")
        if len(relation) != 3:
            raise IndexError(f"The relation triplets should have three elements, namely (`head`,`relation`, `tail`), "
                             f"while the accepted `relation`'s length is `{len(relation)}`!")
        if not (isinstance(relation[0], str) and isinstance(relation[1], str) and isinstance(relation[2], str)):
            raise TypeError(f"We hope that the elements of `relation` are `str` type, while `head`/`relation`/`tail` "
                            f"is respectively `{type(relation[0])}`/`{type(relation[1])}`/`{type(relation[2])}`!")

    def _append_sentence_to_role(self, sentence: Sentence, section_id: int):
        """向对应章节添加语句之后，还需要考虑该语句的讲话人，并且向对应的角色中添加对应的语句"""
        # 首先，如果当前语句的对话人不为None
        # 否则考虑向include中添加
        if sentence.speaker is not None and len(sentence.speaker) > 0:
            self.add_dialog_sentence_to_role(role_name=sentence.speaker,
                                             sents=sentence.sentence,
                                             section=section_id)
        else:
            # 检查当前语句是否包含人名
            # 如果包含，添加到对应人名的include中去
            for name in self.roles_names:
                if name in sentence.sentence:
                    self.add_include_sentence_to_role(role_name=name,
                                                      sents=sentence.sentence,
                                                      section=section_id)

    def add_section(self, section: Section):
        """
        添加一个章节
        :param section:
        :return:
        """
        # 获取当前章节的id
        section_id = section.section_id
        self._inspect_section(section_id)
        self.add_sentences_to_section(sentences=section.sentences,
                                      section_id=section_id,
                                      title=section.title)

    def add_sections(self, sections: List[Section]):
        """
        添加多个章节
        :param sections:
        :return:
        """
        # 将每一个章节逐个添加进去
        for section in sections:
            self.add_section(section)

    def add_sentence_to_section(self,
                                sentence: Union[Sentence, Tuple[str, str]],
                                section_id: int,
                                title: Optional[str] = None):
        """
        将一个句子添加到某个章节中
        :param sentence: 这里的sentence要么是Sentence对象；要么是Tuple[str, str]，第一个str表示说话人，第二个str表示说话内容
        :param section_id: int型，表示需要添加到的章节id
        :param title: str型，可选参数，如果已知当前章节原来没有出现，最好指定标题创建新的对象
        :return:
        """
        if sentence is None:
            return
        # 首先检查当前的sections是否能够满足section_id的需求
        self._inspect_section(section_id)
        # 如果当前的section_id对应的位置是None，则需要新建一个Section对象
        if self.sections[section_id] is None:
            self.sections[section_id] = Section(section_id=section_id, title=title)

        if isinstance(sentence, tuple):
            # 根据Tuple的内容创建Sentence对象
            sentence_obj = Sentence(speaker=sentence[0], sentence=sentence[1])
            sentence = sentence_obj

        self.sections[section_id].sentences.append(sentence)
        self._append_sentence_to_role(sentence=sentence, section_id=section_id)

    def add_sentences_to_section(self,
                                 sentences: Union[List[Sentence], List[Tuple[str, str]]],
                                 section_id: int,
                                 title: Optional[str] = None):
        """
        将多个句子添加到一个章节中
        """
        if len(sentences) <= 0:
            return
        for sentence in sentences:
            self.add_sentence_to_section(sentence, section_id, title)

    def add_sentences_to_sections(self,
                                  contents: List[Dict[str, Any]]):
        """
        将多个句子添加到多个章节中
        :param contents: 这里的contents是一个list型，每一个元素是一个dict，包含一个章节以及其需要添加的句子
                         至少应该包含`section_id`字段以及`sentences`字段，`title`字段可选
        :return:
        """
        if len(contents) <= 0:
            return
        section_count = 0
        sentence_count = 0
        for i, content in enumerate(contents):
            if ("section_id" in content) and ("sentences" in content):
                section_id = content["section_id"]
                sentences = content["sentences"]
                title = None if "title" not in content else content["title"]
                self.add_sentences_to_section(sentences=sentences,
                                              section_id=section_id,
                                              title=title)
                section_count += 1
                sentence_count += len(sentences)
            else:
                logger.warning(f"第{i}个元素当前内容的键中{content.keys()}没有同时包含`section_id`和`sentences`！！！")

        logger.info(f"共计添加了{sentence_count}个句子到{section_count}个章节中！")

    def add_new_role(self, role: Person, whether_exist: bool = True, replace: bool = True):
        """
        向other roles中添加新的角色
        :param role: Person类型，表示新角色
        :param whether_exist: bool型，表示是否需要判断该角色存在与否
        :param replace: bool型，如果存在该角色是否替换
        :return:
        """
        exist = False
        if whether_exist:
            for i, r in enumerate(self.roles):
                if r is not None:
                    if r.name == role.name:
                        exist = True
                        if replace:
                            self.roles[i] = role
                            self.roles_names[i] = role.name
                            self.other_roles[i-2] = role
                        break
        # 如果没有存在，则添加到Other roles和roles中
        if not exist:
            self.other_roles.append(role)
            self.roles.append(role)
            self.roles_names.append(role.name)

    def get_role_according_name(self, role_name: str) -> Person:
        """根据角色名字获取角色对象"""
        # 遍历已有的角色列表
        for role in self.roles:
            if role is not None:
                if role.name == role_name:
                    return role

        # 如果遍历完所有的角色仍然没有找到，则创建该角色
        role = Person(name=role_name)
        self.add_new_role(role, whether_exist=False)
        return role

    def _add_info_to_role(self, role: Person, **kwargs):
        if role is None:
            return
        if len(kwargs) <= 0:
            return
        # 向该人物中添加一些信息
        role.add_info_to_person(**kwargs)

    def add_info_to_first_role(self, **kwargs):
        """添加第一主角的角色信息"""
        if self.first_role is None:
            self.first_role = Person()
            self.roles[0] = self.first_role
            self.roles_names[0] = self.first_role.name
        self._add_info_to_role(self.first_role, **kwargs)

    def add_info_to_second_role(self, **kwargs):
        """添加第二主角的角色信息"""
        if self.second_role is None:
            self.second_role = Person()
            self.roles[1] = self.second_role
            self.roles_names[1] = self.second_role.name
        self._add_info_to_role(self.second_role, **kwargs)

    def add_info_to_role(self, role_name: str, **kwargs):
        """可以根据名字向任何角色中添加信息"""
        # 首先根据名字获取该角色对象
        role = self.get_role_according_name(role_name)
        self._add_info_to_role(role, **kwargs)

    def add_relations(self, relations: Union[Tuple[str, str, str], List[Tuple[str, str, str]]]):
        """向关系三元组中添加关系"""
        if relations is None:
            return
        if isinstance(relations, tuple):
            # 首先检查relations的类型是否正确
            self._inspect_relation_triplets(relations)
            self.relations.append(relations)
        elif isinstance(relations, list):
            for ele in relations:
                self._inspect_relation_triplets(ele)
                self.relations.append(ele)
        else:
            raise TypeError(f"Only `tuple` or `List[tuple]` can be recognized, while get `{type(relations)}` type.")

    def add_outline(self, outline: Optional[Outline] = None, **kwargs):
        """向漫画类中添加一个大纲Outline，或者向已有的Outline中添加一些信息"""
        if outline is not None:
            self.outline = outline
        else:
            if self.outline is None:
                self.outline = Outline()
            self.outline.add_info_to_outline(**kwargs)

    def add_storyline(self, storyline: Optional[StoryLine] = None, **kwargs):
        """向漫画类中添加一个故事剧情走向属性Storyline，或者向已有的Storyline中添加一些信息"""
        if storyline is not None:
            self.storyline = storyline
        else:
            if self.storyline is None:
                self.storyline = StoryLine()
            self.storyline.add_info_to_storyline(**kwargs)

    def _add_dialog_sentence_to_role(self,
                                     role: Person,
                                     sents: Optional[Union[str, List[str]]] = None,
                                     section: int = 0,
                                     sents_dict: Optional[Dict[int, Union[str, List[str]]]] = None):

        if sents is None and sents_dict is None:
            logger.warning(f"No dialog sentences added to {role.name} at this time.")
            return
        if sents is not None:
            if isinstance(sents, str):
                role.add_sent2dialog(sents, section)
            elif isinstance(sents, list):
                role.add_sents2dialog(sents, section)
            else:
                raise TypeError(f"Only `str` or `List[str]` can be accepted, while get `{type(sents)}`.")
        else:
            # key表示章节，是int型；value表示该章节对应的句子内容
            for key, value in sents_dict.items():
                if isinstance(value, str):
                    role.add_sent2dialog(value, key)
                elif isinstance(value, list):
                    role.add_sents2dialog(value, key)
                else:
                    raise TypeError(f"Only `str` or `List[str]` can be accepted, while get `{type(value)}`.")

    def _add_include_sentence_to_role(self,
                                      role: Person,
                                      sents: Optional[Union[str, List[str]]] = None,
                                      section: int = 0,
                                      sents_dict: Optional[Dict[int, Union[str, List[str]]]] = None):

        if sents is None and sents_dict is None:
            logger.warning(f"No dialog sentences added to {role.name} at this time.")
            return
        if sents is not None:
            if isinstance(sents, str):
                role.add_sent2include(sents, section)
            elif isinstance(sents, list):
                role.add_sents2include(sents, section)
            else:
                raise TypeError(f"Only `str` or `List[str]` can be accepted, while get `{type(sents)}`.")
        else:
            # key表示章节，是int型；value表示该章节对应的句子内容
            for key, value in sents_dict.items():
                if isinstance(value, str):
                    role.add_sent2include(value, key)
                elif isinstance(value, list):
                    role.add_sents2include(value, key)
                else:
                    raise TypeError(f"Only `str` or `List[str]` can be accepted, while get `{type(value)}`.")

    def add_dialog_sentence_to_first_role(self,
                                          sents: Optional[Union[str, List[str]]] = None,
                                          section: int = 0,
                                          sents_dict: Optional[Dict[int, Union[str, List[str]]]] = None):
        """向第一主角添加该角色的对话内容"""
        self._add_dialog_sentence_to_role(self.first_role,
                                          sents=sents,
                                          section=section,
                                          sents_dict=sents_dict)

    def add_include_sentence_to_first_role(self,
                                           sents: Optional[Union[str, List[str]]] = None,
                                           section: int = 0,
                                           sents_dict: Optional[Dict[int, Union[str, List[str]]]] = None):
        """向第一主角添加包含该角色名字的语句"""
        self._add_include_sentence_to_role(self.first_role,
                                           sents=sents,
                                           section=section,
                                           sents_dict=sents_dict)

    def add_dialog_sentence_to_second_role(self,
                                           sents: Optional[Union[str, List[str]]] = None,
                                           section: int = 0,
                                           sents_dict: Optional[Dict[int, Union[str, List[str]]]] = None):
        """向第二主角中添加该角色的对话内容"""
        self._add_dialog_sentence_to_role(self.second_role,
                                          sents=sents,
                                          section=section,
                                          sents_dict=sents_dict)

    def add_include_sentence_to_second_role(self,
                                            sents: Optional[Union[str, List[str]]] = None,
                                            section: int = 0,
                                            sents_dict: Optional[Dict[int, Union[str, List[str]]]] = None):
        """向第二主角添加包含该角色名字的语句"""
        self._add_include_sentence_to_role(self.second_role,
                                           sents=sents,
                                           section=section,
                                           sents_dict=sents_dict)

    def add_dialog_sentence_to_role(self,
                                    role_name: str,
                                    sents: Optional[Union[str, List[str]]] = None,
                                    section: int = 0,
                                    sents_dict: Optional[Dict[int, Union[str, List[str]]]] = None):
        """根据角色名字，向该角色中添加对话内容"""
        # 首先根据名字获取该角色的对象
        role = self.get_role_according_name(role_name)
        self._add_dialog_sentence_to_role(role,
                                          sents=sents,
                                          section=section,
                                          sents_dict=sents_dict)

    def add_include_sentence_to_role(self,
                                     role_name: str,
                                     sents: Optional[Union[str, List[str]]] = None,
                                     section: int = 0,
                                     sents_dict: Optional[Dict[int, Union[str, List[str]]]] = None):
        """根据角色名字，向该角色中添加包含该角色名字的语句"""
        # 首先根据名字获取该角色的对象
        role = self.get_role_according_name(role_name)
        self._add_include_sentence_to_role(role,
                                           sents=sents,
                                           section=section,
                                           sents_dict=sents_dict)

    def assign_section_sents_to_role(self):
        """将当前section中的所有Sentence给到分配到各个角色中"""
        if (self.sections is None) or (len(self.sections) <= 0):
            return

        for section in self.sections:
            section_id = section.section_id
            sentences = section.sentences
            # 将每一个句子添加到对应任务的内容中
            for sentence in sentences:
                self._append_sentence_to_role(sentence=sentence,
                                              section_id=section_id)

    def get_sentences(self) -> Dict[int, List[str]]:
        """获取当前漫画的所有语句"""
        if (self.sections is None) or (len(self.sections) <= 0):
            return {}

        sentences = {}
        for section in self.sections:
            section_id = section.section_id
            sents = []
            for sent in section.sentences:
                sents.append(sent.sentence)
            sentences.update({section_id:sents})
        return sentences

    def get_first_role_dialog_sentences(self) -> Dict[int, List[str]]:
        return self.first_role.dialog_sents

    def get_first_role_include_sentences(self) -> Dict[int, List[str]]:
        return self.first_role.include_sents

    def get_second_role_dialog_sentences(self) -> Dict[int, List[str]]:
        return self.second_role.dialog_sents

    def get_second_role_include_sentences(self):
        return self.second_role.include_sents

    def get_dialog_sentences_by_role_name(self, role_name: str) -> Dict[int, List[str]]:
        role = self.get_role_according_name(role_name)
        return role.dialog_sents

    def get_include_sentences_by_role_name(self, role_name: str) -> Dict[int, List[str]]:
        role = self.get_role_according_name(role_name)
        return role.include_sents


from prolog_structures import Rule, RuleBody, Term, Function, Variable, Atom, Number
from typing import List
from functools import reduce

import sys
import random

class Not_unifiable(Exception):
	pass

'''
Please read prolog_structures.py for data structures
that represent Prolog terms, rules, and goals.
'''
class Interpreter:
	def __init__(self):
		pass

	'''
	Example
	occurs_check (v, t) where v is of type Variable, t is of type Term.
	occurs_check (v, t) returns true if the Prolog Variable v occurs in t.
	Please see the lecture note Control in Prolog to revisit the concept of
	occurs-check.
	'''
	def occurs_check (self, v : Variable, t : Term) -> bool:
		if isinstance(t, Variable):
			return v == t
		elif isinstance(t, Function):
			for t in t.terms:
				if self.occurs_check(v, t):
					return True
			return False
		return False

	'''
	Problem 1
	variables_of_term (t) where t is of type Term.
	variables_of_clause (c) where c is of type Rule.

	The function should return the Variables contained in a term or a rule
	using Python set.

	The result must be saved in a Python set. The type of each element (a Prolog Variable)
	in the set is Variable.
	'''
	def variables_of_term (self, t : Term) -> set :
		if isinstance(t, Variable):
			return set(([t]))
		elif isinstance(t, Function):
			x = set()
			for t in t.terms:
				if isinstance(t, Variable):
					x.add(t)
			return x
		return set()

	def variables_of_clause (self, c : Rule) -> set :
		x = self.variables_of_term(c.head)
		for i in c.body.terms:
			x.union(self.variables_of_term(i))

		return x

	'''
	Problem 2
	substitute_in_term (s, t) where s is of type dictionary and t is of type Term
	substitute_in_clause (s, t) where s is of type dictionary and c is of type Rule,

	The value of type dict should be a Python dictionary whose keys are of type Variable
	and values are of type Term. It is a map from variables to terms.

	The function should return t_ obtained by applying substitution s to t.

	Please use Python dictionary to represent a subsititution map.
	'''
	def substitute_in_term (self, s : dict, t : Term) -> Term:
		if s =={}:
			return t
		elif isinstance(t, Function):
			term_helper = []
			for x in t.terms:
				term_helper.append(self.substitute_in_term(s,x))
			return Function(t.relation, term_helper)
		elif isinstance(t, Variable):
			term_helper = s.get(t,t)
			return term_helper
		elif isinstance(t, Atom):
			term_helper = s.get(t,t)
			return term_helper
		elif isinstance(t, Number):
			term_helper = s.get(t.value,t.value)
			term_helper = str(term_helper)
			return Number(term_helper)

	def substitute_in_clause (self, s : dict, c : Rule) -> Rule:
		if s == {}:
			return c
		head_helper = self.substitute_in_term(s, c.head)
		term_helper = []
		for x in c.body.terms:
			term_helper.append(self.substitute_in_term(s,x))
		return Rule(head_helper, RuleBody(term_helper))

	'''
	Problem 3
	unify (t1, t2) where t1 is of type term and t2 is of type Term.
	The function should return a substitution map of type dict,
	which is a unifier of the given terms. You may find the pseudocode
	of unify in the lecture note Control in Prolog useful.

	The function should raise the exception raise Not_unfifiable (),
	if the given terms are not unifiable.

	Please use Python dictionary to represent a subsititution map.
	'''
	def funct (self, s, tup):
		x = tup[0]
		y = tup[1]
		return self.unify_helper(x,y,s)

	def unify_helper(self, prevx, prevy, s:dict):
		x = self.substitute_in_term(s, prevx)
		y = self.substitute_in_term(s, prevy)
		if isinstance(x, Variable) and (self.occurs_check(x,y) == False):
			Snew = {x:y}
			for key,value in s.items():
				ValNew = self.substitute_in_term(Snew, value)
				s[key] = ValNew
			s[x] = y
			return s
		elif isinstance(y, Variable) and (self.occurs_check(x,y) == False):
			Snew = {y:x}
			for key,value in s.items():
				ValNew = self.substitute_in_term(Snew, value)
				s[key] = ValNew
			s[y] = x
			return s
		elif x == y:
			return s
		elif isinstance(x, Function) and isinstance(y, Function):
			if x.relation != y.relation:
				raise Not_unifiable
			TermsOfX = []
			for i in x.terms:
				TermsOfX.append(i)
			TermsOfY = []
			for i in y.terms:
				TermsOfY.append(i)
			if len(TermsOfY) != len(TermsOfX):
				raise Not_unifiable
			tupes = []
			for i in range(len(TermsOfX)):
				tupes.append((TermsOfX[i],TermsOfY[i]))
			return(reduce(self.funct, tupes, s))
		else:
			raise Not_unifiable
	def unify (self, t1: Term, t2: Term) -> dict:
		return self.unify_helper(t1,t2,{})

	fresh_counter = 0
	def fresh(self) -> Variable:
		self.fresh_counter += 1
		return Variable("_G" + str(self.fresh_counter))
	def freshen(self, c: Rule) -> Rule:
		c_vars = self.variables_of_clause(c)
		theta = {}
		for c_var in c_vars:
			theta[c_var] = self.fresh()

		return self.substitute_in_clause(theta, c)

	'''
	Problem 4
	Following the Abstract interpreter pseudocode in the lecture note Control in Prolog to implement
	a nondeterministic Prolog interpreter.

	nondet_query (program, goal) where
		the first argument is a program which is a list of Rules.
		the second argument is a goal which is a list of Terms.

	The function returns a list of Terms (results), which is an instance of the original goal and is
	a logical consequence of the program. See the tests cases (in src/main.py) as examples.
	'''
	def nondet_query (self, program : List[Rule], pgoal : List[Term]) -> List[Term]:
		while(1):
			goal= pgoal[:]
			res = goal[:]
			while(res):
				x = random.randint(0, len(res)-1)
				y = res[x]
				unify = []
				for i in range(len(program)):
					NewY = self.freshen(program[i])
					try:
						self.unify(y,NewY.head)
						unify.append(NewY)
					except Not_unifiable:
						pass
				if unify == []:
					break
				k = random.randint(0, len(unify)-1)
				w = self.unify(y, unify[k].head)
				res.remove(res[x])
				for term in unify[k].body.terms:
					res.append(term)
				for term in range(len(goal)):
					new = self.substitute_in_term(w, goal[term])
					goal[term] = new
				for term in range(len(res)):
					new = self.substitute_in_term(w, res[term])
					res[term] = new
			if res == []:
				return goal

	'''
	Challenge Problem

	det_query (program, goal) where
		the first argument is a program which is a list of Rules.
		the second argument is a goal which is a list of Terms.

	The function returns a list of term lists (results). Each of these results is
	an instance of the original goal and is a logical consequence of the program.
	If the given goal is not a logical consequence of the program, then the result
	is an empty list. See the test cases (in src/main.py) as examples.
	'''
	def det_query (self, program : List[Rule], pgoal : List[Term]) -> List[List[Term]]:
		return [pgoal]