import unittest

from abc import ABC
from typing import Iterable
from typing import List

from iamsystem.fuzzy.abbreviations import Abbreviations
from iamsystem.fuzzy.abbreviations import token_is_upper_case
from iamsystem.fuzzy.api import NormLabelAlgo
from iamsystem.fuzzy.api import SynAlgo
from iamsystem.fuzzy.api import SynType
from iamsystem.fuzzy.cache import CacheFuzzyAlgos
from iamsystem.fuzzy.cache import get_norm_label_algos_syns
from iamsystem.fuzzy.exact import ExactMatch
from iamsystem.fuzzy.norm_fun import WordNormalizer
from iamsystem.fuzzy.regex import FuzzyRegex
from iamsystem.fuzzy.spellwise import ESpellWiseAlgo
from iamsystem.fuzzy.spellwise import SpellWiseWrapper
from iamsystem.keywords.keywords import Keyword
from iamsystem.matcher.matcher import Matcher
from iamsystem.tokenization.api import TokenT
from iamsystem.tokenization.tokenize import english_tokenizer
from iamsystem.tokenization.tokenize import french_tokenizer
from iamsystem.tokenization.tokenize import split_find_iter_closure
from tests.utils import get_termino_ulceres

class TestFuzzyAlgo(NormLabelAlgo, ABC):
    """A fuzzy algos that tests how many times it is called."""

    def __init__(self, name: str = "test_fuzzy"):
        super().__init__(name)
        self.calls = 0
        self.exact = ExactMatch()

    def get_syns_of_word(self, word: str) -> Iterable[SynType]:
        """exact match and count calls."""
        self.calls += 1
        return self.exact.get_syns_of_word(word)

class InvalidSynonymsReturnedTest(unittest.TestCase):
    def test_fuzzy_algo_returns_string(self):
        """fuzzy algorithms must return a tuple of string,
        ex: ('insuffisance', 'cardiaque'). if the algorithm returns another
        iterable like a list, it must fail fast (exception raise).
        """

        def getSynonymsWrong(token: str):
            return [token]

        fuzzy = TestFuzzyAlgo()
        fuzzy.get_syns_of_word = getSynonymsWrong
        cache_syns = CacheFuzzyAlgos()
        cache_syns.add_algo(fuzzy)
        with (self.assertRaises(TypeError)):
            cache_syns.get_synonyms("anyToken")

class AbbreviationTest(unittest.TestCase):
    def get_first_token_long_forms(self, text: str) -> List[SynType]:
        """Utility function to return the long forms of the first token."""
        tokens = self.tokenizer.tokenize(text=text)
        first_token = tokens[0]
        long_forms: List[SynType] = list(
            self.abbs.get_syns_of_token(first_token)
        )
        return long_forms

    def setUp(self) -> None:
        self.abbs = Abbreviations(
            name="my abbreviations",
            token_is_an_abbreviation=token_is_upper_case,
        )
        self.tokenizer = french_tokenizer()
        self.abbs.add(
            short_form="avc",
            long_form="accident vasculaire cerebral",
            tokenizer=self.tokenizer,
        )

    def test_uppercase(self):
        """An abbreviations instance returns the long form only
        if the token is uppercase."""
        long_forms = self.get_first_token_long_forms(text="AVC sylvien")
        self.assertEqual(1, len(long_forms))

    def test_lowercase(self):
        """Lowercase so no annotation returned (upper only)."""
        long_forms = self.get_first_token_long_forms(text="avc sylvien")
        self.assertEqual(0, len(long_forms))

    def test_custom_token_is_an_abbreviation(self):
        """Modify the function that checks if a token is an abbreviation."""

        def first_letter_is_upper(token: TokenT) -> bool:
            """Return True if the first token's characters is uppercase."""
            return token.label[0].isupper()

        self.abbs.is_token_an_abbreviation = first_letter_is_upper
        long_forms = self.get_first_token_long_forms(text="aVC sylvien")
        self.assertEqual(0, len(long_forms))

        long_forms = self.get_first_token_long_forms(text="Avc sylvien")
        self.assertEqual(1, len(long_forms))

class AbbreviationsTest(unittest.TestCase):
    def setUp(self) -> None:
        self.abbs = Abbreviations(
            name="abbs", token_is_an_abbreviation=lambda token: True
        )
        self.tokenizer = french_tokenizer()

    def test_no_syn_returned(self):
        """A global empty tuple is returned by fuzzy algorithms
        when the algorithm has no suggestions"""
        self.assertEqual(len(list(self.abbs.get_syns_of_word("avc"))), 0)

    def test_add(self):
        """Add 2 long forms, check the 2 are returned."""
        self.abbs.add(
            short_form="avc",
            long_form="accident vasculaire cerebral",
            tokenizer=self.tokenizer,
        )
        long_forms: List[SynType] = list(self.abbs.get_syns_of_word("avc"))
        self.assertEqual(1, len(long_forms))
        self.assertTrue(("accident", "vasculaire", "cerebral") in long_forms)
        # another long_form :
        self.abbs.add(
            short_form="avc",
            long_form="another abbreviation",
            tokenizer=self.tokenizer,
        )
        long_forms: Iterable[SynType] = self.abbs.get_syns_of_word("avc")
        self.assertEqual(2, len(list(long_forms)))

    def test_add_tokenized_long_form(self):
        """Test adding a sequence of words."""
        self.abbs.add_tokenized_long_form(
            short_form="avc",
            long_form=["accident", "vasculaire", "cerebral"],
        )
        long_forms: List[SynType] = list(self.abbs.get_syns_of_word("avc"))
        self.assertEqual(1, len(long_forms))
        self.assertTrue(("accident", "vasculaire", "cerebral") in long_forms)

    def test_add_lower_short_form(self):
        """Even though the given short form is uppercase,
        a lowercase word returns the long form."""
        self.abbs.add(
            short_form="AVC",
            long_form="accident vasculaire cerebral",
            tokenizer=self.tokenizer,
        )
        long_forms: List[SynType] = list(self.abbs.get_syns_of_word("avc"))
        self.assertEqual(len(long_forms), 1)

class TupleTest(unittest.TestCase):
    def test_tuple_stored_in_set(self):
        """check a tuple added twice in a set is ignored (default Python)"""
        syns_set = set()
        syn1 = tuple(["insuffisance", "respiratoire", "chronique"])
        syn2 = tuple(["insuffisance", "renale", "chronique"])
        syns_set.add(syn1)
        syns_set.add(syn2)
        self.assertEqual(2, len(syns_set))
        syns_set.add(syn2)
        self.assertEqual(2, len(syns_set))

    def test_tuple_list_vs_tuple_string(self):
        """One frequent mistake I make and that users can make is to call
        'tuple' function on string and not on a list.
        Calling it on a string returns a tuple of characters.
        """
        syns_set = set()
        syn1 = tuple(["insuffisance"])  # ('insuffisance')
        syn2 = tuple("insuffisance")  # ('i','n',...,'e')
        syns_set.add(syn1)
        syns_set.add(syn2)
        self.assertEqual(2, len(syns_set))

class ExactMatchTest(unittest.TestCase):
    def test_exact_match(self):
        """Test exact match returns the correct format."""
        exact = ExactMatch()
        syns = exact.get_syns_of_word("insuffisance")
        self.assertTrue(tuple(["insuffisance"]) in syns)

class MultipleFuzzyAlgosTest(unittest.TestCase):
    def setUp(self) -> None:
        abbs = Abbreviations(name="abbs")
        tokenizer = english_tokenizer()
        abbs.add(
            short_form="fr",
            long_form="frequence respiratoire",
            tokenizer=tokenizer,
        )
        exact_match = ExactMatch()
        self.fuzzy_algos = [exact_match, abbs]
        self.tokens = tokenizer.tokenize("augmentation de la fr")
        self.i = 3  # 'fr' token

    def test_get_norm_label_algos_syns(self):
        """Return all the synonyms of NormLabel algorithms."""
        syns: List[SynAlgo] = get_norm_label_algos_syns(
            fuzzy_algos=self.fuzzy_algos, norm_label="fr"
        )
        self.assertEqual(len(syns), 2)
        label, algo = syns[0]
        self.assertEqual(tuple(["fr"]), label)
        label, algo = syns[1]
        self.assertEqual(tuple(["frequence", "respiratoire"]), label)

class CacheFuzzyAlgosTest(unittest.TestCase):
    def setUp(self) -> None:
        self.fuzzy = TestFuzzyAlgo()
        self.cache_syns = CacheFuzzyAlgos()
        self.tuple_ins = tuple(["insuffisance"])

    def test_number_of_calls(self):
        """The purpose of the cache is to call fuzzy algorithms only once and
        then stores the results in cache. This test is just to check that
        'calls' attribute record correctly the number of calls.
        """
        self.fuzzy.get_syns_of_word("insuffisance")
        self.fuzzy.get_syns_of_word("insuffisance")
        self.assertEqual(2, self.fuzzy.calls)

    def test_caching(self):
        """Fuzzy algorithms cached are only called once and cache
        must work (synonyms are returned)."""
        self.cache_syns.add_algo(self.fuzzy)
        self.assertEqual(0, self.fuzzy.calls)
        self.cache_syns.get_syns_of_word("insuffisance")
        self.assertEqual(1, self.fuzzy.calls)  # test_fuzzy was called
        self.cache_syns.get_syns_of_word("insuffisance")
        self.assertEqual(
            1, self.fuzzy.calls
        )  # test_fuzzy was not called twice
        self.assertTrue("insuffisance" in self.cache_syns.cache)

    def test_get_syns_of_word(self):
        """Add exact match algorithm and check it returns a synonym."""
        self.cache_syns.add_algo(algo=ExactMatch())
        syns: List[SynAlgo] = self.cache_syns.get_syns_of_word("insuffisance")
        syn: SynAlgo = syns[0]
        synonym, algo = syn
        self.assertEqual(self.tuple_ins, synonym)
        self.assertEqual(algo, "exact")

    def test_get_syns_of_word_no_algo(self):
        """By default the cache contains no algorithm so it returns no
        synonym."""
        syns = self.cache_syns.get_syns_of_word("insuffisance")
        self.assertEqual(0, len(syns))

    def test_add_spellwise(self):
        """Check it call the spellwise algorithm and stores its suggestions"""
        algo: SpellWiseWrapper = SpellWiseWrapper(
            ESpellWiseAlgo.LEVENSHTEIN, max_distance=1
        )
        algo.add_words(words=["insuffisance"])
        self.cache_syns.add_algo(algo)
        syns = self.cache_syns.get_syns_of_word("insufisance")
        syn = syns[0]
        self.assertTrue(self.tuple_ins in syn)

