from django.db.models import Sum
from .models import Order, OrderDetail
from apps_base.influencer.models import Influencer
from apps_base.shipping.models import ShippingCost
from apps_base.promotion.models import CouponGenerate, Coupon
from apps_base.order.constants import PROCESO
from decimal import Decimal as D, getcontext

class OrderGenerate(object):

    def get_influencer_total(self, order, coupon, discount):
        order_details = order.order_orderdetail.all()
        influencer_ids = order_details.values_list(
            'productdetail__product_class__influencer__id', flat=True)
        influencers = Influencer.objects.filter(id__in=influencer_ids)
        shipping_influencer = {}
        if coupon:
            influencers_coupon = coupon.influencers.all()
            total_sum_influencer = order.order_orderdetail.filter(
                productdetail__product_class__influencer__in=influencers_coupon.values_list('id', flat=True)).aggregate(Sum('total'))
            total_sum = total_sum_influencer.get('total__sum')
        for influencer in influencers:
            # total_ifn = order.order_orderdetail.filter(
            #     productdetail__product_class__influencer__id=influencer.id).aggregate(Sum('total'))
            # total_ifn = total_ifn.get('total__sum')
            total_influencer = order_details.filter(
                productdetail__product_class__influencer__id=influencer.id)
            total_influencer = total_influencer.aggregate(Sum('total')).get('total__sum', 0)
            if coupon:
                influencer_coupon = influencers_coupon.filter(id=influencer.id)
                if influencer_coupon.exists():
                    percentage = float(total_influencer / total_sum)
                    percentage = round(percentage, 2)
                    total_discount = float(discount * percentage)
                    shipping_influencer[influencer.id] = {
                        'discount_global': discount,
                        'name': influencer.name,
                        'total_sum': float(total_sum),
                        'percentage': percentage * float(100),
                        'total_discount': total_discount,
                        'total_influencer': round(float(float(total_influencer) - float(number, ndigits)), 2),
                        'total': float(total_influencer)
                    }
                else:
                    shipping_influencer[influencer.id] = {
                        'discount_global': discount,
                        'name': influencer.name,
                        'total_sum': float(0),
                        'percentage': 0,
                        'total_discount': float(0),
                        'total_influencer': float(total_influencer),
                        'total': float(total_influencer)
                    }
            else:
                shipping_influencer[influencer.id] = {
                    'discount': discount,
                    'name': influencer.name,
                    'total_sum': float(0),
                    'percentage': 0,
                    'total': float(0),
                    'total_influencer': float(total_influencer),
                    'total': float(total_influencer)
                }
        return shipping_influencer

    def get_discount_infuencer_coupon(self, coupon, discount, order):
        order_details = order.order_orderdetail.all()
        influencers = coupon.influencers.all()
        total_sum_influencer = order.order_orderdetail.filter(
            productdetail__product_class__influencer__in=influencers.values_list('id', flat=True)).aggregate(Sum('total'))
        total_sum = total_sum_influencer.get('total__sum')
        total_discount = discount
        shipping_influencer = {}
        for influencer in influencers:
            total_influencer = order_details.filter(
                productdetail__product_class__influencer__id=influencer.id)
            if total_influencer.exists():
                total_influencer = total_influencer.aggregate(Sum('total')).get('total__sum', 0)
                percentage = float(total_influencer / total_sum)
                shipping_influencer[influencer.id] = {
                    'discount': discount,
                    'name': influencer.name,
                    'total_sum': float(total_sum),
                    'percentage': percentage * float(100),
                    'total': total_discount * percentage
                }
        return shipping_influencer
        #     pass
        # shipping_influencer
        # key : {'discount': 0, 'name': 'mox', 'percentage': 20}
    def create(self, cart, customer, ubigeo, coupon):

        # try:
        #     shipping = ShippingCost.objects.get(ubigeo=ubigeo)
        #     price = shipping.price
        # except Exception as e:
        #     price = 0
        try:
            coupon_generate = Coupon.objects.get(
                prefix=coupon.strip(),  is_active=True)
            print(coupon_generate, 'coupon_generate---')
            if coupon_generate.type_discount == 'PTJ':
                discount = (float(float(sub_total)*coupon_generate.discount) / 100)
            elif coupon_generate.type_discount == 'SLS':
                discount = coupon_generate.discount
        except Exception as e:
            coupon_generate = None
            discount = 0
        shipping = ShippingCost.objects.get(ubigeo=ubigeo)
        price = shipping.price
        total = cart.total + price - D(discount)
        order = Order.objects.create(
            customer=customer,
            cart=cart,
            sub_total=cart.total,
            total=total,
            discount=D(discount),
            shipping_price=price,
            type_status=PROCESO,
        )
        if coupon_generate:
            order.coupon_discount = coupon_generate
            order.save()
        self.create_details(cart, order)
        shipping_influencer = self.get_influencer_total(order, coupon_generate, discount)
        order.shipping_influencer = shipping_influencer
        order.save()
        print(coupon_generate, 'coupon_generate')
        # if coupon_generate:
        #     order.shipping_influencer = self.get_discount_infuencer_coupon(coupon_generate, discount, order)
        #     order.save()
        # raise
        return order

    def update(self, cart, ubigeo, coupon):
        getcontext().prec = 2
        try:
            coupon_generate = Coupon.objects.get(
                prefix=coupon.strip(), is_active=True)
            if coupon_generate.type_discount == 'PTJ':
                discount = (float(float(sub_total)*coupon_generate.discount) / 100)
            elif coupon_generate.type_discount == 'SLS':
                discount = coupon_generate.discount
        except Exception as e:
            coupon_generate = None
            discount = 0
        shipping = ShippingCost.objects.get(ubigeo=ubigeo)
        price = shipping.price
        total = D(cart.total + price) - D(discount)
        order = Order.objects.get(cart__code=cart.code)
        order.sub_total = cart.total
        order.discount = D(discount)
        order.total = total
        order.shipping_price = price
        order.type_status = PROCESO
        self.update_details(cart, order)
        shipping_influencer = self.get_influencer_total(order, coupon_generate, discount)
        order.shipping_influencer = shipping_influencer
        if coupon_generate:
            order.coupon = coupon_generate
            # shipping_influencer = self.get_discount_infuencer_coupon(coupon_generate, discount, order)
            # order.shipping_influencer = self.get_discount_infuencer_coupon(coupon_generate, discount, order)
            # order.save()
        order.save()
        return order

    def update_details(self, cart, order):
        for cart_item in cart.cart_items.all():
            try:
                order_detail = OrderDetail.objects.get(order=order, productdetail=cart_item.product)
                order_detail.quantity=cart_item.quantity
                order_detail.price=cart_item.product.price
                order_detail.sub_total=cart_item.cart_item_total
                order_detail.total=cart_item.cart_item_total
                order_detail.save()
            except Exception as e:
                order_detail = OrderDetail(
                    order=order,
                    productdetail=cart_item.product,
                    quantity=cart_item.quantity,
                    price=cart_item.product.price,
                    sub_total=cart_item.cart_item_total,
                    total=cart_item.cart_item_total,
                )
                order_detail.save()
        return None

    def create_details(self, cart, order):
        for cart_item in cart.cart_items.all():
            order_detail = OrderDetail(
                order=order,
                productdetail=cart_item.product,
                quantity=cart_item.quantity,
                price=cart_item.product.price,
                sub_total=cart_item.cart_item_total,
                total=cart_item.cart_item_total,
            )
            order_detail.save()
        return None


# -*- coding: utf-8 -*-
"""
Created on Tue Jul 26 19:04:23 2022

This code pulls static info from MIMIC IV for validation. Looks at the ADMISSIONS,
PATIENTS, CHARTEVENTS ,TRANSFERS, and services tables.

Covers:
Age
Gender
AdmitWeight
Ethnicity
HospitalAdmitTime24
UnitAdmitTime24
NumBeds
TeachingStatus
Unit Type
Trauma admisssion
time of day at time of prediction

TODO:
admission category (maybe later, needs to look at adm Dxs),
Can't find unit admit source anywhere.
UrgentAdmission - needs conversion

Run time: 15 min

@author: Kirby
"""
#%% package setup
import pandas as pd
import numpy as np
import time
import datetime
from pathlib import Path

start = time.time()

file_path = Path(__file__)
dataset_path = file_path.parent.parent.parent.joinpath('Dataset')
ext_valid_path = file_path.parent.parent.parent.joinpath('Ext Validation')
mimic_path = file_path.parent.parent.parent.parent.joinpath('mimic-iv-2.0')

#%% Finding relevant items.

items = pd.read_csv(mimic_path.joinpath('icu', 'd_items.csv.gz'))
items.loc[:, 'label'] = items['label'].str.lower()
items.loc[:, 'abbreviation'] = items['abbreviation'].str.lower()

# TODO: Add omr table for weights?
weight_items = items[items['label'].str.contains('weight')]
# Curated manually.
weight_items = [224639, 226512, 226531]
weight_items = items[items['itemid'].isin(weight_items)][['itemid']]

height_items = items[items['label'].str.contains('height')]
# 226707 is inches, 226730 is cm. Convert it all to cm.
height_items = height_items[['itemid']]

#%% Load in the info
comp = pd.read_csv(dataset_path.joinpath("MIMICIV_complete_dataset.csv"),
                   parse_dates=['intime'])
adm = pd.read_csv(mimic_path.joinpath('hosp',"admissions.csv.gz"),
                  usecols = ['subject_id', 'hadm_id', 'admittime',
                             'admission_type', 'race'],
                  parse_dates = ['admittime'])
pat = pd.read_csv(mimic_path.joinpath('hosp',"patients.csv.gz"),
                  usecols = ['subject_id', 'gender', 'anchor_age'])

tran = pd.read_csv(mimic_path.joinpath('hosp', "transfers.csv.gz"),
                  parse_dates=['intime','outtime'])

icu = pd.read_csv(mimic_path.joinpath('icu', "icustays.csv.gz"))

serv = pd.read_csv(mimic_path.joinpath('hosp', 'services.csv.gz'),
                   usecols=['hadm_id','curr_service'])

weight = pd.read_csv(mimic_path.joinpath('icu', "chartevents.csv.gz"),
                     nrows = 0)
height = pd.read_csv(mimic_path.joinpath('icu', "chartevents.csv.gz"),
                     nrows = 0)
for chunk in pd.read_csv(mimic_path.joinpath('icu', "chartevents.csv.gz"),
                         chunksize=1000000):
    temp_rows = chunk.merge(weight_items, how = 'inner', on = 'itemid')
    weight = pd.concat([weight,temp_rows])
    temp_rows = chunk.merge(height_items, how = 'inner', on = 'itemid')
    height = pd.concat([height,temp_rows])

#%% Drop stuff we don't care about.

#Drop irrelevant rows.
adm = adm[adm['hadm_id'].isin(comp['hadm_id'])]
pat = pat[pat['subject_id'].isin(comp['subject_id'])]
tran = tran[tran['hadm_id'].isin(comp['hadm_id'])]
icu = icu[icu['stay_id'].isin(comp['stay_id'])]
serv = serv[serv['hadm_id'].isin(comp['hadm_id'])]
weight = weight[weight['stay_id'].isin(comp['stay_id'])]
weight = weight[weight['warning'] == 0]
height = height[height['stay_id'].isin(comp['stay_id'])]
height = height[height['warning'] == 0]

#%% Get weight.
#Convert all weight to kg, mean if multiple values.
weight.loc[:, 'valuenum'] = weight['valuenum'].astype(float)
kg_weight = weight[weight['valueuom'] == 'kg']
lb_weight = weight[weight['valueuom'] != 'kg']

kg_weight = kg_weight[['stay_id', 'charttime', 'valuenum']]

