1"""Smart tasks set timers to turn on/off lights in various ways.
2
3> Currently supporting wake up
4
5SmartTask # return top level info
6    TaskControl # Change top level values
7    StartAction # Get top level info on start action
8        StartActionItem # Get info on specific device in task
9            StartActionItemController # change values for task
10"""
11
12import datetime
13from datetime import datetime as dt
14
15from .command import Command
16from .const import (
17    ATTR_DEVICE_STATE,
18    ATTR_ID,
19    ATTR_LIGHT_DIMMER,
20    ATTR_REPEAT_DAYS,
21    ATTR_SMART_TASK_LIGHTS_OFF,
22    ATTR_SMART_TASK_NOT_AT_HOME,
23    ATTR_SMART_TASK_TRIGGER_TIME_INTERVAL,
24    ATTR_SMART_TASK_TRIGGER_TIME_START_HOUR,
25    ATTR_SMART_TASK_TRIGGER_TIME_START_MIN,
26    ATTR_SMART_TASK_TYPE,
27    ATTR_SMART_TASK_WAKE_UP,
28    ATTR_START_ACTION,
29    ATTR_TRANSITION_TIME,
30    ROOT_SMART_TASKS,
31    ROOT_START_ACTION,
32)
33from .resource import ApiResource
34from .util import BitChoices
35
36WEEKDAYS = BitChoices(
37    (
38        ("mon", "Monday"),
39        ("tue", "Tuesday"),
40        ("wed", "Wednesday"),
41        ("thu", "Thursday"),
42        ("fri", "Friday"),
43        ("sat", "Saturday"),
44        ("sun", "Sunday"),
45    )
46)
47
48
49class SmartTask(ApiResource):
50    """Represent a smart task."""
51
52    def __init__(self, gateway, raw):
53        """Initialize the class."""
54        super().__init__(raw)
55        self._gateway = gateway
56
57    @property
58    def path(self):
59        """Return gateway path."""
60        return [ROOT_SMART_TASKS, self.id]
61
62    @property
63    def state(self):
64        """Boolean representing the light state of the transition."""
65        return self.raw.get(ATTR_DEVICE_STATE) == 1
66
67    @property
68    def task_type_id(self):
69        """Return type of task."""
70        return self.raw.get(ATTR_SMART_TASK_TYPE)
71
72    @property
73    def task_type_name(self):
74        """Return the task type in plain text.
75
76        (Own interpretation of names.)
77        """
78        if self.is_wake_up:
79            return "Wake Up"
80        if self.is_not_at_home:
81            return "Not At Home"
82        if self.is_lights_off:
83            return "Lights Off"
84        return None
85
86    @property
87    def is_wake_up(self):
88        """Boolean representing if this is a wake up task."""
89        return self.raw.get(ATTR_SMART_TASK_TYPE) == ATTR_SMART_TASK_WAKE_UP
90
91    @property
92    def is_not_at_home(self):
93        """Boolean representing if this is a not home task."""
94        return self.raw.get(ATTR_SMART_TASK_TYPE) == ATTR_SMART_TASK_NOT_AT_HOME
95
96    @property
97    def is_lights_off(self):
98        """Boolean representing if this is a lights off task."""
99        return self.raw.get(ATTR_SMART_TASK_TYPE) == ATTR_SMART_TASK_LIGHTS_OFF
100
101    @property
102    def repeat_days(self):
103        """Return int (bit) for enabled weekdays."""
104        return self.raw.get(ATTR_REPEAT_DAYS)
105
106    @property
107    def repeat_days_list(self):
108        """Binary representation of weekdays the event takes place."""
109        return WEEKDAYS.get_selected_values(self.raw.get(ATTR_REPEAT_DAYS))
110
111    @property
112    def task_start_parameters(self):
113        """Return hour and minute that task starts."""
114        return self.raw.get(ATTR_SMART_TASK_TRIGGER_TIME_INTERVAL)[0]
115
116    @property
117    def task_start_time(self):
118        """Return the time the task starts.
119
120        Time is set according to iso8601.
121        """
122        return datetime.time(
123            self.task_start_parameters[ATTR_SMART_TASK_TRIGGER_TIME_START_HOUR],
124            self.task_start_parameters[ATTR_SMART_TASK_TRIGGER_TIME_START_MIN],
125        )
126
127    @property
128    def task_control(self):
129        """Control a task."""
130        return TaskControl(self, self.state, self.path, self._gateway)
131
132    @property
133    def start_action(self):
134        """Return start action object."""
135        return StartAction(self, self.path)
136
137    def __repr__(self):
138        """Return a readable name for smart task."""
139        state = "on" if self.state else "off"
140        return f"<Task {self.id} - {self.task_type_name} - {state}>"
141
142
143class TaskControl:
144    """Class to control the tasks."""
145
146    def __init__(self, task, state, path, gateway):
147        """Initialize TaskControl."""
148        self._task = task
149        self.state = state
150        self.path = path
151        self._gateway = gateway
152
153    @property
154    def tasks(self):
155        """Return task objects of the task control."""
156        return [
157            StartActionItem(self._task, i, self.state, self.path, self.raw)
158            for i in range(len(self.raw))
159        ]
160
161    def set_dimmer_start_time(self, hour, minute):
162        """Set start time for task (hh:mm) in iso8601.
163
164        NB: dimmer starts 30 mins before time in app
165        """
166        #  This is to calculate the difference between local time
167        #  and the time in the gateway
168        d_now = self._gateway.get_gateway_info().current_time
169        d_utcnow = dt.utcnow()
170        diff = d_now - d_utcnow
171        newtime = dt(100, 1, 1, hour, minute, 00) - diff
172
173        command = {
174            ATTR_SMART_TASK_TRIGGER_TIME_INTERVAL: [
175                {
176                    ATTR_SMART_TASK_TRIGGER_TIME_START_HOUR: newtime.hour,
177                    ATTR_SMART_TASK_TRIGGER_TIME_START_MIN: newtime.minute,
178                }
179            ]
180        }
181        return self._task.set_values(command)
182
183    @property
184    def raw(self):
185        """Return raw data that it represents."""
186        return self._task.raw[ATTR_START_ACTION]
187
188
189class StartAction:
190    """Class to control the start action-node."""
191
192    def __init__(self, start_action, path):
193        """Initialize StartAction class."""
194        self.start_action = start_action
195        self.path = path
196
197    @property
198    def state(self):
199        """Return state of start action task."""
200        return self.raw.get(ATTR_DEVICE_STATE)
201
202    @property
203    def devices(self):
204        """Return state of start action task."""
205        return [
206            StartActionItem(self.start_action, i, self.state, self.path, self.raw)
207            for i in range(len(self.raw[ROOT_START_ACTION]))
208        ]
209
210    @property
211    def raw(self):
212        """Return raw data that it represents."""
213        return self.start_action.raw[ATTR_START_ACTION]
214
215
216class StartActionItem:
217    """Class to show settings for a task."""
218
219    def __init__(self, task, index, state, path, raw):
220        """Initialize TaskInfo."""
221        self.task = task
222        self.index = index
223        self.state = state
224        self.path = path
225        self._raw = raw
226
227    @property
228    def devices_dict(self):
229        """Return state of start action task."""
230        json_list = {}
231        index = 0
232        for x_entry in self._raw[ROOT_START_ACTION]:
233            if index != self.index:
234                json_list.update(x_entry)
235            index = index + 1
236        return json_list
237
238    @property
239    def id(self):
240        """Return ID (device id) of task."""
241        return self.raw.get(ATTR_ID)
242
243    @property
244    def item_controller(self):
245        """Control a task."""
246        return StartActionItemController(
247            self, self.raw, self.state, self.path, self.devices_dict
248        )
249
250    @property
251    def transition_time(self):
252        """Transition runs for this long from the time in task_start.
253
254        Value is in seconds x 10. Default to 0 if transition is missing.
255        """
256        return self.raw.get(ATTR_TRANSITION_TIME, 0) / 60 / 10
257
258    @property
259    def dimmer(self):
260        """Return dimmer level."""
261        return self.raw.get(ATTR_LIGHT_DIMMER)
262
263    @property
264    def raw(self):
265        """Return raw data that it represents."""
266        return self._raw[ROOT_START_ACTION][self.index]
267
268    def __repr__(self):
269        """Return a readable name for this class."""
270        return f"<StartActionItem (Device: {self.id} - Dimmer: {self.dimmer} - Time: {self.transition_time})>"
271
272
273class StartActionItemController:
274    """Class to edit settings for a task."""
275
276    def __init__(self, item, raw, state, path, devices_dict):
277        """Initialize TaskControl."""
278        self._item = item
279        self.raw = raw
280        self.state = state
281        self.path = path
282        self.devices_dict = devices_dict
283
284    def set_dimmer(self, dimmer):
285        """Set final dimmer value for task."""
286        command = {
287            ATTR_START_ACTION: {
288                ATTR_DEVICE_STATE: self.state,
289                ROOT_START_ACTION: [
290                    {
291                        ATTR_ID: self.raw[ATTR_ID],
292                        ATTR_LIGHT_DIMMER: dimmer,
293                        ATTR_TRANSITION_TIME: self.raw.get(ATTR_TRANSITION_TIME, 0),
294                    },
295                    self.devices_dict,
296                ],
297            }
298        }
299        return self.set_values(command)
300
301    def set_transition_time(self, transition_time):
302        """Set time (mins) for light transition."""
303        command = {
304            ATTR_START_ACTION: {
305                ATTR_DEVICE_STATE: self.state,
306                ROOT_START_ACTION: [
307                    {
308                        ATTR_ID: self.raw[ATTR_ID],
309                        ATTR_LIGHT_DIMMER: self.raw[ATTR_LIGHT_DIMMER],
310                        ATTR_TRANSITION_TIME: transition_time * 10 * 60,
311                    },
312                    self.devices_dict,
313                ],
314            }
315        }
316        return self.set_values(command)
317
318    def set_values(self, command):
319        """
320        Set values on task control.
321
322        Returns a Command.
323        """
324        return Command("put", self._item.path, command)
325