class FuzzyRegexTest(unittest.TestCase):
    def setUp(self) -> None:
        self.fuzzy = FuzzyRegex(
            name="regex_num",
            pattern=r"^\d*[.,]?\d*$",
            pattern_name="numval",
        )
        split = split_find_iter_closure(pattern=r"(\w|\.|,)+")
        self.tokenizer = french_tokenizer()
        self.tokenizer.split = split
        self.matcher = Matcher(tokenizer=self.tokenizer)

    def test_split_function(self):
        """[calcium, 2.1, mmol, L] expected."""
        tokens = self.tokenizer.tokenize("calcium 2.1 mmol/L")
        self.assertEqual(4, len(tokens))

    def test_replace_pattern_in_keyword(self):
        """The function replaces regex matches by the pattern_name
        In this example, span containing '2.1' is replaced by 'numval'
        """
        keyword = Keyword(label="calcium 2.1 mmol/L")
        new_keyword = self.fuzzy.replace_pattern_in_keyword(
            keyword=keyword, tokenizer=self.tokenizer
        )
        self.assertEqual("calcium numval mmol/L", new_keyword.label)

    def test_get_syns_of_word(self):
        """the function should return the patter name (numval here)."""
        syns = self.fuzzy.get_syns_of_word("2.1")
        self.assertTrue(tuple(["numval"]) in syns)

    def test_detection(self):
        """It works with a matcher."""
        keyword = Keyword(label="CALCIUM NUMVAL mmol/L")
        self.matcher.add_keywords(keywords=[keyword])
        annots = self.matcher.annot_text(text="calcium 2.1 mmol/L")
        self.assertEqual(0, len(annots))
        self.matcher.add_fuzzy_algo(self.fuzzy)
        annots = self.matcher.annot_text(text="calcium 2.1 mmol/L")
        self.assertEqual(1, len(annots))

class WordNormalizerTest(unittest.TestCase):
    def simple_stemmer(self, string: str):
        if string.startswith("gastr"):
            return "gastr"
        else:
            return string

    def setUp(self) -> None:
        self.fuzzy_stemmer = WordNormalizer(
            name="stemmer", norm_fun=self.simple_stemmer
        )
        self.tokenizer = french_tokenizer()
        self.detector = Matcher(tokenizer=self.tokenizer)
        self.detector.add_fuzzy_algo(self.fuzzy_stemmer)

    def test_get_syns_of_word(self):
        """'gastrique' and 'gastrologique' start with 'gastr'
        so 'gastrique' is a synonym of 'gastrologique'.
        """
        self.fuzzy_stemmer.add_words(words=["gastrique", "gastriques"])
        synonyms = self.fuzzy_stemmer.get_syns_of_word("gastrologique")
        self.assertTrue(tuple(["gastrique"]) in synonyms)

    def test_detection_stem(self):
        """'gastrique' and 'gastrologique' start with 'gastr'
        Matcher matches it.
        """
        self.fuzzy_stemmer.add_words(words=["gastrique"])
        self.detector.add_keywords(keywords=get_termino_ulceres())
        text = "Ulcère gastrologique"
        annots = self.detector.annot_text(text)
        self.assertEqual(1, len(annots))

if __name__ == "__main__":
    unittest.main()


import dht
import errno
import gc
import machine
import os
import time
import ujson as json
import ubinascii as binascii

from config import read_config, write_config
from logging import log_info, log_exception
from mqtt import mqtt_create, mqtt_connect, mqtt_publish_message, mqtt_publish_state_message, get_epoch, mqtt_check_msg, mqtt_disconnect
from syncedtime import sync_time_with_ntp, get_epoch

# Test reception e.g. with:
# mosquitto_sub -t foo_topic

# suscribe thermostat ctrl
# - turn fan on (set a safety max time?)
# - turn fan off
# - turn ac on (will turn fan on too) (set a safety max time?)
# - turn ac off (will turn fan off too)
# - turn fan on for a period of time
# - turn ac on for a period of time
# - XXX turn ac on at a temperature once DHT22 is supported
# - XXX Schedules?

# publish ac topic
# - fan starts
# - fan stops
# - ac starts
# - ac stops
# - XXX temperature when it changes above some delta/every min once DHT22 is supported

# Safety rules:
# - always start the fan when AC is started
# - always start the fan first a few seconds before AC
# - wait a safety interval (1min?) since the last AC stop
# - don't run AC for more than 30 mins straight
# - don't allow setting temps below 70F
# - when following a temp, start the AC when it's above the temp plus safety margin, stop when it's below the temp minus safety margin

config_filename = "lessmostat.cfg"

def sub_cb(client, topic, msg):
    """

    Start at the given temp or higher
    { "state" : "on", temp="26" }

    Set fan to "auto" or "on"
    { "state" : "auto" }
    { "state" : "on" }

    XXX TODO complex scheduling
    messages
    topic: x/lessmostat/control/fan msg: { timestamp: , state: "on" | "off",  tod :, dow , temp : degs }
    topic: x/lessmostat/control/ac msg: { timestamp: , state: "on" | "off", tod :,  dow, temp : degs }
    topic: x/lessmostat/control/reset:
    topic: x/lessmostat/control/state: force state publish

    state: "on" | "off" set the device to on or off
    start_dow : start day of the week from 0 to 6, null for today
    start_time : 24 hour and minute start time, null for now
    start_temp : null for any temp

    { "state": "on" } Start
    { "state": "off" } Stop

    Start at the given temp or higher
    { "state" : "on", temp="26" }
    Stop at the given temp or lower
    { "state" : "off", temp="24" }

    Run for one hour
    { "state" : "on", tod: now() }
    { "state" : "off", tod: now()+1 hour }

    set morning and evening temps
    { "state" : "on", tod: 10am, temp="26" }
    { "state" : "off", tod: 8pm, temp="24" }
    { "state" : "on", tod: 8pm, temp="28" }
    { "state" : "off", tod: 10am, temp="26" }

    Stop on sundays 10am
    { "state" : "off", tod:10am, dow:6 }

    The controller will impose safety limits and then resume when the safety stop has been done:
    - ac run less than 30 mins straight
    - ac run with 5 mins between stop / start
    - fan run less than 12 hours straight
    - fan run with 1 mins between stop / start

    sunrise and sunset

    https://sunrise-sunset.org/api
    https://stackoverflow.com/questions/19615350/calculate-sunrise-and-sunset-times-for-a-given-gps-coordinate-within-postgresql

    request = Request('http://api.sunrise-sunset.org/json?lat=25.7741728&lng=-80.19362&formatted=0')
    response = urlopen(request)
    timestring = response.read()

    utcsunrise = timestring[34:39]
    utcsunset = timestring[71:76]
    utcmorning = timestring[182:187]
    utcnight = timestring[231:236]

    Get lat/long from https://sunrise-sunset.org/search?location=miami
    """
    log_info("callback for topic %r msg %r" % (topic, msg))

    try:
        d = json.loads(msg)

        if (topic.endswith("/state")):
            mqtt_publish_state_message(client, state)

        elif (topic.endswith("control/ac")):
            state["config"]["ac_rules"] = [
                { "state" : "on", "temp" : d["temp"] },
            ]
            # XXX Should this publish only the delta?
            mqtt_publish_state_message(client, state)

        elif (topic.endswith("control/fan")):
            state["config"]["fan_rules"] = [
                { "state" : d["state"] },
            ]
            # XXX Should this publish only the delta?
            mqtt_publish_state_message(client, state)

        elif (topic.endswith("control/store_preset")):
            # Copy the current rules into the given preset
            preset_index = max(0, min(d["index"], max_presets - 1))
            state["config"]["presets"][preset_index] = {
                "fan" : state["config"]["fan_rules"][0],
                "ac" : state["config"]["ac_rules"][0],
            }
            # XXX Should this publish only the delta?
            mqtt_publish_state_message(client, state)

        elif (topic.endswith("control/mode")):
            # Switch heating/cooling mode

            # As a safety measure, don't start/stop AC when switching modes,
            # 1) Ignore switching if the ac is already working
            # 2) set the target temperature to the current one before switching
            # Note switching to the same mode will cause the temperature to be reset
            if (state["ac"] == "on"):
                log_info("Ignoring control/mode request %s while ac is on" % d["state"])

            else:
                # XXX This assumes there's only one rule
                state["config"]["ac_rules"] = [
                    { "state" : "on", "temp" : state["sensor"]["temp"] },
                ]
                if (d["state"] == "cooling"):
                    currentMode = "cooling"
                else:
                    currentMode = "heating"
                state["config"]["mode"] = currentMode
                # XXX Should this publish only the delta?
                mqtt_publish_state_message(client, state)

    except Exception as e:
        log_exception("Exception handling topic %r message %r" % (topic, msg), e)

topic_root = "apartment/lessmostat/"

# See http://www.icstation.com/esp8266-wifi-channel-relay-module-smart-home-remote-control-switch-android-phone-control-transmission-distance-100m-p-12592.html
relay_1_on = [0xA0, 0x01, 0x1, 0xA2]
relay_1_off = [0xA0, 0x01, 0x00, 0xA1]
relay_2_on = [0xA0, 0x02, 0x01, 0xA3]
relay_2_off = [0xA0, 0x02, 0x00, 0xA2]

def uart_write(uart, b):
    uart.write(b)
    # XXX Looks like some delay is needed between uart writes otherwise the
    #     second write is lost and the relay state is not modified
    #     This is the case even with some code between uart_write invocations
    time.sleep(1)

fan_on = bytes(relay_2_on)
fan_off = bytes(relay_2_off)
def turn_fan(uart, client, on):
    fan_state = state["fan"]
    # Uptime accumulation assumes there are no redundant calls
    assert ((on and (fan_state != "on")) or ((not on) and fan_state != "off"))
    if (on):
        # XXX Should prevent somewhere it's not trying to re-enable fan before the
        #     safety idle period
        uart_write(uart, fan_on)
        fan_state = "on"

    else:
        uart_write(uart, fan_off)
        fan_state = "off"

    state["fan"] = fan_state
    prev_fan_mod_ts = state["fan_mod_ts"]
    state["fan_mod_ts"] = get_epoch()
    if (not on):
        # Accumulate uptime
        state["fan_uptime"] += (state["fan_mod_ts"] - prev_fan_mod_ts)

    mqtt_publish_message(client, "info/fan", { 'state' : fan_state, 'mod_ts' : state["fan_mod_ts"], 'uptime' : state["fan_uptime"] })