lb_weight = lb_weight[['stay_id', 'charttime', 'valuenum']]
lb_weight.loc[:, 'valuenum'] = lb_weight['valuenum']/2.2

all_weight = pd.concat([kg_weight,lb_weight])
# Get rid of impossible or irrelevant weight measurements (less than 25 kg)
all_weight = all_weight[all_weight['valuenum'] >= 25]
# Take the first measurement per ICU stay as admission weight.
all_weight.sort_values(['stay_id', 'charttime'], inplace = True)
all_weight = all_weight.groupby('stay_id').first()
all_weight.reset_index(inplace=True)
all_weight.drop(columns = 'charttime', inplace = True)
all_weight.rename(columns = {'valuenum':'AdmissionWeight'}, inplace = True)

#%% Get height.
#Convert all height to cm, mean if multiple values.
height.loc[:, 'valuenum'] = height['valuenum'].astype(float)
cm_height = height[height['valueuom'] == 'cm']
in_height = height[height['valueuom'] != 'cm']

cm_height = height[['stay_id', 'charttime', 'valuenum']]

in_height = in_height[['stay_id', 'charttime', 'valuenum']]
in_height.loc[:, 'valuenum'] = in_height['valuenum']*2.54

all_height = pd.concat([cm_height,in_height])
# Get rid of impossible or irrelevant height measurements
all_height = all_height[all_height['valuenum'] >= 120]
all_height = all_height[all_height['valuenum'] <= 270]

# Take the first measurement per ICU stay as admission height.
all_height.sort_values(['stay_id', 'charttime'], inplace = True)
all_height = all_height.groupby('stay_id').first()
all_height.reset_index(inplace=True)
all_height.drop(columns = 'charttime', inplace = True)
all_height.rename(columns = {'valuenum':'AdmissionHeight'}, inplace = True)

#%% Get unit type. UnitType_MedSurg and UnitType_Neuro are two features in model.
unit_type = icu[['stay_id', 'first_careunit']].copy()
unit_type.loc[:, 'UnitType_MedSurg'] = unit_type['first_careunit'] == 'Medical/Surgical Intensive Care Unit (MICU/SICU)'
unit_type.loc[:, 'UnitType_Neuro'] = unit_type['first_careunit'] == 'Neuro Surgical Intensive Care Unit (Neuro SICU)'
for col_name in ['UnitType_MedSurg', 'UnitType_Neuro']:
    unit_type.loc[:, col_name] = unit_type[col_name].astype(int)

#%% Get if trauma or not.
serv.loc[:, 'Trauma'] = (serv['curr_service'] == 'TRAUM').astype(int).copy()
# Get if neurology/neurosurgery admission.
serv.loc[:, 'Neurology/Neurosurgery'] = ((serv['curr_service'] == 'EYE') |
                                         (serv['curr_service'] == 'NMED') |
                                         (serv['curr_service'] == 'NSURG')).astype(int).copy()

serv.drop(columns = 'curr_service', inplace = True)

#%% and unit stay type. Currently not in pruned feature list.
# #Find transfers.
# #Check if in each ICU stay

# #Find Readmits.
# #Check if there were subsequent stay_ids in teh same hadm_id

# #No SDU stays as far as I can tell.
# comp['UnitStayType_Stepdown'] = 0

#%% Merge stuff together.
comp = comp.merge(adm, how = 'left', on = ['subject_id', 'hadm_id'])
comp = comp.merge(serv, how = 'left', on = 'hadm_id')
comp = comp.merge(pat, how = 'left', on = 'subject_id')
comp = comp.merge(unit_type, how = 'left', on = 'stay_id')
comp = comp.merge(all_weight, how = 'left', on = 'stay_id')
comp = comp.merge(all_height, how = 'left', on = 'stay_id')

comp.rename(columns={'admission_type':'UrgentAdmission',
                     'admittime':'hospitalAdmitTime24',
                     'race':'Ethnicity',
                     'gender':'Gender',
                     'anchor_age': 'Age'
                     },inplace=True)

#%% Get urgent admissions.
comp['UrgentAdmission'] = (comp['UrgentAdmission'] == 'URGENT').astype(int)

#%% Tack on teaching status and numbeds column.
comp['TeachingStatus'] = 1
comp['NumBeds'] = 4 #>=500 beds.

#%% AdmitTime format
comp['hospitalAdmitTime24'] = comp['hospitalAdmitTime24'].dt.time
comp['UnitAdmitTime24'] = comp['intime'].dt.time

#%% Format ethnicity to match eICU.
def format_ethnic(ethnic):
    if ethnic.count('WHITE')>0:
        return 'Caucasian'
    elif ethnic.count('BLACK')>0:
        return 'African American'
    elif ethnic.count('ASIAN')>0:
        return 'Asian'
    elif ethnic.count('HISPANIC')>0:
        return 'Hispanic'
    elif ethnic.count('AMERICAN INDIAN')>0:
        return 'Native American'
    else:
        return 'Other/Unknown'

comp['Ethnicity'] = comp['Ethnicity'].apply(format_ethnic)

#%% Format gender.
def format_gender(gender):
    if gender == 'F':
        return 'Female'
    else:
        return 'Male'

comp['Gender'] = comp['Gender'].apply(format_gender)

#%%Get LOS at end of observation window.
comp['LOS'] = comp['end']

#%% Save off data.

for lead_hours in [0,1,3,6,12]:
    for obs_hours in [1,3,6,12]:
        comp.to_csv('MIMICIV_relative_'+ str(lead_hours) + 'hr_lead_' +
                    str(obs_hours) + '_static_features.csv',index=False)

calc_time = time.time() - start


import os
import sys
import yaml
import json
from copy import deepcopy
from importlib import import_module
from inspect import ismethod, isfunction

from .utils import singleton

__all__ = ('Context', 'BaseConfig', )

def _url2dict(arg):
    if arg.endswith('.yaml') or arg.endswith('.yml'):
        with open(arg) as f:
            raw_dict = yaml.load(f, Loader=yaml.FullLoader)
    elif arg.endswith('.py'):
        module_name = os.path.basename(arg)[:-3]
        config_dir = os.path.dirname(arg)
        sys.path.insert(0, config_dir)
        mod = import_module(module_name)
        sys.path.pop(0)
        raw_dict = {
            name: value
            for name, value in mod.__dict__.items()
            if not name.startswith('__')
        }
        sys.modules.pop(module_name)
    elif arg.endswith(".json"):
        with open(arg) as f:
            raw_dict = json.load(f)
    else:
        try:
            raw_dict = json.loads(arg, encoding="utf-8")
        except json.JSONDecodeError:
            raise Exception('config file must be yaml or py')
    return raw_dict

def _dict2config(config, dic):
    """Convert dictionary to config.

    :param Config config: config
    :param dict dic: dictionary

    """
    if isinstance(dic, dict):
        for key, value in dic.items():
            if isinstance(value, dict):
                config[key] = Config()
                _dict2config(config[key], value)
            else:
                config[key] = value

class Config(dict):
    """A Config class is inherit from dict.

    Config class can parse arguments from a config file
    of yaml, json or pyscript.
    :param args: tuple of Config initial arguments
    :type args: tuple of str or dict
    :param kwargs: dict of Config initial argumnets
    :type kwargs: dict
    """

    def __init__(self, *args, **kwargs):
        """Init config class with multiple config files or dictionary."""
        super(Config, self).__init__()
        for arg in args:
            if isinstance(arg, str):
                _dict2config(self, _url2dict(arg))
            elif isinstance(arg, dict):
                _dict2config(self, arg)
            else:
                raise TypeError('args is not dict or str')
        if kwargs:
            _dict2config(self, kwargs)

    def __call__(self, *args, **kwargs):
        """Call config class to return a new Config object.

        :return: a new Config object.
        :rtype: Config

        """
        return Config(self, *args, **kwargs)

    def __setstate__(self, state):
        """Set state is to restore state from the unpickled state values.

        :param dict state: the `state` type should be the output of
             `__getstate__`.

        """
        _dict2config(self, state)

    def __getstate__(self):
        """Return state values to be pickled.

        :return: change the Config to a dict.
        :rtype: dict

        """
        d = dict()
        for key, value in self.items():
            if isinstance(value, Config):
                value = value.__getstate__()
            d[key] = value
        return d

    def __getattr__(self, key):
        """Get a object attr by its `key`.

        :param str key: the name of object attr.
        :return: attr of object that name is `key`.
        :rtype: attr of object.

        """
        if key in self:
            return self[key]
        else:
            raise AttributeError(key)

    def __setattr__(self, key, value):
        """Get a object attr `key` with `value`.

        :param str key: the name of object attr.
        :param value: the `value` need to set to target object attr.
        :type value: attr of object.

        """
        self[key] = value

    def __delattr__(self, key):
        """Delete a object attr by its `key`.

        :param str key: the name of object attr.

        """
        del self[key]

    def __deepcopy__(self, memo):
        """After `deepcopy`, return a Config object.

        :param dict memo: same to deepcopy `memo` dict.
        :return: a deep copyed self Config object.
        :rtype: Config object

        """
        return Config(deepcopy(dict(self)))

class ConfigSerializable(object):
    """Seriablizable config base class."""

    __original__value__ = None

    @property
    def __allattr__(self):
        attrs = filter(
            lambda attr: not (
                attr.startswith("__") or ismethod(
                    getattr(
                        self,
                        attr)) or isfunction(
                    getattr(
                        self,
                        attr))),
            dir(self))
        return list(attrs)

    def update(self, **kwargs):
        for attr in self.__allattr__:
            if attr not in kwargs:
                continue
            setattr(self, attr, kwargs[attr])

    def to_json(self):
        """Serialize config to a dictionary."""

        attr_dict = {}
        for attr in self.__allattr__:
            value = getattr(self, attr)
            if isinstance(value, type) and isinstance(
                    value(), ConfigSerializable):
                value = value().to_json()
            elif isinstance(value, ConfigSerializable):
                value = value.to_json()
            attr_dict[attr] = value
        return Config(deepcopy(attr_dict))

    def dict(self):
        attr_dict = {}
        for attr in self.__allattr__:
            value = getattr(self, attr)
            if isinstance(value, type) and isinstance(
                    value(), ConfigSerializable):
                value = value().dict()
            elif isinstance(value, ConfigSerializable):
                value = value.dict()
            attr_dict[attr] = value
        return attr_dict

    def __getitem__(self, item):
        return getattr(self, item, None)

    def get(self, item, default=""):
        return self.__getitem__(item) or default

    @classmethod
    def from_json(cls, data):
        """Restore config from a dictionary or a file."""
        if not data:
            return cls
        if cls.__name__ == "ConfigSerializable":
            return cls
        config = Config(deepcopy(data))
        for attr in config:
            if not hasattr(cls, attr):
                setattr(cls, attr, config[attr])
                continue
            class_value = getattr(cls, attr)
            config_value = config[attr]
            if isinstance(class_value, ConfigSerializable) and hasattr(
                    config_value, 'from_json'):
                setattr(cls, attr, class_value.from_json(config_value))
            else:
                setattr(cls, attr, config_value)
        return cls

