<?xml version="1.0" encoding="UTF-8"?>
<diagram author="Yentl Van Tendeloo" name="HTTP client">
    <description>
        HTTP client.
    </description>
    <top>
        import uuid
    </top>

    <inport name="socket_in"/>
    <outport name="socket_out"/>

    <inport name="request_in"/>
    <outport name="request_out"/>

    <class name="HTTPClient">
        <constructor>
            <body>
                <![CDATA[
                self.socket = None
                self.received_data = ""
                self.send_data = ""
                self.queue = []
                self.IDs = []
                ]]>
            </body>
        </constructor>
        <scxml initial="init">
            <state id="init">
                <onentry>
                    <script>
                        self.ID = str(uuid.uuid4())
                    </script>
                    <raise scope="output" event="create_socket" port="socket_out">
                        <parameter expr="self.ID"/>
                    </raise>
                </onentry>

                <transition port="socket_in" event="created_socket" cond="self.ID == ID" target="../waiting">
                    <parameter name="socket"/>
                    <parameter name="ID"/>
                    <script>
                        self.socket = socket
                    </script>
                </transition>
            </state>

            <state id="waiting">
                <onentry>
                    <raise scope="output" port="request_out" event="http_client_initialized"/>
                </onentry>

                <transition port="request_in" event="connect" target="../connecting">
                    <parameter name="address"/>
                    <parameter name="timeout"/>

                    <script>
                        self.address = address
                        self.timeout = timeout
                    </script>
                </transition>
            </state>

            <state id="connecting" initial="connecting">
                <state id="connecting">
                    <onentry>
                        <raise scope="output" event="connect_socket" port="socket_out">
                            <parameter expr="self.socket"/>
                            <parameter expr="self.address"/>
                        </raise>
                    </onentry>

                    <transition port="socket_in" event="error_socket" target="../cooldown"/>

                    <transition port="socket_in" event="connected_socket" cond="self.socket == socket" target="../../connected">
                        <parameter name="socket"/>
                        <raise scope="output" port="request_out" event="http_client_ready"/>
                    </transition>
                </state>

                <state id="cooldown">
                    <transition after="0.1" target="../connecting"/>
                </state>

                <transition after="self.timeout" target="../waiting">
                    <raise scope="output" port="request_out" event="http_client_timeout"/>
                </transition>
            </state>

            <parallel id="connected">
                <state id="listening" initial="listen">
                    <state id="listen">
                        <onentry>
                            <raise scope="output" port="socket_out" event="recv_socket">
                                <parameter expr="self.socket"/>
                            </raise>
                        </onentry>
                        <transition event="received_socket" port="socket_in" cond="(self.socket == socket) and (len(data) > 0)" target=".">
                            <parameter name="socket"/>
                            <parameter name="data"/>
                            <script>
                                self.received_data += data
                            </script>
                        </transition>
                        <transition event="received_socket" port="socket_in" cond="(self.socket == socket) and (len(data) == 0)" target="../close">
                            <parameter name="socket"/>
                            <parameter name="data"/>
                        </transition>
                    </state>
                    <state id="close">
                    </state>
                </state>

                <state id="sending" initial="waiting_for_data">
                    <state id="waiting_for_data">
                        <transition cond="len(self.send_data) > 0" target="../transferring">
                            <raise scope="output" port="socket_out" event="send_socket">
                                <parameter expr="self.socket"/>
                                <parameter expr="self.send_data"/>
                            </raise>
                        </transition>
                    </state>
                    <state id="transferring">
                        <transition event="sent_socket" port="socket_in" cond="self.socket == socket" target="../waiting_for_data">
                            <parameter name="socket"/>
                            <parameter name="sent_bytes"/>
                            <script>
                                self.send_data = self.send_data[sent_bytes:]
                            </script>
                        </transition>
                    </state>
                </state>

                <state id="queueing">
                    <state id="queueing">
                        <onentry>
                        </onentry>
                        <transition port="request_in" event="HTTP_input" target=".">
                            <parameter name="data"/>
                            <parameter name="ID"/>
                            <script>
                                self.send_data += "POST / HTTP/1.0\r\n"
                                self.send_data += "Content-Length: %i\r\n" % len(str(data))
                                self.send_data += "\r\n"
                                self.send_data += data
                                self.IDs.append(ID)
                            </script>
                        </transition>
                    </state>
                </state>

                <state id="parsing" initial="wait_for_header">
                    <state id="wait_for_header">
                        <transition cond="'\r\n\r\n' in self.received_data" target="../wait_for_payload">
                            <script>
                                header, self.received_data = self.received_data.split("\r\n\r\n", 1)
                                header = header.lower()
                                if "content-length" in header:
                                    _, after = header.split("content-length:", 1)
                                    after, _ = after.split("\r\n", 1)
                                    after = after.strip()
                                    self.length = int(after)
                                else:
                                    self.length = float('inf')
                            </script>
                        </transition>
                    </state>
                    <state id="wait_for_payload">
                        <transition cond="len(self.received_data) >= self.length and self.IDs[0] is not None" target="../wait_for_header">
                            <script>
                                data = self.received_data[:self.length]
                                self.received_data = self.received_data[self.length:]
                            </script>
                            <raise event="HTTP_output" scope="output" port="request_out">
                                <parameter expr="data"/>
                                <parameter expr="self.IDs.pop(0)"/>
                            </raise>
                        </transition>

                        <transition cond="len(self.received_data) >= self.length and self.IDs[0] is None" target="../wait_for_header">
                            <script>
                                # Drop data
                                self.received_data = self.received_data[self.length:]
                                self.IDs.pop(0)
                            </script>
                        </transition>
                    </state>
                </state>
            </parallel>
        </scxml>
    </class>
</diagram>