ac_on = bytes(relay_1_on)
ac_off = bytes(relay_1_off)
def turn_ac(uart, client, on):
    ac_state = state["ac"]
    # Uptime accumulation assumes there are no redundant calls
    assert ((on and (ac_state != "on")) or ((not on) and ac_state != "off"))
    if (on):
        # Always turn fan on before ac
        if (state["fan"] != "on"):
            log_info("ac forcing fan on")
            turn_fan(uart, client, on)

        # XXX Should prevent somewhere it's not trying to re-enable ac before
        #     the safety idle period

        uart_write(uart, ac_on)
        ac_state = "on"

    else:
        uart_write(uart, ac_off)
        ac_state = "off"

        # Leave the fan on, let it turn off depending on the rules

    state["ac"] = ac_state
    prev_ac_mod_ts = state["ac_mod_ts"]
    state["ac_mod_ts"] = get_epoch()
    if (not on):
        # Accumulate uptime
        state["ac_uptime"] += (state["ac_mod_ts"] - prev_ac_mod_ts)

    mqtt_publish_message(client, "info/ac", { 'state' : ac_state, 'mod_ts' : state["ac_mod_ts"], 'uptime' : state["ac_uptime"] })

state = {
    # Current state
    "start_ts" : None,

    "ac" : "off",
    "ac_mod_ts" : None,
    "ac_uptime" : 0,

    "fan" : "off",
    "fan_mod_ts" : None,
    "fan_uptime" : 0,

    "sensor" : { "humid" : None, "temp" : None },

    # Configuration state
    # XXX This may want to save the mod_ts so it doesn't continuously
    #     restart the AC. Alternatively always wait the safety period when
    #     restarting the program
    "config" : {
        "mqtt_broker" : "192.168.8.200",
        # The AC has a single rule which is to
        # - in cooling mode, start at the given temperature + hi_threshold and
        #   stop at the temperature - lo_threshold
        # - in heating mode, start at the given temperature - lo_threshold and
        #   stop at the temperature + hi_threshold
        "ac_rules" : [
            { "state" : "on", "temp" : 30 },
        ],
        # The fan has a single rule which is to set the fan to "on" (always
        # on or "auto" (match ac state)
        # XXX Allow setting the fan on a timer ("on", 15-30-60-120 min, "auto")
        #     and then revert to auto
        # XXX The fan counter could also show a guess on how long it will take
        #     to bring to the desired temp. Or move to a counter in the
        #     idle/cooling/heating state
        "fan_rules" : [
            { "state" : "auto" }
        ],
        # Hi and low thresholds in 1/10th of a celsius degree (ie in cooling
        # mode will turn on at temp + hi threshold and off at temp - low
        # threshold, in heating will turn on at temp - low and off at temp + hi)
        "lo_threshold_decidegs" : 4,
        "hi_threshold_decidegs" : 4,

        # heating/cooling mode
        "mode" : "cooling",

        # XXX Should this store the thresholds too?
        "presets" : [
            { "fan" : { "state" : "auto" }, "ac" : { "state" : "on", "temp" : 30 } },
            { "fan" : { "state" : "auto" }, "ac" : { "state" : "on", "temp" : 30 } },
            { "fan" : { "state" : "auto" }, "ac" : { "state" : "on", "temp" : 30 } },
        ],
    }
}
max_presets = len(state["config"]["presets"])

def main():
    try:
        log_info("Reading initial configuration")
        read_config(config_filename, state)
        log_info("Initial state is %r" % state)

        # Fetch some constant values from the config
        hi_threshold_decidegs = state["config"]["hi_threshold_decidegs"]
        lo_threshold_decidegs = state["config"]["lo_threshold_decidegs"]
        mqtt_broker = state["config"]["mqtt_broker"]

        # Update time with NTP
        sync_time_with_ntp()

        client_id = binascii.hexlify(machine.unique_id())

        # Now that we have an NTP time, initialize state times
        now_ts = get_epoch()
        state["start_ts"] = now_ts
        state["ac_mod_ts"] = now_ts
        state["fan_mod_ts"] = now_ts

        log_info("Initializing relays uart")
        uart = machine.UART(0, baudrate=115200, bits=8, parity=None, stop=1)
        # Detach REPL from UART so UART can be used for the relays
        log_info("Detaching UART from repl")
        os.dupterm(uart, 1)

        # The DHT is connected to the 5V, GND and RX (gpio 3)
        # Steal the RX pin from the uart, see
        # https://forum.micropython.org/viewtopic.php?t=6669
        log_info("Initializing DHT sensor")
        dht_sensor = dht.DHT22(machine.Pin(3, machine.Pin.IN))
        # DHT22 is known to timeout the first few times measure() is called, retry
        num_retries = 10
        while (num_retries > 0):
            try:
                num_retries -= 1
                dht_sensor.measure()
                break
            except OSError as e:
                if (e.errno == errno.ETIMEDOUT):
                    log_info("DHT sensor timed out, retrying")

                else:
                    raise

        # On power unplug reset the relays are closed, but on machine reset they
        # are left to whatever state before reset, so set the AC and fan relays
        # to a known state (closed)
        # XXX Should this have some hysteresis in case this is always starting
        #     and engaging the ac/fan? It already has a delay in main.py, but
        #     the main loop could have a warm-up timer where it ignores the
        #     rules (or just a plain sleep)
        log_info("Setting ac to known state")
        uart_write(uart, ac_off)
        log_info("Setting fan to known state")
        uart_write(uart, fan_off)

        client = mqtt_create(mqtt_broker, client_id, topic_root, sub_cb)
        # Now that the state is known, connect and accept control commands
        mqtt_connect(client)

        # Advertise the initial state after accepting control commands so
        # clients can start as soon as they get the initial state
        # XXX There's the theoretical possibility of a stale control command
        #     coming in before the state is advertised?
        mqtt_publish_state_message(client, state)

        gc.collect()

        log_info("Starting sensor reading and MQTT message handling forever loop")
        while (True):
            # Gather sensor information
            # Do sparingly since this can take 2s on DHT22, 1s on DHT11
            dht_sensor.measure()

            temp = dht_sensor.temperature()
            humid = dht_sensor.humidity()

            # Publish sensor information
            # XXX Should this only publish changes?
            mqtt_publish_message(client, "info/sensor", {'temp' : temp, 'humid' : humid})

            state["sensor"]["temp"] = temp
            state["sensor"]["humid"] = humid

            sync_time_with_ntp()

            # Wait some seconds between reporting sensor data (the wait could be
            # longer depending on the execution time of the loop below, but it's
            # okay even if we ever need some time accurate policy because sensor
            # messages contain timestamp)
            sleep_ms = 10_000
            # Sleep a few millis between message checks so target temperature
            # updates on the client are responsive
            sleep_iteration_ms = 500
            iterations = sleep_ms / sleep_iteration_ms
            for i in range(iterations):
                # Non-blocking check for messages

                try:
                    # This raises OSERROR (-1), ECONNRESET (errno 114),
                    # ECONNABORTED (errno 113) on error
                    mqtt_check_msg(client)

                except OSError as e:
                    # Don't bother logging these to file as they are too noisy
                    # and non fatal
                    log_exception("Exception checking MQTT message", e, True)

                    mqtt_connect(client)

                time.sleep_ms(sleep_iteration_ms)

                # Check rules
                # Collect active rules
                # No need to refresh temp, will be refreshed in the next 30s
                # XXX This only needs checking if the rules changed, since the
                #     temperature is only updated above
                ac_rules = state["config"]["ac_rules"]
                # XXX Note heating/cooling assumes the right wire (white heating
                #     / yellow for cooling) is being driven by the ac_on relay.
                #     Ideally this should use a three or four-channel relay,
                #     another option is to connect the green wire (fan) to both,
                #     drive heating with the current fan relay the and lose
                #     independent fan control?
                heating = (state["config"]["mode"] == "heating")
                cooling = not heating
                for rule in ac_rules:
                    rule_state = rule["state"]
                    rule_temp = rule.get("temp", None)
                    # XXX In heating mode the AC seems to wait for around one
                    #     minute to turn the fan off, there should be a way to
                    #     put that safety rule
                    if (rule_temp is not None):
                        under_threshold = (temp*10 <= rule_temp*10 - lo_threshold_decidegs)
                        over_threshold = (temp*10 >= rule_temp*10 + hi_threshold_decidegs)
                        if ((state["ac"] != "on") and (rule_state == "on") and
                            ((heating and under_threshold) or (cooling and over_threshold))):
                            log_info("Starting ac")
                            turn_ac(uart, client, True)

                        elif ((state["ac"] != "off") and (rule_state == "on") and
                            ((heating and over_threshold) or (cooling and under_threshold))):
                            log_info("Stopping ac")
                            turn_ac(uart, client, False)

                fan_rules = state["config"]["fan_rules"]
                for rule in fan_rules:
                    rule_state = rule["state"]
                    if ((rule_state == "on") and (state["fan"] != "on")):
                        log_info("Starting fan")
                        turn_fan(uart, client, True)

                    elif ((rule_state == "auto") and (state["fan"] != state["ac"])):
                        # Note that in reality this code will only run to turn
                        # the fan off, the fan is turned on unconditionally for
                        # safety reasons at ac turn on time
                        log_info("Matching fan to ac")
                        turn_fan(uart, client, state["ac"] == "on")

        mqtt_disconnect(client)

    finally:
        log_info("Exited main, writing configuration")
        # Write the configuration only at exit, don't want to wear out the flash
        # by writing to it on every target degree update
        # XXX This should write every day/hour if pending, otherwise when
        #     rebooting due to eg missing power it won't save
        write_config(config_filename, state)

if (__name__ == "__main__"):
    main()


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

import calendar
import csv
# import pdb; pdb.set_trace()
import os

from argparse import ArgumentParser
from datetime import datetime
from datetime import date, timedelta

# ArgumentParser con una descripción de la aplicación
# (https://docs.python.org/2/library/argparse.html#argumentparser-objects)
parser = ArgumentParser(description='%(prog) generar archivo de texto')

# Argumento opcional. Si se pametriza, requiere acompañarlo de un valor
parser.add_argument('-mes', help='mes que desea procesar', type=int)

# Por último parsear los argumentos
args = parser.parse_args()

# obtenemos primera y ultima fecha del mes anterior
hoy = date.today()
prev = hoy.replace(day=1) - timedelta(days=1)

if args.mes:
    prev = datetime(prev.year, args.mes, 1)

inicioMes = datetime(prev.year, prev.month, 1)
finMes = datetime(prev.year, prev.month,
                  calendar.monthrange(prev.year, prev.month)[1])

# apertura del archivo ascii para escritura
f = open('3.CITI-Ventas.txt', 'w')
f2 = open('4.CITI-Ventas-Alicuotas.txt', 'w')

class Error(Exception):
    """Clase base para excepciones en el módulo."""
    pass

class ErrorRangoFecha(Error):
    """Excepción lanzada por errores en las entradas.

    Atributos:
        expresion -- expresión de entrada en la que ocurre el error
        mensaje -- explicación del error
    """

    def __init__(self, fecha, inicio, fin):
        self.fecha = fecha
        self.inicio = inicio
        self.fin = fin

def limpiar():
    if os.name == "posix":
        os.system("clear")
    elif os.name == ("ce", "nt", "dos"):
        os.system("cls")