@singleton
class BaseConfig(ConfigSerializable):
    """The base config"""
    device_category = os.getenv('DEVICE_CATEGORY', 'CPU')  # device category
    # ML framework backend
    backend_type = os.getenv('BACKEND_TYPE', 'TENSORFLOW')
    # local control server
    lc_server = os.getenv("LC_SERVER", "http://127.0.0.1:9100")
    # dataset
    original_dataset_url = os.getenv("ORIGINAL_DATASET_URL")
    train_dataset_url = os.getenv("TRAIN_DATASET_URL")
    test_dataset_url = os.getenv("TEST_DATASET_URL")
    data_path_prefix = os.getenv("DATA_PATH_PREFIX", "/home/data")
    # k8s crd info
    namespace = os.getenv("NAMESPACE", "")
    worker_name = os.getenv("WORKER_NAME", "")
    # the name of JointInferenceService and others Service
    service_name = os.getenv("SERVICE_NAME", "")
    # the name of FederatedLearningJob and others Job
    job_name = os.getenv("JOB_NAME", "sedna")

    pretrained_model_url = os.getenv("PRETRAINED_MODEL_URL", "./")
    model_url = os.getenv("MODEL_URL")
    model_name = os.getenv("MODEL_NAME")
    log_level = os.getenv("LOG_LEVEL", "INFO")

    transmitter = os.getenv("TRANSMITTER", "ws")
    agg_data_path = os.getenv("AGG_DATA_PATH", "./")
    s3_endpoint_url = os.getenv("S3_ENDPOINT_URL", "")
    access_key_id = os.getenv("ACCESS_KEY_ID", "")
    secret_access_key = os.getenv("SECRET_ACCESS_KEY", "")

    # user parameter
    parameters = os.getenv("PARAMETERS")

    def __init__(self):
        if self.parameters:
            self.parameter = _url2dict(self.parameters)

class Context:
    """The Context provides the capability of obtaining the context"""
    parameters = os.environ

    @classmethod
    def get_parameters(cls, param, default=None):
        """get the value of the key `param` in `PARAMETERS`,
        if not exist, the default value is returned"""
        value = cls.parameters.get(
            param) or cls.parameters.get(str(param).upper())
        return value if value else default

    @classmethod
    def get_algorithm_from_api(cls, algorithm, **param) -> dict:
        """get the algorithm and parameter from api"""
        hard_example_name = cls.get_parameters(f'{algorithm}_NAME')
        hem_parameters = cls.get_parameters(f'{algorithm}_PARAMETERS')
        if not hard_example_name:
            return {}
        try:
            hem_parameters = json.loads(hem_parameters)
            hem_parameters = {
                p["key"]: p.get("value", "")
                for p in hem_parameters if "key" in p
            }
        except Exception:
            hem_parameters = {}

        hem_parameters.update(**param)

        hard_example_mining = {
            "method": hard_example_name,
            "param": hem_parameters
        }

        return hard_example_mining


import sys
import zlib
import random
import base64
from io import BytesIO

from PIL import Image

__version__ = '0.1.0'

class TermImage(object):
    def __init__(self, data: str):
        """Creates a new TermImage object using base64 encoded image data"""

        # The raw base64 encoded data
        self._raw = data

        # PIL image, allowing us to get information about the
        # raw data, necessary for displaying the image in
        # the terminal
        self._pil_img = Image.open(BytesIO(base64.b64decode(data)))

        # Give this image a random id so that it can be used in
        # multiple places without regenerating the image
        self.id = random.randint(0, 4294967295)

        # Set the format for this image. This can't be accessed after
        # resize apparently, so it needs to be done here.
        self.format = self._pil_img.format

    @classmethod
    def open(cls, file_path: str):
        """Open a local image as a TermImage instance"""
        data = open(file_path, "rb").read()
        encoded = base64.b64encode(data)
        return cls(encoded)

    @property
    def width(self):
        """Returns the width of this image"""
        return self._pil_img.width

    @property
    def height(self):
        """Returns the height of this image"""
        return self._pil_img.height

    def resize(self, width: int, height: int, resample: int = 1):
        """Wrapper around Image.resize, allowing this image to be resized.
        This is a destructive operation."""
        self._pil_img = self._pil_img.resize((width, height), resample)
        return self

    def base64(self):
        """Return this image as base64. This is needed because on resize
        the raw data will change."""
        buffered = BytesIO()
        self._pil_img.save(buffered, format=self.format)
        return base64.b64encode(buffered.getvalue())

    def render(self):
        cmd = {
            'a': 'T',
            # 'i': self.id,
            's': self.width,
            'v': self.height
        }

        if self.format == "PNG":
            cmd['f'] = 100
        else:
            cmd['f'] = 24

        self._write_chunked(cmd, self.base64())

    # def delete(self):
    #     cmd = {'a': 'd', 'i': self.id}
    #     self._write_gr_cmd(cmd)

    def _write_gr_cmd(self, cmd, payload=None):
        sys.stdout.buffer.write(self._serialize_gr_command(cmd, payload))
        sys.stdout.flush()

    def _serialize_gr_command(self, cmd, payload=None):
        cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
        ans = []
        w = ans.append
        w(b'\033_G'), w(cmd.encode('ascii'))
        if payload:
            w(b';')
            w(payload)
        w(b'\033\\')
        return b''.join(ans)

    def _write_chunked(self, cmd, data):
        if cmd['f'] != 100:
            data = zlib.compress(data)
            cmd['o'] = 'z'
        while data:
            chunk, data = data[:4096], data[4096:]
            m = 1 if data else 0
            cmd['m'] = m
            self._write_gr_cmd(cmd, chunk)
            cmd.clear()

img = TermImage.open("avatar.png")
img.resize(80, 80)
img.render()


from flask import Flask, request
import json
app = Flask(__name__)
import serial
import smtplib
import logging
import platform
import subprocess
import sys
import threading
from google.assistant.library.event import EventType
from aiy.assistant import auth_helpers
from aiy.assistant.library import Assistant
from aiy.board import Board, Led
from aiy.voice import tts
import time
SMTP_SERVER = 'smtp.gmail.com'
SMTP_PORT = 587
GMAIL_USERNAME = 'tsha813@gmail.com'
GMAIL_PASSWORD = '' #hidden for security ;)

class Emailer:
    def sendmail(self, recipient, subject, content):

        headers = ["From: " + GMAIL_USERNAME, "Subject: " + subject, "To: " + recipient,
                   "MIME-Version: 1.0", "Content-Type: text/html"]
        headers = "\r\n".join(headers)

        session = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
        session.ehlo()
        session.starttls()
        session.ehlo()

        session.login(GMAIL_USERNAME, GMAIL_PASSWORD)

        session.sendmail(GMAIL_USERNAME, recipient, headers + "\r\n\r\n" + content)
        #session.sendmail(GMAIL_USERNAME, recipient, headers.decode('utf-8') + "\r\n\r\n" + content.decode('utf-8'))
        session.quit

sender = Emailer()
sendTo = 'tsha813@gmail.com'
ser = serial.Serial('/dev/ttyACM0',9600,timeout=1)
ser.reset_input_buffer()
light = 0
lock = 0
garage = 0
temp = 0
actual_temp = 75
wanted_temp = 0
check_temp = 0
email = 0
light_bool = False
door_bool = False
@app.route('/receive', methods=['POST'])
def receive():
    data = json.loads(request.data.decode('utf-8'))
    global light
    global lock
    global garage
    global temp
    global actual_tmep
    global wanted_temp
    global check_temp
    global email
   # print(data['direction'])
    if data['direction'] == 'unlock1':
        lock = 1

    elif data['direction'] == 'lock1':
        lock = 0

    if data['direction'] == 'on1':
        light = 1

    elif data['direction'] == 'off1':
        light = 0

    if data['direction'] == 'unlock2':
        garage = 1

    elif data['direction'] == 'lock2':
        garage = 0

    if data['direction'] == 'tempu':
        temp = 1
        if check_temp == 0:
            wanted_temp = actual_temp
            check_temp = 1
        wanted_temp = wanted_temp + 1

    elif data['direction'] == 'tempd':
        temp = 0
        if check_temp == 0:
            wanted_temp = actual_temp
            check_temp = 1
        wanted_temp = wanted_temp - 1
    else:
        temp = 2

    if data['direction'] == 'email':
        email = 1

    ser.write((str(light)+ ' ' + str(lock) + ' ' + str(garage) + ' ' + str(temp) + ' ' + str(email) + '\n' ).encode('utf-8'))
    print(str(light)+ ' ' + str(lock) + ' ' + str(garage) + ' ' + str(temp) + ' ' + str(email) + '\n')

    if email == 1:
        #line = ser.readline().decode('utf-8')
        email_str = ""
        print("sending email...")
        if light_bool:
            email_str = email_str + "The light is on."
        else:
            email_str = email_str + "The Light is off."

        if door_bool:
            email_str = email_str + " The door is locked."
        else:
            email_str = email_str + " The door is unlocked."

        email_str = email_str + " The temperature in the house is " + str(actual_temp) + " degrees."
        line = ser.readline().decode('utf-8').rstrip()
        print(line)
        sender.sendmail(sendTo,"House devices status", email_str)
        email = 0
    return "Woop Woop this finally worked"

def light_on():
    global light
    light = 1
    ser.write((str(light)+ ' ' + str(lock) + ' ' + str(garage) + ' ' + str(temp) + ' ' + str(email) + '\n' ).encode('utf-8'))

def light_off():
    global light
    light = 0
    ser.write((str(light)+ ' ' + str(lock) + ' ' + str(garage) + ' ' + str(temp) + ' ' + str(email) + '\n' ).encode('utf-8'))

def lock_on():
    global lock
    lock = 1
    ser.write((str(light)+ ' ' + str(lock) + ' ' + str(garage) + ' ' + str(temp) + ' ' + str(email) + '\n' ).encode('utf-8'))

def lock_off():
    global lock
    lock = 0
    ser.write((str(light)+ ' ' + str(lock) + ' ' + str(garage) + ' ' + str(temp) + ' ' + str(email) + '\n' ).encode('utf-8'))

def garage_on():
    global garage
    garage = 1
    ser.write((str(light)+ ' ' + str(lock) + ' ' + str(garage) + ' ' + str(temp) + ' ' + str(email) + '\n' ).encode('utf-8'))

def garage_off():
    global garage
    garage = 0
    ser.write((str(light)+ ' ' + str(lock) + ' ' + str(garage) + ' ' + str(temp) + ' ' + str(email) + '\n' ).encode('utf-8'))

def email_send():
    global email
    email = 1
    ser.write((str(light)+ ' ' + str(lock) + ' ' + str(garage) + ' ' + str(temp) + ' ' + str(email) + '\n' ).encode('utf-8'))
    email_str = ""
    print("sending email...")
    if light_bool:
        email_str = email_str + "The light is on."
    else:
        email_str = email_str + "The Light is off."

    if door_bool:
        email_str = email_str + " The door is locked."
    else:
        email_str = email_str + " The door is unlocked."

    email_str = email_str + " The temperature in the house is " + str(actual_temp) + " degrees."
    line = ser.readline().decode('utf-8').rstrip()
    print(line)
    sender.sendmail(sendTo,"House devices status", email_str)
    email = 0

def temp_up():
	global temp
	temp = 1
	ser.write((str(light)+ ' ' + str(lock) + ' ' + str(garage) + ' ' + str(temp) + ' ' + str(email) + '\n' ).encode('utf-8'))
	temp = 2

def temp_down():
	global temp
	temp = 0
	ser.write((str(light)+ ' ' + str(lock) + ' ' + str(garage) + ' ' + str(temp) + ' ' + str(email) + '\n' ).encode('utf-8'))
	temp = 2