def validar_fecha(valor):
    try:
        fecha = datetime.strptime(valor, '%d/%m/%Y')
        if fecha >= inicioMes and fecha <= finMes:
            return fecha.strftime('%Y%m%d')
        else:
            raise ErrorRangoFecha(valor,
                inicioMes.strftime('%d/%m/%Y'),
                finMes.strftime('%d/%m/%Y'))
    except ValueError:
        print('La fecha {} no es correcta'.format(valor))

def validar_comprobante(valor):
    switcher = {
        'FACA': '001',
        'FACB': '006',
        'FACC': '011',
        'LSGA': '090',
        'NCRA': '003',
        'NCRB': '008',
        'NCRC': '013',
        'NDEA': '002',
        'NDEB': '007',
        'NDEC': '012',
    }
    return switcher.get(valor, "F")

def validar_importe(valor):
    valor = valor.replace(',', '.')
    return float(valor)

def validar_total(valor):
    if isinstance(valor, float):
        total = valor
    else:
        total = validar_importe(valor)

    if total < 0:
        total = abs(total)
        valor = '-' + format(total, '.2f').replace(".", "").rjust(14, '0')
    else:
        total = abs(total)
        valor = format(total, '.2f').replace(".", "").rjust(15, '0')
    return valor

def validar_operacion(valor1, valor2):
    if valor1 == valor2:
        return 'N'
    else:
        return '0'

def validar_neto_gravado(gravado, no_gravado):
    if gravado == no_gravado:
        return validar_total(gravado)
    else:
        return validar_total('0')

def validar_alicuota_iva(iva, otro):
    iva = validar_importe(iva)
    otro = validar_importe(otro)
    if iva != 0:
        return '3'
    elif otro != 0:
        return '4'
    else:
        return '5'

def validar_iva(iva, otro):
    iva = validar_importe(iva)
    otro = validar_importe(otro)
    if iva != 0:
        return validar_total(iva)
    elif otro != 0:
        return validar_total(otro)
    else:
        return validar_total(0)

def imprimir_linea(reg):
    f.write(validar_fecha(reg['FECHA']) +
            validar_comprobante(reg['TIPOCOMPROB'] + reg['LETRA']) +
            reg['TERMINAL'].rjust(5, '0') +
            reg['NUMERO'].rjust(20, '0') +
            reg['NUMERO'].rjust(20, '0') +
            '80' +
            reg['CUIT'].replace('-', '').rjust(20, '0') +
            reg['NOMBRE'][0:30].ljust(30, ' ') +
            validar_total(reg['TOTAL']) +
            validar_total(reg['NOGRAVA']) +
            '0'.rjust(15, '0') +
            '0'.rjust(15, '0') +
            validar_total(reg['PERCIVA']) +
            '0'.rjust(15, '0') +
            '0'.rjust(15, '0') +
            validar_total(reg['IMPINT']) +
            'PES' +
            '0001000000' +
            '1' +
            validar_operacion(reg['NOGRAVA'], reg['TOTAL']) +
            '0'.rjust(15, '0') +
            validar_fecha(reg['FECHA'])
    )

def imprimir_linea_alicuota(reg):
    f2.write(validar_comprobante(reg['TIPOCOMPROB'] + reg['LETRA']) +
             reg['TERMINAL'].rjust(5, '0') +
             reg['NUMERO'].rjust(20, '0') +
             validar_neto_gravado(reg['GRAVADO'], reg['NOGRAVA']) +
             validar_alicuota_iva(reg['IVA21'], reg['OTROIVA']) +
             validar_iva(reg['IVA21'], reg['OTROIVA'])
    )

def lubre():
    with open('citi-ventas.csv') as csvarchivo:
        entrada = csv.DictReader(csvarchivo,
                                 delimiter=';',
                                 quoting=csv.QUOTE_NONE)
        for (contador, reg) in enumerate(entrada):
            try:
                if contador > 0:
                    # salto de línea en el archivo
                    # lo hago aquí para evitar el último salto de línea
                    f.write('\n')
                    f2.write('\n')
                imprimir_linea(reg)
                imprimir_linea_alicuota(reg)
            except BaseException:
                print('Error al procesar linea número ', contador + 2)
                raise

if __name__ == "__main__":
    try:
        limpiar()
        lubre()
        print('Archivo {} creado correctamente.'.format('3.CITI-Ventas y 4.CITI-Ventas-Alicuotas'))
    except ErrorRangoFecha as err:
        print('La fecha {} no pertenece al rango {}-{}'.format(err.fecha, err.inicio, err.fin))
    finally:
        f.close()
        f2.close()


"""
Wrappers for the "ScriptingBrige" framework on macOS 10.5 or later. This
framework provides an easy way to use the scripting functionality of
applications ("AppleScript") from Cocoa applications.

The functionality of this framework is comparable to that off "appscript",
although the latter is better tuned for use in Python applications and is
available on macOS 10.4 as well.

These wrappers don't include documentation, please check Apple's documentation
for information on how to use this framework and PyObjC's documentation
for general tips and tricks regarding the translation between Python
and (Objective-)C frameworks
"""
import os
import sys

sys.path.insert(0, os.path.dirname(__file__))

from pyobjc_setup import Extension, setup  # noqa: E402

VERSION = "10.1"

setup(
    name="pyobjc-framework-ScriptingBridge",
    description="Wrappers for the framework ScriptingBridge on macOS",
    min_os_level="10.5",
    packages=["ScriptingBridge"],
    ext_modules=[
        Extension(
            "ScriptingBridge._ScriptingBridge",
            ["Modules/_ScriptingBridge.m"],
            extra_link_args=["-framework", "ScriptingBridge"],
            py_limited_api=True,
            depends=[
                os.path.join("Modules", fn)
                for fn in os.listdir("Modules")
                if fn.startswith("_ScriptingBridge")
            ],
        )
    ],
    version=VERSION,
    install_requires=["pyobjc-core>=" + VERSION, "pyobjc-framework-Cocoa>=" + VERSION],
    long_description=__doc__,
    options={"bdist_wheel": {"py_limited_api": "cp36"}},
)


'''
 - 20년 가을학기 분산병렬 프로그래밍
 - 4장 스레드간 동기화
 - Lock을 사용해 counter 증감 프로그램 보완
 - 이호섭
'''

import threading
import time
import random

counter = 1                 # 공유 변수
lock = threading.Lock()     # Lock 객체 생성

def workerA():
    global counter
    lock.acquire()

    try:
        while counter < 1000:
            counter += 1
            print("Worker A is incrementing counter to {}".format(counter))
    finally:
        lock.release()

def workerB():
    global counter
    lock.acquire()

    try:
        while counter > -1000:
            counter -= 1
            print("Worker B is decrementing counter to {}".format(counter))
    finally:
        lock.release()

def main():
    t0 = time.time()
    thread1 = threading.Thread(target=workerA)
    thread2 = threading.Thread(target=workerB)

    thread1.start()
    thread2.start()

    thread1.join()
    thread2.join()

    t1 = time.time()

    print("Execution Time {}".format(t1-t0))

if __name__ == '__main__':
    main()


"""Tools for executing multiple recourse experiment runs in parallel.

Given a list of run configs and a python mainfile capable of executing them,
it partitions the run configs into batches and launches a subprocess to
execute each batch."""
import sys
import os

#  Append MRMC/. to the path to fix imports.
sys.path.append(os.path.join(os.getcwd(), ".."))

import tempfile
from typing import Sequence, Mapping, Any, Optional, Tuple
import subprocess
import json

import numpy as np
import pandas as pd

class ParallelRunner:
    """Given a python experiment mainfile and a set of run configs to execute
    it on, run multiple instances of the mainfile over the run configs in
    parallel and collect their results.

    The mainfile should accept accept the command --config command line
    argument, which is the path to a .json file of run configs.

    The config.json file is created by the ParallelRunner and contains
    a list of dictionaries mapping configuration variables to values. Each
    dictionary corresponds to one run.

    Attributes:
        experiment_mainfile_path: The python mainfile to run in parallel.
        final_results_dir: The directory to write the final results to.
        scratch_dir: The process-local directory to write temporary resutls to.
        num_processes: The number of processes to execute in parallel.
        recourse_method: The recourse method to use when constructing the
            batch config file.
        dataset_name: The name of the dataset to use when constructing the
            batch config file.
        model_type: The model to use when constructing the batch config file.
        split: The data split to use when constructing the batch config file.
        use_slurm: Whether to use the SLURM distributed job scheduler.
        rng: The random generator used to distribute runs across processes.
            It does not effect the results, but may effect load balancing.
        verbose: Whether to print out progress."""

    def __init__(
        self,
        experiment_mainfile_path: str,
        final_results_dir: str,
        num_processes: int,
        recourse_method: str,
        dataset_name: str,
        model_type: str,
        split: str,
        use_slurm: bool = True,
        random_seed: Optional[int] = None,
        scratch_dir: Optional[str] = None,
        verbose: bool = False,
    ):
        """Creates a new ParallelRunner.

        Args:
            experiment_mainfile_path: The python mainfile to run in parallel.
            final_results_dir: The directory to write the final results to.
            num_processes: The number of child processes to run in parallel.
            recourse_method: The recourse method to use when constructing the
                batch config file.
            dataset_name: The name of the dataset to use when constructing the
                batch config file.
            model_type: The model to use when constructing the batch config
                file.
            split: The data split to use when constructing the batch config
                file.
            use_slurm: Whether to use the SLURM job scheduler. This should be
                True if you are executing this via the sbatch command.
            random_seed: The random seed to use when assigning runs to
                processes. This has no impact on experiment results.
            scratch_dir: The directory to use for storing temporary results.
        """
        self.experiment_mainfile_path = experiment_mainfile_path
        self.results_dir = final_results_dir
        self.scratch_dir = scratch_dir
        self.num_processes = num_processes
        self.use_slurm = use_slurm
        self.recourse_method = recourse_method
        self.dataset_name = dataset_name
        self.model_type = model_type
        self.split = split
        if not random_seed:
            random_seed = np.random.randint(0, 100000)
        self.rng = np.random.default_rng(random_seed)
        self.verbose = verbose

    def execute_runs(
        self, run_configs: Sequence[Mapping[str, Any]]
    ) -> Mapping[str, pd.DataFrame]:
        """Partitions a set of runs into batches and executes them in parallel.

        Each run_config is a dictionary whose format is specified by the
        mainfile which will be executed in parallel.

        Args:
            run_configs: The run configs to partition and execute in parallel.

        Returns:
            A mapping from filename to result DataFrame aggregated across all
            runs."""
        partitioned_run_configs = self.partition_runs(run_configs)
        scratch_dir = ""
        if self.scratch_dir:
            # ensure the scratch_dir is well-formatted.
            scratch_dir = os.path.join(self.scratch_dir, "")
        with tempfile.TemporaryDirectory(prefix=scratch_dir) as scratch_dir:
            scratch_results_dir = self.run_processes(
                partitioned_run_configs, scratch_dir
            )
            return self.collect_results(scratch_results_dir)

    def partition_runs(
        self, run_configs: Sequence[Mapping[str, Any]]
    ) -> Sequence[Sequence[Mapping[str, Any]]]:
        """Randomly partitions a list of run configs into batches to be
        executed in parallel."""
        shuffled_run_indices = np.arange(0, len(run_configs))
        self.rng.shuffle(shuffled_run_indices)

        # initialize a list of empty batches, one for each process
        partitioned_run_configs = [[] for _ in range(self.num_processes)]

        # iterate through and assign run configs into batches configs
        partition_index = 0
        for run_index in shuffled_run_indices:
            partitioned_run_configs[partition_index].append(
                run_configs[run_index]
            )
            partition_index = (partition_index + 1) % self.num_processes

        return partitioned_run_configs

    def run_processes(
        self,
        partitioned_run_configs: Sequence[Sequence[Mapping[str, Any]]],
        scratch_directory: str,
    ) -> str:
        """Runs many processes in parallel where each process is given a
        batch of run configs.

        Each process takes its batch from one of the partitions in the
        partitioned_run_configs argument.

        Args:
            partitioned_run_configs: The list of run config batches to be used
                by each process.
            scratch_directory: The temporary directory each process uses for
                input and output.

        Returns:
            The file directory where .csv results are written."""
        running_processes = self._launch_processes(
            partitioned_run_configs, scratch_directory
        )

        return self._monitor_processes(running_processes, scratch_directory)

    def collect_results(
        self, scratch_results_directory: str
    ) -> Mapping[str, pd.DataFrame]:
        """Loads and returns the final aggregated results from disk."""
        result_dfs = {}
        for result_name in os.listdir(scratch_results_directory):
            result_path = os.path.join(scratch_results_directory, result_name)
            stripped_result_name = os.path.splitext(result_name)[0]
            result_dfs[stripped_result_name] = pd.read_csv(result_path)
        return result_dfs

    def _launch_processes(
        self,
        partitioned_run_configs: Sequence[Sequence[Mapping[str, Any]]],
        scratch_directory: str,
    ) -> Mapping[subprocess.Popen, str]:
        """Asynchronously launches a subprocess for each of the run configs.

        Args:
            partitioned_run_configs: The batches of run configs to use in each
                subprocess.
            scratch_directory: A directory to use write the input and output
                of each subprocess to.

        Returns:
            A mapping from Process objects to the subdirectories they will
            write their results to."""
        running_processes: Mapping[subprocess.Popen, str] = {}
        for i, run_configs in enumerate(partitioned_run_configs):
            process_io_directory = os.path.join(
                scratch_directory, f"scratch_work_{i}"
            )
            os.mkdir(process_io_directory)
            p, process_results_directory = self._launch_process(
                run_configs, process_io_directory
            )
            running_processes[p] = process_results_directory
        return running_processes

    def _launch_process(
        self,
        run_configs: Sequence[Mapping[str, Any]],
        process_io_directory: str,
    ) -> Tuple[subprocess.Popen, str]:
        """Asynchronously launches a subprocess to execute a batch of
        run_configs.

        Args:
            run_configs: The run configs to execute.
            process_io_directory: The directory to use for writing the process
                input and output.

        Returns:
            A tuple of created process and final results path."""
        process_results_directory = os.path.join(
            process_io_directory, "results"
        )
        os.mkdir(process_results_directory)

        process_config_filename = self._write_process_config(
            process_io_directory, run_configs
        )

        process_cmd = self._format_subprocess_command(
            process_config_filename, process_results_directory
        )

        return (
            subprocess.Popen([process_cmd], shell=True),
            process_results_directory,
        )

    def _write_process_config(
        self,
        process_io_directory: str,
        run_configs: Sequence[Mapping[str, Any]],
    ) -> str:
        """Writes the run configs to a config file for the subprocess to read
        and returns the config file path."""
        process_config = {
            "run_configs": run_configs,
            "experiment_name": "experiment_scratch_work",
            "recourse_method": self.recourse_method,
            "dataset_name": self.dataset_name,
            "model_type": self.model_type,
            "split": self.split,
        }
        process_config_filename = os.path.join(
            process_io_directory, "config.json"
        )
        with open(process_config_filename, "w") as f:
            json.dump(process_config, f)
        return process_config_filename

    def _format_subprocess_command(
        self, process_config_filename: str, results_dir: str
    ) -> str:
        """Formats the command to launch a subprocess.

        If using SLURM, launches the subprocess as an srun step."""
        process_cmd = ""
        if self.use_slurm:
            process_cmd = "srun -N 1 -n 1 --exclusive "
        process_cmd += (
            f"python {self.experiment_mainfile_path} "
            f"--config {process_config_filename} --results_dir {results_dir} "
            "--only_csv"
        )
        return process_cmd

    def _monitor_processes(
        self,
        running_processes: Mapping[subprocess.Popen, str],
        scratch_directory: str,
    ) -> str:
        """Monitors the running subprocesses, aggregating their results as they
        terminate."""
        # Prepare the aggregated results directory
        aggregated_results_directory = os.path.join(
            scratch_directory, "results"
        )
        os.mkdir(aggregated_results_directory)

        if self.verbose:
            print(
                f"Waiting for {len(running_processes)} processes to finish..."
            )

        while running_processes:
            # Check for terminated processes and aggregate their results
            terminated_processes = []
            for process in running_processes:
                if process.poll() is not None:
                    if process.poll() != 0:
                        raise RuntimeError("One of the processes failed!")
                    self._aggregate_results(
                        aggregated_results_directory,
                        running_processes[process],
                    )
                    terminated_processes.append(process)
            for process in terminated_processes:
                del running_processes[process]
                if self.verbose:
                    remaining_procs = len(running_processes)
                    print(
                        f"Waiting for {remaining_procs} processes to finish..."
                    )
        return aggregated_results_directory

    def _aggregate_results(
        self, aggregated_results_directory: str, new_results_directory: str
    ) -> None:
        """Concatenates the newly generated results with the previous results.

        The newly merged results are saved over the original aggregated
        results."""
        for result in os.listdir(new_results_directory):
            result_path = os.path.join(new_results_directory, result)
            if not os.path.isfile(result_path):
                raise RuntimeError(
                    (
                        f"Result aggregation found directory "
                        f"{result} but only supports .csv files."
                    )
                )
            if not result.endswith(".csv"):
                raise RuntimeError(
                    (
                        f"Result aggregation found file {result} but only "
                        "supports .csv files."
                    )
                )
            new_results = pd.read_csv(result_path)
            aggregated_result_path = os.path.join(
                aggregated_results_directory, result
            )
            if not os.path.exists(aggregated_result_path):
                aggregated_results = new_results
            else:
                old_results = pd.read_csv(aggregated_result_path)
                aggregated_results = pd.concat(
                    [old_results, new_results]
                ).reset_index(drop=True)
            aggregated_results.to_csv(aggregated_result_path, index=False)


#!/usr/bin/python
# __init__.py
# -*- coding: utf-8 -*-

# import library in other folder
import os
import logging.handlers

from . import filas

from pathlib import Path
from datetime import datetime

__all__ = (
    'util_logger'
)
__version__ = '0.1.0'

def remove_message(message_path = None):
    os.remove(message_path) if Path(message_path).exists() else None
    return None

def input_message(path = None, message = None, settime=True):
    now = str(datetime.now())[0:19]
    f = open(path,'a')
    if settime == True:
        f.write(now+' '+message+'\n')
    if settime == False:
        f.write(message+'\n')
    f.close()
    return None

def makeLogger(logfile):
    # print('start logger rootpath: '+ rootpath)
    loggerger = logging.getLogger(__name__)
    # to set log location :  filename='./log/worklogger.log'
    #loggerger.setLevel(level=logging.INFO)
    loggerger.setLevel(level=logging.DEBUG)
    file_max_bytes = 10 * 1024 * 1024

    fileHandler = logging.handlers.RotatingFileHandler(filename=logfile, maxBytes=file_max_bytes, backupCount=10)

    # formmater setting
    formatter = logging.Formatter('[%(levelname)s|%(filename)s:%(lineno)s] %(asctime)s > %(message)s')

    # set value fileHandler and StreamHandler
    fileHandler = logging.FileHandler(filename=logfile,encoding='utf-8')
    streamHandler = logging.StreamHandler()

    # set handler and fommater
    fileHandler.setFormatter(formatter)
    streamHandler.setFormatter(formatter)

    # start logging hander
    loggerger.addHandler(fileHandler)
    loggerger.addHandler(streamHandler)
    loggerger.info("start dev logging in the path : %s" %logfile)
    return loggerger

log_file_name = '%s_%s.log' % (filas.executed_file_name, filas.now_date_time)
log_folder_name = os.path.join('_logs')
log_full_name = os.path.join(log_folder_name, log_file_name)
logger = makeLogger(log_full_name)


class HTMLstring:

    def __init__(self, head: str = "", body: str = ""):
        self.head = head
        self.body = body

    def __str__(self):
        string = f"""<html>
    <head>
        {self.head}
    </head>
    <body>
        {self.body}
    </body>
</html>"""
        return string

    def add_text(self, text: str, header: int = 0):
        if header != 0:
            text = f"<h{header}> {text} </h{header}>"
        else:
            text = f"<p> {text} </p>"
        text += "\n"
        self.body += text

    def add_image(self, filepath: str, alt: str, start=False, end=False):
        text = f'<img src="{filepath}" alt="{alt}">'
        if start:
            text = "<p> " + text
        if end:
            text = text + "<p>"
        self.body += text

    def save(self, filepath):
        file = open(filepath, 'w')
        file.write(str(self))
        file.close()


__author__ = "Maximus Mutschler"
__version__ = "1.0"
__email__ = "maximus.mutschler@uni-tuebingen.de"

import numpy as np
import tensorflow as tf

from TensorFlow.data_augment import cifar10_augment