def process_event(assistant, led, event):
    logging.info(event)
    if event.type == EventType.ON_START_FINISHED:
        led.state = Led.BEACON_DARK  # Ready.
        print('Say "OK, Google" then speak, or press Ctrl+C to quit...')
    elif event.type == EventType.ON_CONVERSATION_TURN_STARTED:
        led.state = Led.ON  # Listening.
    elif event.type == EventType.ON_RECOGNIZING_SPEECH_FINISHED and event.args:
        print('You said:', event.args['text'])
        text = event.args['text'].lower()
        if text == 'turn on the light':
            assistant.stop_conversation()
            light_on()
        elif text == 'turn off the light':
            assistant.stop_conversation()
            light_off()
        elif text == 'unlock the door':
            assistant.stop_conversation()
            lock_on()
        elif text == 'lock the door':
            assistant.stop_conversation()
            lock_off()
        elif text == 'open the garage door':
            assistant.stop_conversation()
            garage_on()
        elif text == 'close the garage door':
            assistant.stop_conversation()
            garage_off()
        elif text == 'send current status':
            assistant.stop_conversation()
            email_send()
        elif text == 'turn up the temperature':
            assistant.stop_conversation()
            temp_up()
        elif text == 'turn down the temperature':
            assistant.stop_conversation()
            temp_down()

    elif event.type == EventType.ON_END_OF_UTTERANCE:
        led.state = Led.PULSE_QUICK  # Thinking.
    elif (event.type == EventType.ON_CONVERSATION_TURN_FINISHED
          or event.type == EventType.ON_CONVERSATION_TURN_TIMEOUT
          or event.type == EventType.ON_NO_RESPONSE):
        led.state = Led.BEACON_DARK  # Ready.
    elif event.type == EventType.ON_ASSISTANT_ERROR and event.args and event.args['is_fatal']:
        sys.exit(1)

def main():
    logging.basicConfig(level=logging.INFO)
    line = ser.readline().decode('utf-8').rstrip()
    print(line)
    credentials = auth_helpers.get_assistant_credentials()
    with Board() as board, Assistant(credentials) as assistant:
        for event in assistant.start():
            process_event(assistant, board.led, event)
def flask_run():
	app.run(host='192.168.1.201', port=5000)

def voice_run():
	main()
if __name__ == '__main__':
    flask_thread = threading.Thread(target=flask_run)
    voice_thread = threading.Thread(target = voice_run)
    flask_thread.start()
    voice_thread.start()
    flask_thread.join()
    voice_thread.join()


# coding=utf-8
# coding=utf-8
import requests
from bs4 import BeautifulSoup
import re
import pandas as pd
from lxml import etree

#生成页面url
def get_url1(pages):
    urls={}       #存贮链接的字典，key为页数，value为链接
    for page in range(1,int(pages)+1):
        page_url="https://sh.lianjia.com/chengjiao/pg{}/".format(page)
        #print(page_url)
        #获取详情页url,利用正则表达式匹配
        response1=requests.get(page_url)
        if response1.status_code==200:
            pattern=re.compile('

<a href="(.*?)"')   #编译正则表达式，()是为了提取匹配的字符串
            response1.encoding = 'gbk'
            url=re.findall(pattern,response1.text)      #匹配所有满足正则表达式的信息
            #print(len(url))   #每页有30条房屋信息
            urls[page]=url
    #print(urls)
    return urls

#获取详情页所需的字段，beautifulsoup
def get_field(urls,pages):
    info = {"ID":[],"区域":[],"房型": [],"建筑年代":[], "面积": [],"房屋朝向":[],"装修情况":[],"总价（万）": [],"单价(元/平米)":[],
            "成交周期（天）":[],"成交日期":[],"带看次数":[]}
    for page in range(1,int(pages)+1):
        for i in urls[page]:
            response2=requests.get(i)
            if response2.status_code == 200:
                # 获取所需字段信息,beautifulsoup可以获取的
                soup=BeautifulSoup(response2.text ,'lxml')
                info["总价（万）"].append(soup.select('div.info.fr > div.price > span > i')[0].text )  #通过采用soup.select()方法，可以得到所需的内容;.表示类;运用方法 .text得到文本
                info["成交周期（天）"].append(soup.select('div.msg > span:nth-of-type(2) > label')[0].text)
                info["带看次数"].append(soup.select('div.info.fr > div.msg > span:nth-of-type(4) > label')[0].text.strip())
                info["单价(元/平米)"].append(soup.select(' div.info.fr > div.price > b')[0].text)
                info["成交日期"].append(soup.select(' div.house-title > div > span')[0].text.replace('成交',''))
                #获取所需字段信息,xpath提取
                d=etree.HTML(response2.text)
                info["房型"].append(d.xpath('// *[ @ id = "introduction"] / div[1] / div[1] / div[2] / ul / li[1] / text()')[0].strip())
                info["面积"].append(d.xpath('//*[@id="introduction"]/div[1]/div[1]/div[2]/ul/li[3]/text()')[0].strip())
                info["建筑年代"].append(d.xpath('//*[@id="introduction"]/div[1]/div[1]/div[2]/ul/li[8]/text()')[0])
                info["房屋朝向"].append(d.xpath('//*[@id="introduction"]/div[1]/div[1]/div[2]/ul/li[7]/text()')[0].strip())
                info["装修情况"].append(d.xpath('// *[ @ id = "introduction"] / div[1] / div[1] / div[2] / ul / li[9] / text()')[0].strip())
                info["ID"].append(d.xpath('//*[@id="introduction"]/div[1]/div[2]/div[2]/ul/li[1]/text()')[0].strip())
                info["区域"].append(d.xpath('/html/body/section[2]/div[2]/div/div/div[1]/a[1]/text()')[0])

    #将房屋信息读取到dataframe
    df=pd.DataFrame(info)
    pd.set_option('display.max_columns', None)          #显示所有列
    print(df)
    return df
#将数据导出保存为csv
def output_csv(df):
    df.to_csv('lianjia_ershoufang.csv', sep=',', index=False, header=False)

if __name__ == "__main__":
    pages=input("please input pages:")
    urls=get_url1(pages)
    df=get_field(urls, pages)
    output_csv(df)


#coding: 'utf-8'

"""
LDP_Net
demo.py

created by Kazunari on 2018/08/29
"""

import argparse
import matplotlib
import matplotlib.pyplot as plt
import numpy as np

import chainer

import sys
import os.path as osp
sys.path.append(osp.curdir)
from model.ldp_net import LDP_Net
from dataset.Local_Depth_Dataset import LocalDepthDataset
from dataset.LDD_Transform import LDDTransform

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--gpu', '-g', type=int, default=-1)
    parser.add_argument('--pretrained_model', '-m')
    parser.add_argument('--dataset_path', '-p', default="/Users/Kazunari/projects/datasets/LocalDepthDataset")
    parser.add_argument('--out', '-o', default=None)

    args = parser.parse_args()

    ldd = LocalDepthDataset(args.dataset_path)
    dataset = chainer.datasets.TransformDataset(ldd, LDDTransform(ldd))

    model = LDP_Net(rgbd_channel=3, pretrained_model=args.pretrained_model)

    if args.gpu >= 0:
        chainer.cuda.get_device_from_id(args.gpu).use()
        model.to_gpu()

    errors = []

    for i in range(1, 20000, 50):
        x_1, x_2, t, mask = dataset.get_example(i)

        x_1 = np.expand_dims(x_1, axis=0)[:, :3, :, :]
        x_2 = np.expand_dims(x_2, axis=0)

        pred = model(x_1, x_2)

        np_pred = np.asarray(pred.data[0], dtype=np.float32)

        elems_error = np.abs(np.diff([np_pred, t], axis=0))
        valid_pixel = np.count_nonzero(mask)
        masked_error = np.where(mask, elems_error, np.zeros_like(elems_error, dtype=np.float32))
        ldp_error = np.sum(masked_error) / valid_pixel

        elems_error = np.abs(np.diff([x_1[:, 3, :, :] / 10, t], axis=0))
        masked_error = np.where(mask, elems_error, np.zeros_like(elems_error, dtype=np.float32))
        eigen_error = np.sum(masked_error) / valid_pixel

        print("ldp_n error: " + str(ldp_error))
        print("eigen error: " + str(eigen_error))

if __name__ == '__main__':
    main()


from itertools import combinations
from collections import Counter

def solution(orders, course):
    answer = []
    for c in course:
        lst = []
        for order in orders:
            for l in list(combinations(sorted(order), c)):
                lst.append(''.join(l))
        result = Counter(lst).most_common()
        if result and result[0][1] > 1:
            maximum = result[0][1]
            for r in result:
                if r[1] == maximum:
                    answer.append(r[0])
    answer.sort()
    return answer

프로그래머스/level2/메뉴리뉴얼.py


#!/usr/bin/python
###############################################################################
#                                                                             #
# This file is part of IfcOpenShell.                                          #
#                                                                             #
# IfcOpenShell is free software: you can redistribute it and/or modify        #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or         #
# (at your option) any later version.                                         #
#                                                                             #
# IfcOpenShell is distributed in the hope that it will be useful,             #
# but WITHOUT ANY WARRANTY; without even the implied warranty of              #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                #
# Lesser GNU General Public License for more details.                         #
#                                                                             #
# You should have received a copy of the Lesser GNU General Public License    #
# along with this program. If not, see <http://www.gnu.org/licenses/>.        #
#                                                                             #
###############################################################################

###############################################################################
#                                                                             #
# This script builds IfcOpenShell and its dependencies                        #
#                                                                             #
# Prerequisites for this script to function correctly:                        #
#     * cmake * git * bzip2 * tar * c(++) compilers * autoconf                #
#                                                                             #
#   if building with USE_OCCT additionally:                                   #
#     * glx.h                                                                 #
#                                                                             #
#   if building with OCCT 7.4.0 additionally:                                 #
#     * libfontconfig1-dev                                                    #
#                                                                             #
#   if building with -shared                                                  #
#     * libgl1-mesa-dev libxext-dev libxmu-dev libxmu-headers libxi-dev       #
#                                                                             #
#   for python37 to install correctly additionally:                           #
#     * libffi(-dev[el])                                                      #
#                                                                             #
#     on debian 7.8 these can be obtained with:                               #
#          $ apt-get install git gcc g++ autoconf bison bzip2 cmake           #
#            mesa-common-dev libffi-dev libfontconfig1-dev                    #
#                                                                             #
#     on ubuntu 14.04:                                                        #
#          $ apt-get install git gcc g++ autoconf bison make cmake            #
#            mesa-common-dev libffi-dev libfontconfig1-dev                    #
#                                                                             #
#     on OS X El Capitan with homebrew:                                       #
#          $ brew install git bison autoconf automake libffi cmake            #
#                                                                             #
###############################################################################
import logging
import os
import sys
import subprocess as sp
import shutil
import tarfile
import multiprocessing
import platform
import sysconfig

# @todo temporary for expired mpfr.org certificate on 2023-04-08
import ssl
ssl._create_default_https_context = ssl._create_unverified_context

from urllib.request import urlretrieve

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
logger.addHandler(ch)

PROJECT_NAME = "IfcOpenShell"
USE_CURRENT_PYTHON_VERSION = os.getenv("USE_CURRENT_PYTHON_VERSION")
ADD_COMMIT_SHA = os.getenv("ADD_COMMIT_SHA")

PYTHON_VERSIONS = ["3.6.14", "3.7.13", "3.8.13", "3.9.11", "3.10.3", "3.11.0"]
JSON_VERSION = "v3.6.1"
OCE_VERSION = "0.18.3"
OCCT_VERSION = "7.5.3"
BOOST_VERSION = "1.80.0"
PCRE_VERSION = "8.41"
LIBXML2_VERSION = "2.9.11"
SWIG_VERSION = "4.0.2"
OPENCOLLADA_VERSION = "v1.6.68"
HDF5_VERSION = "1.12.1"

GMP_VERSION = "6.2.1"
MPFR_VERSION = "3.1.6" # latest is 4.1.0
CGAL_VERSION = "5.3"

# binaries
cp = "cp"
bash = "bash"
git = "git"
bunzip2 = "bunzip2"
tar = "tar"
cc = "cc"
cplusplus = "c++"
autoconf = "autoconf"
automake = "automake"
make = "make"
date = "date"
curl = "curl"
wget = "wget"
strip = "strip"

explicit_targets = [s for s in sys.argv[1:] if not s.startswith("-")]
flags = set(s.lstrip('-') for s in sys.argv[1:] if s.startswith("-"))

# Helper function for coloured printing

NO_COLOR = "\033[0m" # <ref>http://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux</ref>
BLACK_ON_WHITE = "\033[0;30;107m"
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
MAGENTA = "\033[35m"

def cecho(message, color=NO_COLOR):
    """Logs message `message` in color `color`."""
    logger.info(f"{color}{message}\033[0m")

def which(cmd):
    for path in os.getenv("PATH").split(":"):
        if os.path.exists(path) and cmd in os.listdir(path):
            return cmd
    return None

# Set defaults for missing empty environment variables

USE_OCCT = os.environ.get("USE_OCCT", "true").lower() == "true"

TOOLSET = None
if platform.system() == "Darwin":
    # C++11 features used in OCCT 7+ need a more recent stdlib
    TOOLSET = "10.9" if USE_OCCT else "10.6"

IFCOS_NUM_BUILD_PROCS = os.getenv("IFCOS_NUM_BUILD_PROCS", multiprocessing.cpu_count() + 1)

CMAKE_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "cmake"))