def get_cifar10_iterator(train_data_size=40000, batch_size=100):
    """
     Creates an iterator that iterates over CIFAR-images and the corresponding labels.
     From all pixels the pixel_mean is substracted. No shuffling is done. The iterator repeats forever.
     Returns:
     iterator, inference_mode_var (must hold an element of type :class:`~code_.abstract_net_class.inferenceMode` )
     ,train_data_size, eval_data_Size, test_data_size
    """
    with tf.variable_scope("Iterator", reuse=tf.AUTO_REUSE):
        with tf.device("device:CPU:0"):
            (x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()

            channels = 3
            spatial_resolution = 32
            label_size = 1

            pixel_mean = np.mean(x_train, axis=0)

            x_eval = x_train[train_data_size:, ...]
            x_train = x_train[0:train_data_size, ...]
            y_eval = y_train[train_data_size:, ...]
            y_train = y_train[0:train_data_size, ...]

            eval_data_size = len(y_eval)
            test_data_size = len(y_test)

            x_train = x_train.reshape(train_data_size, spatial_resolution, spatial_resolution, channels).astype('uint8')
            x_eval = x_eval.reshape(eval_data_size, spatial_resolution, spatial_resolution, channels).astype('uint8')
            x_test = x_test.reshape(test_data_size, spatial_resolution, spatial_resolution, channels).astype('uint8')
            y_train = y_train.reshape(train_data_size, label_size).astype('uint8')
            y_eval = y_eval.reshape(eval_data_size, label_size).astype('uint8')
            y_test = y_test.reshape(test_data_size, label_size).astype('uint8')

            num_aug_threads = 8

            pixel_mean = np.mean(x_train, axis=0)
            pixel_std = np.std(x_train, axis=0)

            def mapf_normalize(x, y): return (x - pixel_mean) / pixel_std, y

            def mapf_cast(x, y): return tf.cast(x, tf.float32), y

            test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test))
            test_dataset = test_dataset.map(mapf_cast, num_parallel_calls=num_aug_threads)
            test_dataset = test_dataset.map(mapf_normalize, num_parallel_calls=num_aug_threads)
            test_dataset = test_dataset.repeat()  # repeats for ever
            test_dataset = test_dataset.batch(batch_size)
            test_dataset = test_dataset.prefetch(8)

            eval_dataset = tf.data.Dataset.from_tensor_slices((x_eval, y_eval))
            eval_dataset = eval_dataset.map(mapf_cast, num_parallel_calls=num_aug_threads)
            eval_dataset = eval_dataset.map(mapf_normalize, num_parallel_calls=num_aug_threads)
            eval_dataset = eval_dataset.repeat()  # repeats for ever
            eval_dataset = eval_dataset.batch(batch_size)
            eval_dataset = eval_dataset.prefetch(8)

            train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
            train_dataset = train_dataset.map(mapf_cast, num_parallel_calls=num_aug_threads)
            train_dataset = train_dataset.map(mapf_normalize, num_parallel_calls=num_aug_threads)
            train_dataset = train_dataset.map(cifar10_augment, num_parallel_calls=num_aug_threads)
            train_dataset = train_dataset.repeat()
            train_dataset = train_dataset.batch(batch_size)
            train_dataset = train_dataset.prefetch(8)

            sess = tf.get_default_session()
            iterator, inference_mode_var = _create_handle_iterator(train_dataset, eval_dataset, test_dataset, sess)

            sess.run(tf.global_variables_initializer())
            print("successfully loaded CIFAR data")

            return iterator, inference_mode_var

def _create_handle_iterator(train_dataset, eval_dataset, test_dataset, sess):
    """
    Creates iterator that is dependent on the value of inference_mode_var. Depending on its inference_mode_var's value
    data is loaded either from the train or eval or test dataset.
    :return:  iterator, inference_mode_var
    """
    train_iterator_handle = sess.run((train_dataset.make_one_shot_iterator().string_handle()))
    eval_iterator_handle = sess.run(eval_dataset.make_one_shot_iterator().string_handle())
    test_iterator_handle = sess.run(test_dataset.make_one_shot_iterator().string_handle())

    inference_mode_var = tf.get_variable("mode", shape=(),
                                         initializer=tf.constant_initializer(value=InferenceMode.TRAIN,
                                                                             dtype=tf.uint8),
                                         trainable=False, dtype=tf.uint8)
    cases = tf.case(
        [(tf.equal(inference_mode_var, tf.cast(InferenceMode.TRAIN, tf.uint8)),
          lambda: train_iterator_handle),
         (
             tf.equal(inference_mode_var, tf.cast(InferenceMode.EVAL, tf.uint8)), lambda: eval_iterator_handle),
         (
             tf.equal(inference_mode_var, tf.cast(InferenceMode.TEST, tf.uint8)), lambda: test_iterator_handle)]
    )
    iterator = tf.data.Iterator.from_string_handle(cases,
                                                   train_dataset.output_types,
                                                   train_dataset.output_shapes)
    return iterator, inference_mode_var

class InferenceMode:
    """
    Has to be used to define the inference mode of a network model
    """
    TRAIN = 0
    EVAL = 1
    TEST = 2
    PREDICT = 3


import math

from math import factorial
import sys
sys.float_info.max
def  bi_sum(n, k):
   # nplus = n+1
   c = 0
   for i in range(k, n+1):
         c+=(factorial(n))//(factorial(i)*factorial(n-i))
   print(c%1000000)

bi_sum(1947, 987)

Combinatorics Problems/Introduction to Alternative Splicing.py


# import the get_string function from the cs50 library
from cs50 import get_string

# Main method
def main():
    # prompt the user to input some text
    text = get_string("Text: ")
    letters, words, sentences = count_elements(text)
    L = letters / words * 100
    S = sentences / words * 100
    index = compute_index(L, S)
    grade_level = get_grade_level(index)
    print(grade_level)

# method to count letters, words, and sentences in the given text
def count_elements(text):
    letters = sum(1 for char in text if char.isalpha())
    words = len(text.split())
    sentences = sum(1 for char in text if char in [".", "!", "?"])
    return letters, words, sentences

# method to compute the Coleman-Liau index
def compute_index(L, S):
    return 0.0588 * L - 0.296 * S - 15.8

# method to get the corresponding grade level
def get_grade_level(index):
    rounded_index = round(index)
    if rounded_index >= 16:
        return "Grade 16+"
    elif rounded_index < 1:
        return "Before Grade 1"
    else:
        return f"Grade {rounded_index}"

# check if the script is being run as the main program
if __name__ == "__main__":
    # call the main method to start the program
    main()


import glob
import os
from tensorflow.keras.models import load_model
import cv2
import numpy as np
import sys
import shutil

class Classify(object):
    # 指定存储的网络模型路径
    def __init__(self, s_modelpath):
        # 训练时独热编码所对应的类别 其中 污渍+ 代表的类别为 污渍or晶点
        self.label_dict = {0: "折痕", 1: "污渍+", 2: "破洞", 3: "错版"}
        self.base_model = load_model(s_modelpath)

    def judge(self, srcimg, orilabel):
        # 读取目录
        if os.path.isdir(srcimg):
            test_accuracy = self.all_image_accuracy(srcimg, orilabel)
            return test_accuracy
        # 读取图片
        else:
            try:
                image1 = cv2.imdecode(np.fromfile(srcimg, dtype=np.uint8), 1)
            except Exception as e:
                os.remove(srcimg)
                print("can not decode this image")
                sys.exit()
            result = self.judge_single_image(srcimg, orilabel)
            return result

    # 单张图片分类
    def judge_single_image(self, image1, orilabel):
        image = cv2.resize(image1, (128, 128))
        # 预测类别
        class_index = self.base_model.predict_classes(image.reshape(-1, 128, 128, 3) / 255.0)
        class_index = np.argmax(class_index, axis=1)
        # 数字标签判断
        if isinstance(orilabel, int):
            if orilabel == class_index:
                # print("Classified correctly")
                return True
            else:
                # print("wrong")
                return False
        # 字符类别判断
        elif isinstance(orilabel, str):
            if self.label_dict[class_index] == orilabel:
                # print("Classified correctly")
                return True
            else:
                # print("wrong")
                return False
        else:
            print("输入标签类型错误")
            sys.exit()

    # 用来测试某一个同类别目录下的准确度
    def all_image_accuracy(self, one_dir, orilabel):
        # 这里是 bmp 图片
        image_list = glob.glob(os.path.join(one_dir, "*.bmp"))
        # 实际测试的图片数量(有部分图片不能读取，防止对准确度进行干扰)  todo:是否需要加入时间模块
        test_image_number = 0
        # 分类准确的图片数量
        class_correct_number = 0
        for item in image_list:
            try:
                image1 = cv2.imdecode(np.fromfile(item, dtype=np.uint8), 1)
            except Exception as e:
                os.remove(item)
                continue
            result = self.judge_single_image(image1, orilabel)
            test_image_number += 1
            if result:
                class_correct_number += 1
        test_accuracy = class_correct_number / test_image_number
        print("the test accuracy amost %.2f" % test_accuracy)
        return test_accuracy

    # 识别图片
    def recognized(self, srcimg):
        # 文件夹
        if os.path.isdir(srcimg):
            # 这里是 bmp 图片
            image_list = glob.glob(os.path.join(srcimg, "*.bmp"))
            image_class_dict = dict()
            for item in image_list:
                try:
                    image1 = cv2.imdecode(np.fromfile(item, dtype=np.uint8), 1)
                except Exception as e:
                    os.remove(item)
                    continue
                image = cv2.resize(image1, (128, 128))
                # 预测类别
                class_index = self.base_model.predict_classes(image.reshape(-1, 128, 128, 3) / 255.0)
                class_index = np.argmax(class_index, axis=1)
                image_class_dict[item] = self.label_dict[class_index]
            # 返回字典 {图片：标签}  todo:是否需要将不同标签的图片放入特定文件夹
            return image_class_dict

        # 单张图片处理方式
        else:
            try:
                image1 = cv2.imdecode(np.fromfile(srcimg, dtype=np.uint8), 1)
            except Exception as e:
                print("can not decode this image")
                sys.exit()
            # resize图片至训练时大小, demo中图片为 128 * 128
            image = cv2.resize(image1, (128, 128))
            # 预测类别
            class_index = self.base_model.predict_classes(image.reshape(-1, 128, 128, 3) / 255.0)
            class_index = np.argmax(class_index, axis=1)
            # 返回类别名
            return self.label_dict[class_index]

    def continue_train(self):
        # 基于现有demo继续训练
        pass


from aws_xray_sdk.core import patch_all
from logger import configure_logger

import transform_utils
import response_builder

from managers.pipeline_manager import PipelineManager

patch_all()

LOGGER = configure_logger(__name__)

def get(event, _context):
    """ Returns the pipelines belonging to the authenticated user.
    It uses filter_stacks() to filter the CloudFormation stacks with type
    'pipeline' and owner belonging to the same Cognito group as the user
    is logged in as.
    """
    pm = PipelineManager(event)

    data = {}
    data['pipelines'] = []

    keys = ['StackName', 'Parameters', 'CreationTime', 'LastUpdatedTime']

    try:
        stacks = pm.list_stacks(keys)
    except Exception as ex:
        return response_builder.error('Unknown Error: {}'.format(ex))
    else:
        for stack in stacks:
            name = transform_utils.remove_prefix(
                stack['StackName'])
            params = transform_utils.kv_to_dict(
                stack['Parameters'],
                'ParameterKey',
                'ParameterValue')
            data['pipelines'].append(
                {
                    'name': transform_utils.remove_prefix(name),
                    'created_at': stack['CreationTime'],
                    'updated_at': stack['LastUpdatedTime'],
                    'app_dev': transform_utils.remove_prefix(params['ServiceDev']),
                    'app_test': transform_utils.remove_prefix(params['ServiceTest']),
                    'app': transform_utils.remove_prefix(params['ServiceProd'])
                })

        return response_builder.success(data)


# coding: utf-8
# This Pipeline is a simple example
import sys

import luigi

import pandas as pd

from sklearn.model_selection import train_test_split

from sklearn.externals import joblib

from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier

from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

from sklearn.pipeline import make_pipeline

class IrisData(luigi.ExternalTask):
    def output(self):
        return luigi.LocalTarget("./data/iris.csv")

class TrainTestSplit(luigi.Task):
    def requires(self):
        return IrisData()

    def output(self):
        return {
            'X_train': luigi.LocalTarget("./data/X_train.csv"),
            'X_test': luigi.LocalTarget("./data/X_test.csv"),
            'y_train': luigi.LocalTarget("./data/y_train.csv"),
            'y_test': luigi.LocalTarget("./data/y_test.csv")
        }

    def run(self):
        cols = [
            "Sepal_Length",
            "Sepal_Width",
            "Petal_Length",
            "Petal_Width",
            "Species"
        ]

        features = [
            "Sepal_Length",
            "Sepal_Width",
            "Petal_Length",
            "Petal_Width"
        ]

        iris_df = pd.read_csv(self.input().path, names=cols)

        X = iris_df[features]
        y = iris_df["Species"]

        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1234)

        X_train.to_csv(self.output()['X_train'].path, index = False, header = True)
        X_test.to_csv(self.output()['X_test'].path, index = False, header = True)
        y_train.to_csv(self.output()['y_train'].path, index = False, header = True)
        y_test.to_csv(self.output()['y_test'].path, index = False, header = True)

class TrainModel(luigi.Task):
    algorithm = luigi.Parameter()

    def requires(self):
        return TrainTestSplit()

    def output(self):
        return {
            'model': luigi.LocalTarget("./models/" + self.algorithm + "_model.pkl"),
            'metadata': luigi.LocalTarget("./models/" + self.algorithm + "_metadata.txt")
        }

    def run(self):
        X_train = pd.read_csv(self.input()['X_train'].path)
        y_train = pd.read_csv(self.input()['y_train'].path)

        if self.algorithm == 'SVC':
            model = SVC(kernel='linear', C=1.0)
        elif self.algorithm == 'RF':
            model = RandomForestClassifier()
        else:
            lr = LogisticRegression(solver='newton-cg',
                                    multi_class='multinomial',
                                    random_state=1)

            model = make_pipeline(StandardScaler(), lr)

        model.fit(X_train, y_train)

        joblib.dump(model, self.output()['model'].path)

        with self.output()['metadata'].open('w') as meta:
            meta.write(str(model))

class IrisPipeline(luigi.WrapperTask):
    def requires(self):
        yield TrainModel(algorithm='SVC')
        yield TrainModel(algorithm='RF')
        yield TrainModel(algorithm='LR')

if __name__ == "__main__":
    luigi_args = ['IrisPipeline',
                  '--local-scheduler',
                  '--workers', '4']
    luigi.run(luigi_args)


from . import api
from flask import render_template, jsonify, request
import http
import os
import sys
CURPATH = os.path.dirname(os.path.abspath(__file__))  # /project/web/api/
PROJPATH = os.path.dirname(os.path.dirname(CURPATH))  # /project/
INCPATH = os.path.join(PROJPATH, 'Include')  # /project/Include
sys.path.extend([CURPATH, PROJPATH, INCPATH])
sys.path = list(set(sys.path))
from Include import get_home

@api.route('/packet/logger', methods=['GET', 'POST'])
def packet_logger():
    return render_template('packet_log.html')

@api.route('/packet/logger/update', methods=['POST'])
def packet_logger_update():
    home = get_home()
    p1 = '
'.join([' '.join(['%02X' % y for y in x]) for x in home.packets_energy[::-1]])
    p2 = '
'.join([' '.join(['%02X' % y for y in x]) for x in home.packets_control[::-1]])
    p3 = '
'.join([' '.join(['%02X' % y for y in x]) for x in home.packets_smart_recv[::-1]])
    return jsonify({'energy': p1, 'control': p2, 'smart_recv': p3})

@api.route('/packet/logger/clear/<target>', methods=['POST'])
def packet_log_clear(target):
    home = get_home()
    if target == 'energy':
        home.packets_energy.clear()
    elif target == 'control':
        home.packets_control.clear()
    elif target == 'smart_recv':
        home.packets_smart_recv.clear()
    return '', http.HTTPStatus.NO_CONTENT

@api.route('/packet/logger/<device>/enable/<target>', methods=['POST'])
def packet_log_enable(device, target):
    home = get_home()
    req = request.get_data().decode(encoding='utf-8')
    value = int(req[6:].strip()) if 'value=' in req else 1
    if device == 'energy':
        if target == '31':
            home.enable_log_energy_31 = bool(value)
        elif target == '41':
            home.enable_log_energy_41 = bool(value)
        elif target == '42':
            home.enable_log_energy_42 = bool(value)
        elif target == 'D1':
            home.enable_log_energy_d1 = bool(value)
        elif target == 'room1':
            home.enable_log_energy_room_1 = bool(value)
        elif target == 'room2':
            home.enable_log_energy_room_2 = bool(value)
        elif target == 'room3':
            home.enable_log_energy_room_3 = bool(value)
        home.packets_energy.clear()
    elif device == 'control':
        if target == '28':
            home.enable_log_control_28 = bool(value)
        elif target == '31':
            home.enable_log_control_31 = bool(value)
        elif target == '61':
            home.enable_log_control_61 = bool(value)
        home.packets_control.clear()
    return '', http.HTTPStatus.NO_CONTENT


import spacy
nlp = spacy.load('en')

# doc = nlp("My daughter is at the party")
# print([(ch.text, ch.root.dep_) for ch in doc.noun_chunks])

# doc = nlp("Where is my daughter?")
# print([(ch.text, ch.dep_) for ch in doc])

def my_to_your(message):
    # Change my --> your
    ms1 = message.replace(" my "," your ")
    ms2 = ms1.replace("My ","Your ")
    ms3 = ms2.replace(" I ", " you ")
    ms4 = ms3.replace("I ","You ")
    ms5 = ms4.replace("my","your")
    return ms5

def find_keyword(message):
    doc = nlp(message)
    lst = [(ch.text, ch.root.dep_) for ch in doc.noun_chunks]
    has_dobj = False
    for (text,dep) in lst:
        if "dobj" in dep:
            has_dobj = True
            return text
    if not has_dobj:
        for (text,dep) in lst:
            if "nsubj" in dep:
                return text
    else:
        return "error"


import cv2
import cv2.typing
from typing import Optional, Sequence

from filter.OverlayMarkers import OverlayMarkers
from config.CalibrationConfig import CalibrationConfigLoader

class CalibrationSession:
    _board: cv2.aruco.CharucoBoard
    _camera_size: Optional[cv2.typing.Size] = None
    _detector: cv2.aruco.CharucoDetector
    _img_points: Sequence[cv2.typing.MatLike] = []
    _obj_points: Sequence[cv2.typing.MatLike] = []
    _overlay_markers = OverlayMarkers()

    def __init__(self, squaresX: int, squaresY: int, squareLengthMeters: float, markerLengthMeters: float) -> None:
        self._board = cv2.aruco.CharucoBoard((squaresX, squaresY), squareLengthMeters, markerLengthMeters, cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_5X5_1000))

        self._detector = cv2.aruco.CharucoDetector(
            self._board,
            cv2.aruco.CharucoParameters(),
            cv2.aruco.DetectorParameters(),
            cv2.aruco.RefineParameters()
        )

    def intake_frame(self, capture: cv2.typing.MatLike, save: bool) -> None:
        if self._camera_size == None:
            self._camera_size = (capture.shape[0], capture.shape[1])

        charuco_corners, charuco_ids, marker_corners, marker_ids = self._detector.detectBoard(capture)
        self._overlay_markers.add_charuco_detection(capture, charuco_corners, charuco_ids, marker_corners, marker_ids)

        if save and len(marker_corners) > 0 and len(marker_ids) > 0:
            obj_points, image_points = self._board.matchImagePoints(charuco_corners, charuco_ids) # type: ignore
            self._obj_points.append(obj_points) # type: ignore
            self._img_points.append(image_points) # type: ignore
            print("Took calibration snap")

    def save_to_file(self, calibration_config_loader: CalibrationConfigLoader) -> None:
        if len(self._img_points) == 0 or len(self._obj_points) == 0:
            print("Unable to calibrate: no data")
            return

        calibration_config_loader.remove_config()

        if self._camera_size == None:
            print("Unable to calibrate: cannot determine camera size")
            return

        rms, distortion_matrix, distortion_coefficients, _, _ = cv2.calibrateCamera(self._obj_points, self._img_points, self._camera_size, None, None) # type: ignore

        if rms:
            calibration_config_loader.write(distortion_matrix, distortion_coefficients) # type: ignore
            print("Finished calibration")
        else:
            print("Calibration failed")


def competitive_matching(sorted_list):
    aligned=[]
    source=[]
    target=[]
    for pair in sorted_list.keys():
        if pair[0] not in source and pair[1] not in target:
            aligned.append(pair)
            source.append(pair[0])
            target.append(pair[1])
    return aligned


import sys

stack1 = list(input())
N = int(input())

stack2 = []

for i in range(N):
    command = sys.stdin.readline().split()
    if command[0] == 'B':
        if len(stack1) != 0:
            stack1.pop()
    elif command[0] == 'L':
        if len(stack1) != 0:
            stack2.append(stack1.pop())
    elif command[0] == 'D':
        if len(stack2) != 0:
            stack1.append(stack2.pop())
    else:
        stack1.append(command[-1])

print(''.join(stack1 + list(reversed(stack2))))


#! /usr/bin/env python
# -*- coding: UTF-8 -*-
# Author : <github.com/tintinweb>

from local import LocalPath
from archive import ArchivePatool
Archive = ArchivePatool  # lets use PaTool
import os
import commands
from utils.filesystem import TempFile
import logging

logger = logging.getLogger(__name__)

class Jar(LocalPath):
    def __init__(self, source, destination=None):
        self.uri = source
        if not destination:
            self.tmp_local_file = TempFile()
            self.tmp_local_file.mkdirectory()
            destination = str(self.tmp_local_file)

        source = self._fetch(source, destination)
        LocalPath.__init__(self, source)

    def _fetch(self, source, destination):
        commands.JdCli.decompile(source, destination)
        # cmd = [Config.PATH_JAR2SRC, '--outputDir', destination, source]
        # logger.debug(cmd)
        # ret = subprocess.call(cmd)
        # if ret != 0:
        #    raise Exception("command failed")
        return destination