build_dir = os.environ.get("BUILD_DIR", os.path.join(os.path.dirname(__file__), "..", "build"))

path = [build_dir, platform.system(), "wasm" if "wasm" in flags else platform.machine()]
if TOOLSET:
    path.append(TOOLSET)
DEFAULT_DEPS_DIR = os.path.realpath(os.path.join(*path))

DEPS_DIR = os.getenv("DEPS_DIR", DEFAULT_DEPS_DIR)

if not os.path.exists(DEPS_DIR):
    os.makedirs(DEPS_DIR)

BUILD_CFG = os.getenv("BUILD_CFG", "RelWithDebInfo")

# Print build configuration information

cecho (f"""This script fetches and builds {PROJECT_NAME} and its dependencies
""", BLACK_ON_WHITE)
cecho("""Script configuration:

""", GREEN)
cecho(f"""* USE_OCCT               = {USE_OCCT}""", MAGENTA)
if USE_OCCT:
    cecho(" - Compiling against official Open Cascade")
else:
    cecho(" - Compiling against Open Cascade Community Edition")
cecho(f"* Dependency Directory   = {DEPS_DIR}", MAGENTA)
cecho(f" - The directory where {PROJECT_NAME} dependencies are installed.")
cecho(f"* Build Config Type      = {BUILD_CFG}", MAGENTA)
cecho(""" - The used build configuration type for the dependencies.
   Defaults to RelWithDebInfo if not specified.""")

if BUILD_CFG == "MinSizeRel":
    cecho("     WARNING: MinSizeRel build can suffer from a significant performance loss.", RED)

cecho(f"* IFCOS_NUM_BUILD_PROCS  = {IFCOS_NUM_BUILD_PROCS}", MAGENTA)
cecho(""" - How many compiler processes may be run in parallel.
""")

dependency_tree = {
    'IfcParse': ('boost', 'libxml2', 'hdf5'),
    'IfcGeom': ('IfcParse', 'occ', 'json', 'cgal'),
    'IfcConvert': ('IfcGeom',),
    'OpenCOLLADA': ('libxml2', 'pcre'),
    'IfcGeomServer': ('IfcGeom',),
    'IfcOpenShell-Python': ('python', 'swig', 'IfcGeom'),
    'swig': ('pcre',),
    'boost': (),
    'libxml2': (),
    'python': (),
    'occ': ('freetype',),
    'pcre': (),
    'json': (),
    'hdf5': (),
    'cgal': (),
    'freetype': (),
}

def v(dep):
   yield dep
   for d in dependency_tree[dep]:
     for x in v(d):
       yield x

if "v" in flags:
    logger.setLevel(logging.DEBUG)
else:
    logger.setLevel(logging.INFO)

BUILD_STATIC = "shared" not in flags
ENABLE_FLAG = "--enable-static" if BUILD_STATIC else "--enable-shared"
DISABLE_FLAG = "--disable-shared" if BUILD_STATIC else "--disable-static"
LINK_TYPE = "static" if BUILD_STATIC else "shared"
LINK_TYPE_UCFIRST = LINK_TYPE[0].upper() + LINK_TYPE[1:]
LIBRARY_EXT = "a" if BUILD_STATIC else "so"
PIC = "-fPIC" if BUILD_STATIC else ""

if any(f.startswith("py-") for f in flags):
    PYTHON_VERSIONS = [pyv for pyv in PYTHON_VERSIONS if "py-%s" % "".join(pyv.split('.')[0:2]) in flags]

if len(explicit_targets):
    targets = set(sum((list(v(target)) for target in explicit_targets), []))
else:
    targets = set(dependency_tree.keys())

targets = set(t for t in targets if 'without-%s' % t.lower() not in flags)

print("Building:", *sorted(targets, key=lambda t: len(list(v(t)))))

# Check that required tools are in PATH

for cmd in [git, bunzip2, tar, cc, cplusplus, autoconf, automake, make, "patch", "cmake"]:
    if which(cmd) is None:
        raise ValueError(f"Required tool '{cmd}' not installed or not added to PATH")

# identifiers for the download tool (could be less memory consuming as ints, but are more verbose as strings)
download_tool_default = download_tool_py = "py"
download_tool_git = "git"

# Create log directory and file

log_dir = os.path.join(DEPS_DIR, "logs")
if not os.path.exists(log_dir):
    os.makedirs(log_dir)
LOG_FILE = os.path.join(log_dir, sp.check_output([date, "+%Y%m%d"], encoding="utf-8").strip()) + ".log"
if not os.path.exists(LOG_FILE):
    open(LOG_FILE, "w").close()
logger.info(f"using command log file '{LOG_FILE}'")

def run(cmds, cwd=None, can_fail=False):

    """
    Wraps `subprocess.Popen.communicate()` and logs the command being executed,
    sets up logging `stderr` to `LOG_FILE` (in append mode) and returns stdout
    with leading and trailing whitespace removed.
    """

    logger.debug(f"running command {' '.join(cmds)} in directory {cwd}")
    log_file_handle = open(LOG_FILE, "a")
    proc = sp.Popen(cmds, cwd=cwd, stdout=sp.PIPE, stderr=sp.PIPE, encoding="utf-8")
    stdout, stderr = proc.communicate()
    log_file_handle.write(stdout)
    log_file_handle.write(stderr)
    log_file_handle.close()
    logger.debug(f"command returned {proc.returncode}")

    if proc.returncode != 0 and not can_fail:
        print("-" * 70)
        print(stderr)
        print("-" * 70)
        raise RuntimeError(f"Command `{' '.join(cmds)}` returned exit code {proc.returncode}")

    return stdout.strip()

if platform.system() == "Darwin":
    if run(["sw_vers", "-productVersion"]) >= "11.":
        # Apparently not supported
        PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) >= (3, 7)]
    if run(["sw_vers", "-productVersion"]) < "10.16":
        # Apparently not supported
        PYTHON_VERSIONS = [pv for pv in PYTHON_VERSIONS if tuple(map(int, pv.split("."))) < (3, 11)]

BOOST_VERSION_UNDERSCORE = BOOST_VERSION.replace(".", "_")

OCE_LOCATION = f"https://github.com/tpaviot/oce/archive/OCE-{OCE_VERSION}.tar.gz"
BOOST_LOCATION = f"https://boostorg.jfrog.io/artifactory/main/release/{BOOST_VERSION}/source/"

# Helper functions

def run_autoconf(arg1, configure_args, cwd):
    configure_path = os.path.realpath(os.path.join(cwd, "..", "configure"))
    if not os.path.exists(configure_path):
        run([bash, "./autogen.sh"], cwd=os.path.realpath(os.path.join(cwd, ".."))) # only run autogen.sh in the directory it is located and use cwd to achieve that in order to not mess up things
    # Using `sh` over `bash` fixes issues with building swig
    prefix = os.path.realpath(f"{DEPS_DIR}/install/{arg1}")

    wasm = []
    if "wasm" in flags:
        wasm.append("emconfigure")

    run([*wasm, "/bin/sh", "../configure"] + configure_args + [f"--prefix={prefix}"], cwd=cwd)

def run_cmake(arg1, cmake_args, cmake_dir=None, cwd=None):
    if cmake_dir is None:
        P = ".."
    else:
        P = cmake_dir

    wasm = []
    if "wasm" in flags:
        wasm.append("emcmake")

    run([*wasm, "cmake", P, *cmake_args, f"-DCMAKE_BUILD_TYPE={BUILD_CFG}"], cwd=cwd)

def git_clone_or_pull_repository(clone_url, target_dir, revision=None):
    """Lazily clones the `git` repository denoted by `clone_url` into
    the `target_dir` or pulls latest changes if the `target_dir` exists (naively assumes
    that a working clone exists there) and optionally checks out a revision
    `revision` after cloning or in the existing clone if `revision` is not
    `None`."""
    if not os.path.exists(target_dir):
        logger.info(f"cloning '{clone_url}' into '{target_dir}'")
        run([git, "clone", "--recursive", clone_url, target_dir])
    else:
        logger.info(f"directory '{target_dir}' already cloned. Pulling latest changes.")

    # detect whether we are on a branch and pull
    if run([git, "rev-parse", "--abbrev-ref", "HEAD"], cwd=target_dir) != "HEAD":
        run([git, "pull", clone_url], cwd=target_dir)

    if revision != None:
        run([git, "checkout", revision], cwd=target_dir)

def build_dependency(name, mode, build_tool_args, download_url, download_name, download_tool=download_tool_default, revision=None, patch=None, additional_files={}, no_append_name=False, **kwargs):
    """Handles building of dependencies with different tools (which are
    distinguished with the `mode` argument. `build_tool_args` is expected to be
    a list which is necessary in order to not mess up quoting of compiler and
    linker flags."""
    check_dir = os.path.join(DEPS_DIR, "install", name)
    if os.path.exists(check_dir):
        logger.info(f"Found existing {name}, skipping")
        return
    build_dir = os.path.join(DEPS_DIR, "build")
    if not os.path.exists(build_dir):
        os.makedirs(build_dir)

    logger.info(f"\rFetching {name}...   ")

    if download_tool == download_tool_py:
        if no_append_name:
            url = download_url
        else:
            url = os.path.join(download_url, download_name)

        download_path = os.path.join(build_dir, download_name)
        if not os.path.exists(download_path):
            urlretrieve(url, os.path.join(build_dir, download_path))
        else:
            logger.info(f"Download '{download_path}' already exists, assuming it's an undamaged download and that it has been extracted if possible, skipping")
    elif download_tool == download_tool_git:
        logger.info(f"\rChecking {name}...   ")
        git_clone_or_pull_repository(download_url, target_dir=os.path.join(build_dir, download_name), revision=revision)
    else:
        raise ValueError(f"download tool '{download_tool}' is not supported")
    download_dir = os.path.join(build_dir, download_name)

    if os.path.isdir(download_dir):
        extract_dir_name = download_name
        extract_dir = os.path.join(build_dir, extract_dir_name)
    else:
        download_tarfile_path = os.path.join(build_dir, download_name)
        if download_name.endswith(".tar.gz") or download_name.endswith(".tgz"):
            compr = "gz"
        elif download_name.endswith(".tar.bz2"):
            compr = "bz2"
        else:
            raise RuntimeError("fix source for new download type")
        download_tarfile = tarfile.open(name=download_tarfile_path, mode=f"r:{compr}")
        # tarfile seriously doesn't have a function to retrieve the root directory more easily
        extract_dir_name = os.path.commonprefix([x for x in download_tarfile.getnames() if x != "."])
        #run([tar, "--exclude=\"*/*\"", "-tf", download_name], cwd=build_dir).strip() no longer works
        if extract_dir_name is None:
            extract_dir_name = run([bash, "-c", f"tar -tf {download_name} 2> /dev/null | head -n 1 | cut -f1 -d /"], cwd=build_dir)
        extract_dir = os.path.join(build_dir, extract_dir_name)
        if not os.path.exists(extract_dir):
            run([tar, "-xf", download_name], cwd=build_dir)

    for path, url in additional_files.items():
        if not os.path.exists(path):
            urlretrieve(url, os.path.join(extract_dir, path))

    if patch is not None:
        if isinstance(patch, str):
            patch = [patch]
        for p in patch:
            patch_abs = os.path.abspath(os.path.join(os.path.dirname(__file__), p))
            if os.path.exists(patch_abs):
                try: run(["patch", "-p1", "--batch", "--forward", "-i", patch_abs], cwd=extract_dir)
                except Exception as e:
                    # Assert that the patch has already been applied
                    run(["patch", "-p1", "--batch", "--reverse", "--dry-run", "-i", patch_abs], cwd=extract_dir)

    if mode == "ctest":
        run(["ctest", "-S", "HDF5config.cmake,BUILD_GENERATOR=Unix", "-C", BUILD_CFG, "-V", "-O", "hdf5.log"], cwd=extract_dir)
        run([tar, "-xf", kwargs["ctest_result"] + ".tar.gz"], cwd=os.path.join(extract_dir, 'build'))
        shutil.copytree(
            os.path.join(extract_dir, "build", kwargs["ctest_result"], kwargs["ctest_result_path"]),
            os.path.join(DEPS_DIR, "install", name)
        )
    elif mode != "bjam":
        extract_build_dir = os.path.join(extract_dir, "build")
        if os.path.exists(extract_build_dir):
            shutil.rmtree(extract_build_dir)
        os.makedirs(extract_build_dir)

        logger.info(f"\rConfiguring {name}...")
        if mode == "autoconf":
            run_autoconf(name, build_tool_args, cwd=extract_build_dir)
        elif mode == "cmake":
            run_cmake(name, build_tool_args, cwd=extract_build_dir)
        else:
            raise ValueError()
        logger.info(f"\rBuilding {name}...   ")
        run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "VERBOSE=1"], cwd=extract_build_dir)
        logger.info(f"\rInstalling {name}... ")
        run([make, "install"], cwd=extract_build_dir)
        logger.info(f"\rInstalled {name}     \n")
    else:
        logger.info(f"\rConfiguring {name}...")
        run([bash, "./bootstrap.sh"], cwd=extract_dir)
        logger.info(f"\rBuilding {name}...   ")
        run(["./b2", f"-j{IFCOS_NUM_BUILD_PROCS}"] + build_tool_args, cwd=extract_dir, can_fail="wasm" in flags)
        logger.info(f"\rInstalling {name}... ")
        shutil.copytree(os.path.join(extract_dir, "boost"), os.path.join(DEPS_DIR, "install", f"boost-{BOOST_VERSION}", "boost"))
        logger.info(f"\rInstalled {name}     \n")

cecho("Collecting dependencies:", GREEN)

# Set compiler flags for 32bit builds on 64bit system
# TODO: This is untested

ADDITIONAL_ARGS = []

if platform.system() == "Darwin":
    ADDITIONAL_ARGS = [f"-mmacosx-version-min={TOOLSET}"] + ADDITIONAL_ARGS

if "wasm" in flags:
    ADDITIONAL_ARGS.extend(("-sWASM_BIGINT", "-fexceptions"))

# If the linker supports GC sections, set it up to reduce binary file size
# -fPIC is required for the shared libraries to work

CXXFLAGS = os.environ.get("CXXFLAGS", "")
CFLAGS = os.environ.get("CFLAGS", "")
LDFLAGS = os.environ.get("LDFLAGS", "")

ADDITIONAL_ARGS_STR = " ".join(ADDITIONAL_ARGS)
if "wasm" not in flags and sp.call([bash, "-c", "ld --gc-sections 2>&1 | grep -- --gc-sections &> /dev/null"]) != 0:
    CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
    CFLAGS_MINIMAL = f"{CFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
    if BUILD_STATIC:
        CXXFLAGS = f"{CXXFLAGS} {PIC} -fdata-sections -ffunction-sections -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
        CFLAGS = f"{CFLAGS}   {PIC} -fdata-sections -ffunction-sections -fvisibility=hidden {ADDITIONAL_ARGS_STR}"
    else:
        CXXFLAGS = CXXFLAGS_MINIMAL
        CFLAGS = CFLAGS_MINIMAL
    LDFLAGS = f"{LDFLAGS}  -Wl,--gc-sections {ADDITIONAL_ARGS_STR}"
else:
    CXXFLAGS_MINIMAL = f"{CXXFLAGS} {PIC} {ADDITIONAL_ARGS_STR}"
    CFLAGS_MINIMAL = f"{CFLAGS}   {PIC} {ADDITIONAL_ARGS_STR}"
    if BUILD_STATIC:
        CXXFLAGS = f"{CXXFLAGS} {PIC} -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
        CFLAGS = f"{CFLAGS}   {PIC} -fvisibility=hidden -fvisibility-inlines-hidden {ADDITIONAL_ARGS_STR}"
    else:
        CXXFLAGS=CXXFLAGS_MINIMAL
        CFLAGS=CFLAGS_MINIMAL
    LDFLAGS = f"{LDFLAGS} {ADDITIONAL_ARGS_STR}"

os.environ["CXXFLAGS"] = CXXFLAGS
os.environ["CFLAGS"] = CFLAGS
os.environ["LDFLAGS"] = LDFLAGS

# Some dependencies need a more recent CMake version than most distros provide
# @tfk: this is no longer needed
# build_dependency(name="cmake-%s" % (CMAKE_VERSION,), mode="autoconf", build_tool_args=[], download_url="https://cmake.org/files/v%s" % (CMAKE_VERSION_2,), download_name="cmake-%s.tar.gz" % (CMAKE_VERSION,))

if 'hdf5' in targets:
    HDF5_MAJOR = ".".join(HDF5_VERSION.split(".")[:-1])
    build_dependency(
        name=f"hdf5-{HDF5_VERSION}",
        mode="ctest",
        build_tool_args=[],
        download_url=f"https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-{HDF5_MAJOR}/hdf5-{HDF5_VERSION}/src/",
        download_name=f"CMake-hdf5-{HDF5_VERSION}.tar.gz",
        ctest_result=f"HDF5-{HDF5_VERSION}-{platform.system()}",
        ctest_result_path=f"HDF_Group/HDF5/{HDF5_VERSION}"
    )

if "json" in targets:
    json_url = f"https://github.com/nlohmann/json/releases/download/{JSON_VERSION}/json.hpp"
    json_install_path = f"{DEPS_DIR}/install/json/nlohmann/json.hpp"
    if not os.path.exists(os.path.dirname(json_install_path)):
        os.makedirs(os.path.dirname(json_install_path))
    if not os.path.exists(json_install_path):
        urlretrieve(json_url, json_install_path)

if "pcre" in targets:
    build_dependency(
        name=f"pcre-{PCRE_VERSION}",
        mode="autoconf",
        build_tool_args=[DISABLE_FLAG],
        download_url=f"https://downloads.sourceforge.net/project/pcre/pcre/{PCRE_VERSION}/",
        download_name=f"pcre-{PCRE_VERSION}.tar.bz2"
    )

# An issue exists with swig-1.3 and python >= 3.2
# Therefore, build a recent copy from source
if "swig" in targets:
    build_dependency(
        name="swig",
        mode="autoconf",
        build_tool_args=["--disable-ccache", f"--with-pcre-prefix={DEPS_DIR}/install/pcre-{PCRE_VERSION}"],
        download_url="https://github.com/swig/swig.git",
        download_name="swig",
        download_tool=download_tool_git,
        revision=f"rel-{SWIG_VERSION}"
    )

if "freetype" in targets:
    build_dependency(
        name=f"freetype",
        mode="cmake",
        build_tool_args=[
            f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/freetype"
        ],
        download_url = "https://github.com/freetype/freetype",
        download_name = "freetype2",
        download_tool=download_tool_git,
    )

if USE_OCCT and "occ" in targets:
    patches = []
    if OCCT_VERSION < "7.4":
        patches.append("./patches/occt/enable-exception-handling.patch")

    if "wasm" in flags:
        patches.append("./patches/occt/no_em_js.patch")

    build_dependency(
        name=f"occt-{OCCT_VERSION}",
        mode="cmake",
        build_tool_args=[
            f"-DINSTALL_DIR={DEPS_DIR}/install/occt-{OCCT_VERSION}",
            f"-DBUILD_LIBRARY_TYPE={LINK_TYPE_UCFIRST}",
            "-DBUILD_MODULE_Draw=0",
            "-DBUILD_RELEASE_DISABLE_EXCEPTIONS=Off",
            f"-D3RDPARTY_FREETYPE_DIR={DEPS_DIR}/install/freetype"
        ],
        download_url = "https://github.com/Open-Cascade-SAS/OCCT",
        download_name = "occt",
        download_tool=download_tool_git,
        patch=patches,
        revision="V" + OCCT_VERSION.replace('.', '_')
    )
elif "occ" in targets:
    build_dependency(
        name=f"oce-{OCE_VERSION}",
        mode="cmake",
        build_tool_args=[
            "-DOCE_DISABLE_TKSERVICE_FONT=ON",
            "-DOCE_TESTING=OFF",
            "-DOCE_BUILD_SHARED_LIB=OFF",
            "-DOCE_DISABLE_X11=ON",
            "-DOCE_VISUALISATION=OFF",
            "-DOCE_OCAF=OFF",
            f"-DOCE_INSTALL_PREFIX={DEPS_DIR}/install/oce-{OCE_VERSION}"
        ],
        download_url="https://github.com/tpaviot/oce/archive/",
        download_name=f"OCE-{OCE_VERSION}.tar.gz"
    )

if "libxml2" in targets:
    build_dependency(
        f"libxml2-{LIBXML2_VERSION}",
        "autoconf",
        build_tool_args=[
            "--without-python",
            ENABLE_FLAG,
            DISABLE_FLAG,
            "--without-zlib",
            "--without-iconv",
            "--without-lzma"
        ],
        download_url="ftp://xmlsoft.org/libxml2/",
        download_name=f"libxml2-{LIBXML2_VERSION}.tar.gz"
    )

if "OpenCOLLADA" in targets:
    patches = ["./patches/opencollada/pr622_and_disable_subdirs.patch"]

    if "wasm" in flags:
        # This is necessary for the WASM build, because recent versions of
        # clang don't have the tr1:: namespace anymore. However, it breaks
        # some versions of gcc (9.4.0 at least) due to specializing std::hash
        # outside of the std:: namespace.
        patches.append("./patches/opencollada/remove_tr1.patch")

    build_dependency(
        "OpenCOLLADA",
        "cmake",
        build_tool_args=[
            f"-DLIBXML2_INCLUDE_DIR={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2",
            f"-DLIBXML2_LIBRARIES={DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}",
            f"-DPCRE_INCLUDE_DIR={DEPS_DIR}/install/pcre-{PCRE_VERSION}/include",
            f"-DPCRE_PCREPOSIX_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcreposix.{LIBRARY_EXT}",
            f"-DPCRE_PCRE_LIBRARY={DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib/libpcre.{LIBRARY_EXT}",
            f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/OpenCOLLADA/"
        ],
        download_url="https://github.com/KhronosGroup/OpenCOLLADA.git",
        download_name="OpenCOLLADA",
        download_tool=download_tool_git,
        patch=patches,
        revision=OPENCOLLADA_VERSION
    )