class ApplicationDotNet(LocalPath):
    def __init__(self, source, destination=None):
        self.uri = source
        if not destination:
            self.tmp_local_file = TempFile()
            self.tmp_local_file.mkdirectory()
            destination = str(self.tmp_local_file)

        source = self._fetch(source, destination)
        LocalPath.__init__(self, source)

    def _fetch(self, source, destination):
        commands.JustDecompileDotNet.decompile(source, destination)
        return destination

class Apk(LocalPath):
    def __init__(self, source):
        self.uri = source
        self.tmp_local_file = TempFile()
        self.tmp_local_file.mkdirectory()

        source = self._fetch(source, str(self.tmp_local_file))
        LocalPath.__init__(self, source)

    def _fetch(self, source, destination):
        '''
        1) create tempfolder
        2) unpack .apk to tempfolder .apk
        3) convert .apk to tempfolder .jar
        4) decompile .jar to tempfolder .java

        dex2jar -o <output_jar> <input_apk>

        '''
        path, fname = os.path.split(source)

        destination_jar = os.path.join(destination, fname + ".jar")
        logger.debug("destination: %s" % destination_jar)
        # 1) convert .apk to .jar
        commands.Dex2Jar.decompile(source, destination_jar)
        # cmd = [Config.PATH_DEX2JAR, '-o', destination_jar, source]
        # logger.debug(cmd)
        # ret = subprocess.call(cmd)
        # if ret != 0:
        #    raise Exception("command failed")

        # 2) unpack .apk
        destination_dir_apk = os.path.join(destination, '.apk')
        os.makedirs(destination_dir_apk)
        logger.debug("unpack .apk to %s" % destination_dir_apk)
        apk_unpacked = Archive(source, destination_dir_apk)
        # 3) decompile .jar
        destination_dir_source = os.path.join(destination, '.source')
        os.makedirs(destination_dir_source)
        logger.debug("decompile .jar to %s" % destination_dir_source)
        jar_decompiled = Jar(destination_jar, destination_dir_source)

        logger.debug("remove .jar - we do not need it anymore")
        os.unlink(destination_jar)
        return destination

class Application32Bits(LocalPath):  # ida32
    # https://www.hex-rays.com/products/ida/support/idadoc/417.shtml
    """
    https://www.hex-rays.com/products/ida/support/idadoc/417.shtml

    idc.wait()
    idaapi.decompile_many(outfile, None, flags=0xffffffff)

    see idaallthethings
    + https://www.hex-rays.com/products/ida/support/idadoc/417.shtml
    1) get os type (windows,linux) (idaw vs idal)
    2) idaw -B -S"myscript.idc argument1 \"argument 2\" argument3"

    idaw|idal -B -S"ida-allthethings.py" "C:\_tmp\telnet.exe"

    https://www.hex-rays.com/products/decompiler/manual/batch.shtml
    """

    def __init__(self, source):
        self.uri = source
        self.tmp_local_file = TempFile()
        self.tmp_local_file.mkdirectory()

        source = self._fetch(source, str(self.tmp_local_file))
        LocalPath.__init__(self, source)

    def _fetch(self, source, destination, branch=None):
        commands.Ida32.decompile(source, destination)
        '''
        idascript = os.path.abspath(r'ida-allthethings.py')
        print (destination)
        destination_file = os.path.join(destination, os.path.split(source)[1].rsplit(".", 1)[0] + '.c')

        decompile_script_cmd = '%s \\"%s\\"' % (idascript, destination_file)
        cmd = [Config.PATH_IDA32, '-B', '-M', '-S"%s"' % decompile_script_cmd, '"' + source + '"']
        logger.debug(cmd)
        print (' '.join(cmd))
        ret = subprocess.call(' '.join(cmd), shell=True)
        # ret = subprocess.call(cmd, shell=True)
        # subprocess.call()
        if ret != 0:
            raise Exception("command failed: %s" % ret)
        '''
        return destination

class Application64Bits(LocalPath):
    def __init__(self, source):
        self.uri = source
        self.tmp_local_file = TempFile()
        self.tmp_local_file.mkdirectory()

        source = self._fetch(source, str(self.tmp_local_file))
        LocalPath.__init__(self, source)

    def _fetch(self, source, destination, branch=None):
        commands.Ida64.decompile(source, destination)
        return destination

class SoliditySmartContract(LocalPath):
    def __init__(self, source):
        self.uri = source
        self.tmp_local_file = TempFile()
        self.tmp_local_file.mkdirectory()

        source = self._fetch(source, str(self.tmp_local_file))
        LocalPath.__init__(self, source)

    def _fetch(self, source, destination, branch=None):
        commands.Porosity.decompile(source, destination)
        return destination


"""pulpapi - Access to Pulp REST API"""
from rest_framework.response import Response
from rest_framework.reverse import reverse as reverse_absolute
from rest_framework import renderers
from rest_framework import serializers
from rest_framework import status
from rest_framework import viewsets
import requests

#TODO: Configurable Pulp connectivity. For now, assume a local container
# service link called "pulpapi", and don't verify the HTTPS connection

#=====================================
# Client access to the remote Pulp API
#=====================================

pulp_url = "https://pulpapi/pulp"
pulp_api_path = "/api/v2/"
pulp_api_url = pulp_url + pulp_api_path
pulp_repo_url = "http://localhost/pulp/repos/"

def _access_pulp_url(http_method, *api_segments, **request_kwds):
    url_segments = [pulp_api_url]
    url_segments.extend(api_segments)
    api_url = "/".join(url_segments) + "/"
    reply = http_method(api_url,
                        auth=('admin', 'admin'),
                        verify=False,
                        **request_kwds)
    reply.raise_for_status()
    return reply

def _get_pulp_url(*api_segments, **request_kwds):
    return _access_pulp_url(requests.get, *api_segments, **request_kwds)

def _post_pulp_url(*api_segments, **request_kwds):
    return _access_pulp_url(requests.post, *api_segments, **request_kwds)

def _delete_pulp_url(*api_segments, **request_kwds):
    return _access_pulp_url(requests.delete, *api_segments, **request_kwds)

def _convert_repo(repo):
    repo_id = repo["id"]
    return {"repo_id": repo_id,
            "display_name": repo["display_name"],
            "details": repo,
           }

def iter_repos():
    pulp_info = _get_pulp_url("repositories")
    if pulp_info.text == "not found":
        raw_repos = []
    else:
        raw_repos = pulp_info.json()
    for repo in raw_repos:
        yield _convert_repo(repo)

def get_repo(repo_id):
    return _convert_repo(_get_pulp_url("repositories", repo_id).json())

def create_repo(repo_id, display_name):
    details = {"id":repo_id, "display_name":display_name}
    pulp_reply = _post_pulp_url("repositories", json=details)
    return _convert_repo(pulp_reply.json())

def delete_repo(repo_id):
    try:
        return _delete_pulp_url("repositories", repo_id).json()
    except requests.HTTPError as e:
        if e.response.status_code == status.HTTP_404_NOT_FOUND:
            return {"error": None, "result": None, "spawned_tasks": []}
        raise

def set_feed(repo_id, feed_url=None):
    config = {"importer_type_id": "yum_importer"}
    if feed_url is not None:
        config["importer_config"] = {"feed": feed_url}
    pulp_reply = _post_pulp_url("repositories", repo_id, "importers",
                                json=config)
    return pulp_reply.json()

def get_feed(repo_id):
    pulp_reply = _get_pulp_url("repositories", repo_id, "importers")
    return pulp_reply.json()[0]

def start_sync(repo_id):
    pulp_reply = _post_pulp_url("repositories", repo_id, "actions/sync")
    return pulp_reply.json()

def start_merge(source_repo_id, target_repo_id):
    config = {"source_repo_id": source_repo_id}
    pulp_reply = _post_pulp_url("repositories", target_repo_id,
                                "actions/associate", json=config)
    return pulp_reply.json()

def set_target(repo_id):
    config = {
        "distributor_id": "target",
        "distributor_type_id": "yum_distributor",
        "distributor_config": {
            "http": True,
            "https": False, # No default SSL certs set up
            "relative_url": repo_id,
        },
        "auto_publish": True
    }
    pulp_reply = _post_pulp_url("repositories", repo_id, "distributors",
                                json=config)
    distributor = pulp_reply.json()
    repo_url = pulp_repo_url + distributor["config"]["relative_url"]
    return distributor, repo_url

def get_target(repo_id):
    pulp_reply = _get_pulp_url("repositories", repo_id, "distributors")
    return pulp_reply.json()[0]

def start_publish(repo_id):
    config = {"id": "target"}
    pulp_reply = _post_pulp_url("repositories", repo_id, "actions/publish",
                                json=config)
    return pulp_reply.json()

# TODO: Pass through properly structured error information
class RemotePulpError(Exception): pass

# TODO: Switch from requests to aiohttp and make this non-blocking
def wait_for_task(task_id):
    while True:
        pulp_reply = _get_pulp_url("tasks", task_id)
        reply_data = pulp_reply.json()
        status = reply_data["state"]
        if status == "finished":
            break
        if status not in ("waiting", "running", "suspended"):
            raise RemotePulpError(str(reply_data))
    return reply_data

#=========================================
# Local REST API proxy for remote Pulp API
#=========================================

COLLECTION_NAME = 'pulp_repo'
def add_to_router(router, prefix):
    router.register(prefix, PulpRepoViewSet, base_name=COLLECTION_NAME)

# Adapts HyperlinkedIdentityField to use __getitem__ rather than __getattr__
class DictBasedIdentityField(serializers.HyperlinkedIdentityField):

    def get_url(self, obj, view_name, request, format):
        # Unsaved objects will not yet have a valid URL.
        try:
            lookup_value = obj[self.lookup_field]
        except KeyError:
            return None
        kwargs = {self.lookup_url_kwarg: lookup_value}
        return self.reverse(view_name, kwargs=kwargs, request=request, format=format)

class PulpRepoSerializer(serializers.Serializer):
    url = DictBasedIdentityField(view_name='pulp_repo-detail',
                                 lookup_field='repo_id',
                                 lookup_url_kwarg='pk')
    repo_id = serializers.CharField()
    display_name = serializers.CharField()
    details = serializers.DictField(read_only=True)

    def create(self, validated_data):
        repo = pulpapi.create_repo(**validated_data)
        return repo

class PulpRepoViewSet(viewsets.ViewSet):
    serializer_class = PulpRepoSerializer

    def _serialize(self, data, request, *, many=False):
        context={'request': request}
        return self.serializer_class(data, context=context, many=many).data

    def list(self, request):
        return Response(self._serialize(iter_repos(), request, many=True))

    def retrieve(self, request, *, pk):
        return Response(self._serialize(get_repo(pk), request))

    def create(self, request):
        serializer = self.serializer_class(data=request.data)
        if not serializer.is_valid():
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)
        repo = serializer.save()
        return Response(self._serialize(repo, request),
                        status=status.HTTP_201_CREATED)