if "python" in targets and not USE_CURRENT_PYTHON_VERSION and "wasm" not in flags:
    # Python should not be built with -fvisibility=hidden, from experience that introduces segfaults
    OLD_CXX_FLAGS = os.environ["CXXFLAGS"]
    OLD_C_FLAGS = os.environ["CFLAGS"]
    os.environ["CXXFLAGS"] = CXXFLAGS_MINIMAL
    os.environ["CFLAGS"] = CFLAGS_MINIMAL

    # On OSX a dynamic python library is built or it would not be compatible
    # with the system python because of some threading initialization
    PYTHON_CONFIGURE_ARGS = []
    if platform.system() == "Darwin":
        PYTHON_CONFIGURE_ARGS = ["--enable-shared"]

    for PYTHON_VERSION in PYTHON_VERSIONS:
        try:
            build_dependency(
                f"python-{PYTHON_VERSION}",
                "autoconf",
                PYTHON_CONFIGURE_ARGS,
                f"http://www.python.org/ftp/python/{PYTHON_VERSION}/",
                f"Python-{PYTHON_VERSION}.tgz"
            )
        except RuntimeError as e:
            # Sometimes setting up modules such as pip/lzma can cause
            # the python installer script to return a non zero exit
            # code where actually the headers and dynamic libraries
            # are installed correctly. This is all we need so we catch
            # the exception and only reraise if a partially successful
            # install is not detected.
            if not os.path.exists(
                os.path.join(DEPS_DIR, "install", f"python-{PYTHON_VERSION}")
            ):
                raise e

    os.environ["CXXFLAGS"] = OLD_CXX_FLAGS
    os.environ["CFLAGS"] = OLD_C_FLAGS

if "boost" in targets:
    str_concat = lambda prefix: lambda postfix: "" if postfix.strip() == "" else "=".join((prefix, postfix.strip()))
    toolset = []
    if "wasm" in flags:
        toolset.append("toolset=emscripten")
    build_dependency(
        f"boost-{BOOST_VERSION}",
        mode="bjam",
        build_tool_args=[
            f"--stagedir={DEPS_DIR}/install/boost-{BOOST_VERSION}",
            "--with-system",
            "--with-program_options",
            "--with-regex",
            "--with-thread",
            "--with-date_time",
            "--with-iostreams",
            f"link={LINK_TYPE}",
            *toolset,
            *map(str_concat("cxxflags"), CXXFLAGS.strip().split(' ')),
            *map(str_concat("linkflags"), LDFLAGS.strip().split(' ')),
            "stage", "-s", "NO_BZIP2=1"],
        download_url=BOOST_LOCATION,
        patch="./patches/boost/boostorg_regex_62.patch",
        download_name=f"boost_{BOOST_VERSION_UNDERSCORE}.tar.bz2"
    )
    if "wasm" in flags:
        # only supported on nix for now
        run(("find", ".", "-name", "*.bc", "-exec", "bash", "-c", "emar q ${1%.bc}.a $1", "bash", "{}", ";"), cwd=f"{DEPS_DIR}/install/boost-{BOOST_VERSION}/lib")

if "cgal" in targets:
    gmp_args = []
    mpfr_args = []
    if "wasm" in flags:
        gmp_args.extend(("--disable-assembly", "--host", "none", "--enable-cxx"))
        mpfr_args.extend(("--host", "none"))

    build_dependency(
        name=f"gmp-{GMP_VERSION}",
        mode="autoconf",
        build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, "--with-pic", *gmp_args],
        download_url="https://ftp.gnu.org/gnu/gmp/",
        download_name=f"gmp-{GMP_VERSION}.tar.bz2"
    )

    build_dependency(
        name=f"mpfr-{MPFR_VERSION}",
        mode="autoconf",
        build_tool_args=[ENABLE_FLAG, DISABLE_FLAG, *mpfr_args, f"--with-gmp={DEPS_DIR}/install/gmp-{GMP_VERSION}"],
        download_url=f"http://www.mpfr.org/mpfr-{MPFR_VERSION}/",
        download_name=f"mpfr-{MPFR_VERSION}.tar.bz2"
    )

    build_dependency(
        name=f"cgal-{CGAL_VERSION}",
        mode="cmake",
        build_tool_args=[
            f"-DGMP_LIBRARIES={DEPS_DIR}/install/gmp-{GMP_VERSION}/lib/libgmp.{LIBRARY_EXT}",
            f"-DGMP_INCLUDE_DIR={DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
            f"-DMPFR_LIBRARIES={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib/libmpfr.{LIBRARY_EXT}" ,
            f"-DMPFR_INCLUDE_DIR={DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
            f"-DBoost_INCLUDE_DIR={DEPS_DIR}/install/boost-{BOOST_VERSION}",
            f"-DCMAKE_INSTALL_PREFIX={DEPS_DIR}/install/cgal-{CGAL_VERSION}/",
            "-DCGAL_HEADER_ONLY=On",
            "-DBUILD_SHARED_LIBS=Off"
        ],
        download_url="https://github.com/CGAL/cgal.git",
        download_name="cgal",
        download_tool=download_tool_git,
        revision=f"v{CGAL_VERSION}"
    )

cecho("Building IfcOpenShell:", GREEN)

IFCOS_DIR = os.path.join(DEPS_DIR, "build", "ifcopenshell")
if os.environ.get("NO_CLEAN", "").lower() not in {"1", "on", "true"}:
    if os.path.exists(IFCOS_DIR):
        shutil.rmtree(IFCOS_DIR)
os.makedirs(IFCOS_DIR, exist_ok=True)
executables_dir = os.path.join(IFCOS_DIR, "executables")
os.makedirs(executables_dir, exist_ok=True)

OFF_ON = ["OFF", "ON"]

cmake_args = [
    "-DUSE_MMAP="                      "OFF",
    "-DBUILD_EXAMPLES="                "OFF",
    "-DBUILD_SHARED_LIBS="             +OFF_ON[not BUILD_STATIC],
    "-DBOOST_ROOT="                    f"{DEPS_DIR}/install/boost-{BOOST_VERSION}",
    "-DGLTF_SUPPORT="                  "ON",
    "-DJSON_INCLUDE_DIR="              f"{DEPS_DIR}/install/json",
    "-DBoost_NO_BOOST_CMAKE="          "On",
    "-DADD_COMMIT_SHA="              +("On" if ADD_COMMIT_SHA else "Off")
]

if "wasm" in flags:
    # Boost is built by the build script so should not be found
    # inside of the sysroot set by the emscriptem toolchain
    cmake_args.append("-DWASM_BUILD=On")

if "cgal" in targets:
    cmake_args.extend([
        "-DCGAL_INCLUDE_DIR="          f"{DEPS_DIR}/install/cgal-{CGAL_VERSION}/include",
        "-DGMP_INCLUDE_DIR="           f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/include",
        "-DGMP_LIBRARY_DIR="           f"{DEPS_DIR}/install/gmp-{GMP_VERSION}/lib",
        "-DMPFR_INCLUDE_DIR="          f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/include",
        "-DMPFR_LIBRARY_DIR="          f"{DEPS_DIR}/install/mpfr-{MPFR_VERSION}/lib",
    ])

if "occ" in targets and USE_OCCT:
    occ_include_dir =                  f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/include/opencascade"
    occ_library_dir =                  f"{DEPS_DIR}/install/occt-{OCCT_VERSION}/lib"
    cmake_args.extend([
        "-DOCC_INCLUDE_DIR="           +occ_include_dir,
        "-DOCC_LIBRARY_DIR="           +occ_library_dir
    ])

elif "occ" in targets:
    occ_include_dir =                  f"{DEPS_DIR}/install/oce-{OCE_VERSION}/include/oce"
    occ_library_dir =                  f"{DEPS_DIR}/install/oce-{OCE_VERSION}/lib"
    cmake_args.extend([
        "-DOCC_INCLUDE_DIR="           +occ_include_dir,
        "-DOCC_LIBRARY_DIR="           +occ_library_dir
    ])

if "OpenCOLLADA" in targets:
    cmake_args.extend([
        "-DOPENCOLLADA_INCLUDE_DIR="   f"{DEPS_DIR}/install/OpenCOLLADA/include/opencollada",
        "-DOPENCOLLADA_LIBRARY_DIR="   f"{DEPS_DIR}/install/OpenCOLLADA/lib/opencollada"
    ])
else:
    cmake_args.extend([
        "-DCOLLADA_SUPPORT="           "Off",
    ])

if "pcre" in targets:
    cmake_args.append(
        "-DPCRE_LIBRARY_DIR="          f"{DEPS_DIR}/install/pcre-{PCRE_VERSION}/lib"
    )

if "libxml2" in targets:
    cmake_args.extend([
        "-DLIBXML2_INCLUDE_DIR="       f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/include/libxml2",
        "-DLIBXML2_LIBRARIES="         f"{DEPS_DIR}/install/libxml2-{LIBXML2_VERSION}/lib/libxml2.{LIBRARY_EXT}"
    ])

if "hdf5" in targets:
    cmake_args.extend([
        "-DHDF5_INCLUDE_DIR="          f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/include",
        "-DHDF5_LIBRARY_DIR="          f"{DEPS_DIR}/install/hdf5-{HDF5_VERSION}/lib"
    ])
else:
    cmake_args.append("-DHDF5_SUPPORT=Off")

if not explicit_targets or {"IfcGeom", "IfcConvert", "IfcGeomServer"} & set(explicit_targets):
    logger.info("\rConfiguring executables...")

    exec_args = [
        "-DBUILD_IFCGEOM="                 +OFF_ON["IfcGeom" in targets],
        "-DBUILD_GEOMSERVER="              +OFF_ON["IfcGeomServer" in targets],
        "-DBUILD_CONVERT="                 +OFF_ON["IfcConvert" in targets],
        "-DBUILD_IFCPYTHON="               "OFF",
        "-DCMAKE_INSTALL_PREFIX="          f"{DEPS_DIR}/install/ifcopenshell",
    ]

    run_cmake("", exec_args + cmake_args, cmake_dir=CMAKE_DIR, cwd=executables_dir)

    logger.info("\rBuilding executables...   ")

    run([make, f"-j{IFCOS_NUM_BUILD_PROCS}"], cwd=executables_dir)
    run([make, "install/strip" if BUILD_CFG == "Release" else "install"], cwd=executables_dir)

if "IfcOpenShell-Python" in targets:
    # On OSX the actual Python library is not linked against.
    ADDITIONAL_ARGS = ""
    if platform.system() == "Darwin":
        ADDITIONAL_ARGS = "-Wl,-flat_namespace,-undefined,suppress"

    if "wasm" in flags:
        ADDITIONAL_ARGS = f"-Wl,-undefined,suppress -sSIDE_MODULE=2 -sEXPORTED_FUNCTIONS=_PyInit__ifcopenshell_wrapper"

    os.environ["CXXFLAGS"] = f"{CXXFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
    os.environ["CFLAGS"] = f"{CFLAGS_MINIMAL} {ADDITIONAL_ARGS}"
    os.environ["LDFLAGS"] = f"{LDFLAGS} {ADDITIONAL_ARGS}"

    python_dir = os.path.join(IFCOS_DIR, "pythonwrapper")
    os.makedirs(python_dir, exist_ok=True)

    def compile_python_wrapper(python_version, python_library, python_include, python_executable):
        logger.info(f"\rConfiguring python {python_version} wrapper...")

        cache_path = os.path.join(python_dir, "CMakeCache.txt")
        if os.path.exists(cache_path):
            os.remove(cache_path)

        os.environ["PYTHON_LIBRARY_BASENAME"] = os.path.basename(python_library)

        swig_when_built = []
        if "swig" in targets:
            swig_when_built.append(f"-DSWIG_EXECUTABLE={DEPS_DIR}/install/swig/bin/swig")

        run_cmake("",
            cmake_args + [
                "-DPYTHON_LIBRARY="          +python_library,
                *([f"-DPYTHON_EXECUTABLE={python_executable}"] if python_executable else []),
                # *([f"-DPYTHON_MODULE_INSTALL_DIR={os.environ['PYTHONPATH']}/ifcopenshell"] if "wasm" in flags else []),
                *(["-DPYTHON_MODULE_INSTALL_DIR="+os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "package"))] if "wasm" in flags else []),
                "-DPYTHON_INCLUDE_DIR="      +python_include,
                "-DCMAKE_INSTALL_PREFIX="    f"{DEPS_DIR}/install/ifcopenshell/tmp",
                "-DUSERSPACE_PYTHON_PREFIX=" +["Off", "On"][os.environ.get("PYTHON_USER_SITE", "").lower() in {"1", "on", "true"}],
                *swig_when_built],
            cmake_dir=CMAKE_DIR, cwd=python_dir)

        logger.info(f"\rBuilding python {python_version} wrapper...   ")

        run([make, f"-j{IFCOS_NUM_BUILD_PROCS}", "_ifcopenshell_wrapper"], cwd=python_dir)
        run([make, "install/local"], cwd=os.path.join(python_dir, "ifcwrap"))

        if python_executable:
            module_dir = os.path.dirname(run([python_executable, "-c", "import inspect, ifcopenshell; print(inspect.getfile(ifcopenshell))"]))

            if platform.system() != "Darwin":
                # TODO: This symbol name depends on the Python version?
                run([strip, "-s", "-K", "PyInit__ifcopenshell_wrapper", "_ifcopenshell_wrapper.so"], cwd=module_dir)

            return module_dir

    if "wasm" in flags:
        compile_python_wrapper(
            f"{os.environ['PYMAJOR']}.{os.environ['PYMINOR']}.{os.environ['PYMICRO']}",
            f"{os.environ['TARGETINSTALLDIR']}/lib/libpython{os.environ['PYMAJOR']}.{os.environ['PYMINOR']}.a",
            os.environ['PYTHONINCLUDE'],
            None
        )

    elif USE_CURRENT_PYTHON_VERSION:
        python_info = sysconfig.get_paths()

        py_path_components = [
            sysconfig.get_config_var('LIBDIR'),
            sysconfig.get_config_var("INSTSONAME")
        ]

        if sysconfig.get_config_var('multiarchsubdir'):
            py_path_components.insert(1, sysconfig.get_config_var('multiarchsubdir').replace("/", ""))

        python_lib = os.path.join(*py_path_components)

        compile_python_wrapper(platform.python_version(), python_lib, python_info["include"], sys.executable)
    else:
        for python_version in PYTHON_VERSIONS:
            python_library = run([bash, "-c", f"ls    {DEPS_DIR}/install/python-{python_version}/lib/libpython*.*"])
            python_include = run([bash, "-c", f"ls -d {DEPS_DIR}/install/python-{python_version}/include/python*"])
            python_executable = os.path.join(DEPS_DIR, "install", f"python-{python_version}", "bin", f"python{python_version[0]}")

            module_dir = compile_python_wrapper(python_version, python_library, python_include, python_executable)
            run([cp, "-R", module_dir, os.path.join(DEPS_DIR, "install", "ifcopenshell", f"python-{python_version}")])

logger.info("\rBuilt IfcOpenShell...\n\n")


# _*_ coding:utf-8 _*_
# @File  : short_message.py
# @Time  : 2020-09-15 13:40
# @Author: zizle

""" 短信通业务逻辑 """
import json
from datetime import datetime
from PyQt5.QtWidgets import qApp, QWidget, QVBoxLayout, QLabel
from PyQt5.QtCore import QTimer, Qt, QMargins, QUrl
from PyQt5.QtNetwork import QNetworkRequest
from settings import SERVER_API
from .short_message_ui import ShortMessageUI

class ContentWidget(QWidget):
    """ 内容控件 """
    def __init__(self, current_datetime, timer_start=False, *args, **kwargs):
        super(ContentWidget, self).__init__(*args, **kwargs)
        self.current_datetime = current_datetime
        self.auto_request_timer = QTimer(self)  # 定时请求数据
        self.auto_request_timer.timeout.connect(self._get_last_short_message)
        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(QMargins(3, 0, 5, 0))
        main_layout.addStretch()
        self.setLayout(main_layout)

        self._get_last_short_message()
        if timer_start:
            self.auto_request_timer.start(30000)
        self.setStyleSheet(
            "#contentLabel{background-color:rgb(240,240,240);padding:2px 3px 8px 8px;border-radius:5px;}"
            "#contentLabel:hover{background-color:rgb(230,230,230);padding:2px 3px 8px 8px;border-radius:5px;}"
        )

    def _get_last_short_message(self):
        """ 获取最新短信通 """
        # 请求比self.last_datetime大的数据(服务器仅返回当天的数据)
        # print("self.current_datetime: {}".format(self.current_datetime))
        network_message = getattr(qApp, "_network")
        url = SERVER_API + "short-message/?start_time={}".format(self.current_datetime)
        reply = network_message.get(QNetworkRequest(QUrl(url)))
        reply.finished.connect(self.latest_short_message_reply)

    def latest_short_message_reply(self):
        """ 最新的短信通数据返回 """
        reply = self.sender()
        if reply.error():
            pass
        else:
            data = json.loads(reply.readAll().data().decode("utf-8"))
            self.insert_latest_short_message(data["short_messages"])

    def insert_latest_short_message(self, contents):
        """ 新增最新短信通 """
        for index, content_item in enumerate(contents):
            content_label = QLabel(content_item["content"], self)
            content_label.setWordWrap(True)
            content_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
            content_label.setObjectName("contentLabel")

            self.layout().insertWidget(0, content_label)
            if index == len(contents) - 1:
                self.current_datetime = content_item["create_time"]

class ShortMessage(ShortMessageUI):
    def __init__(self, *args, **kwargs):
        super(ShortMessage, self).__init__(*args, **kwargs)
        self.animation_timer = QTimer(self)
        self.animation_timer.timeout.connect(self.refresh_animation_text)

        # 默认添加今日的内容控件
        current_datetime = datetime.today().strftime("%Y-%m-%dT00:00:00")
        content_widget = ContentWidget(current_datetime, timer_start=True)
        self.animation_timer.start(600)
        self.scroll_area.setWidget(content_widget)
        self.date_edit.dateChanged.connect(self.current_date_changed)

    def refresh_animation_text(self):
        """ 资讯持续更新中 """
        tips = self.animation_text.text()
        tip_points = tips.split(' ')[1]
        if len(tip_points) > 5:
            self.animation_text.setText("资讯持续更新中 ")
        else:
            self.animation_text.setText("资讯持续更新中 " + "·" * (len(tip_points) + 1))

    def current_date_changed(self, date):
        """ 当前时间发生改变 """
        date_edit_text = self.date_edit.text()
        current_date = datetime.strptime(date_edit_text, "%Y-%m-%d")
        current_date_str = current_date.strftime("%Y-%m-%dT00:00:00")
        week_name = self.WEEKS.get(current_date.strftime("%w"))
        self.current_date.setText(date_edit_text + week_name)

        if current_date_str == datetime.today().strftime("%Y-%m-%dT00:00:00"):
            timer_start = True
            self.animation_text.show()
            if not self.animation_timer.isActive():
                self.animation_timer.start(600)
        else:
            timer_start = False
            self.animation_text.hide()
            if self.animation_timer.isActive():
                self.animation_timer.stop()
        self.animation_text.setText("资讯持续更新中 ")
        content_widget = ContentWidget(current_date_str, timer_start=timer_start)
        self.scroll_area.setWidget(content_widget)


from __future__ import annotations
from typing import Tuple, List
from random import randint
from card import Card
from abc import ABC, abstractmethod

class DefaultDeck(ABC):
    @abstractmethod
    def __init__(self) -> None:
        self.deck = []
        return None

    def get_cards(self, number_of_cards: int) -> List[Card]:
        if len(self.deck) < number_of_cards:
            raise IndexError("not enough cards in deck")
        cards = []
        while number_of_cards != 0:
            card = self.deck.pop()
            cards.append(card)
            number_of_cards -= 1
        return cards

    def add_card(self, card: Card) -> None:
        if not isinstance(card, Card):
            raise TypeError("card not of correct type")
        self.deck.append(card)
        return None

    def add_deck(self, new_deck: DefaultDeck) -> None:
        if not isinstance(new_deck, DefaultDeck):
            raise TypeError("deck not of correct type")
        for card in new_deck.deck:
            self.add_card(card)
        return None

    def shuffle(self) -> None:
        if len(self.deck) <= 1:
            return None
        for _ in range(len(self.deck) * 10):
            idx1, idx2 = self.__get_two_rand_indexes()
            self.deck[idx1], self.deck[idx2] = self.deck[idx2], self.deck[idx1]
        return None

    def show_cards(self, simple: bool=True) -> List[str]:
        output = []
        for card in self.deck:
            string = self.__card_to_string(card, simple)
            output.append(string)
        return output

    def __get_two_rand_indexes(self) -> Tuple[int, int]:
        rand_idx_1 = randint(0, len(self.deck) - 1)
        rand_idx_2 = randint(0, len(self.deck) - 1)
        return (rand_idx_1, rand_idx_2)

    def __card_to_string(self, card: Card, simple_view: bool) -> str:
        if not isinstance(card, Card):
            raise TypeError("card not of correct type")
        temp_string = card.value
        if (card.suit is not None) and (not simple_view):
            temp_string += f' - {card.suit}'
        if (card.description is not None) and (not simple_view):
            temp_string += f' - {card.description}'
        return temp_string

class Standard52CardDeck(DefaultDeck):
    def __init__(self) -> None:
        self.deck = []
        for suit in ["Spades", "Clubs", "Hearts", "Diamonds"]:
            for value in ["Ace", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"]:
                card = Card(f'{value[0] if len(value) > 2 else value} {suit}', suit, f'{value} of {suit}')
                self.deck.append(card)
        return None

class Standard52CardDeckJokers(DefaultDeck):
    def __init__(self) -> None:
        self.deck = [Card("Joker", "Red", "Red Joker"), Card("Joker", "Black", "Black Joker")]
        for suit in ["Spades", "Clubs", "Hearts", "Diamonds"]:
            for value in ["Ace", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"]:
                card = Card(f'{value[0] if len(value) > 2 else value} {suit}', suit, f'{value} of {suit}')
                self.deck.append(card)
        return None

class CatanResourceDeck(DefaultDeck):
    def __init__(self) -> None:
        self.deck = []
        for _ in range(19):
            self.deck.append(Card("Ore", "Resource", ""))
            self.deck.append(Card("Grain", "Resource", ""))
            self.deck.append(Card("Lumber", "Resource", ""))
            self.deck.append(Card("Wool", "Resource", ""))
            self.deck.append(Card("Brick", "Resource", ""))
        return None


import matplotlib.pylab as pl
import matplotlib.pyplot as plt
import numpy as np

index = list(range(1, 13))
values = list(range(3, 15))
plt.bar(index, values)
plt.plot(index, values, 'rD')
plt.plot(index, values, '+')
plt.title('Title')
plt.xlabel('xlabel')
plt.ylabel('ylabel')
plt.show()

t = np.arange(0., 4., 0.1)
plt.plot(t, t, t, t + 3, t, t ** 2, t, np.exp(t))
plt.show()

pl.plot(t, t, t, t + 3, t, t ** 2, t, np.exp(t))
pl.show()