#!/usr/bin/env python3
import json
import sys
import subprocess
import mimetypes
from urllib.parse import urlparse
from urllib.parse import parse_qs

from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
from io import BytesIO
import re
import os
import urllib.request, urllib.parse, urllib.error
import html
import posixpath
import shutil
import socket

PORT_NUMBER = 8000
MS_PORT = 0
ADB_PATH = ""
MOBILEDEVICE_PATH = ""


def ios_applications(device_id):
    s = subprocess.check_output([MOBILEDEVICE_PATH, "list_apps", "-u", device_id]).decode(sys.stdout.encoding)
    jsona = s.split("\n")
    return json.dumps(jsona)


class Server(SimpleHTTPRequestHandler):

    def do_POST(self):
        success, message = self.deal_post_data()

        if success:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect(('localhost', int(MS_PORT)))
            s.sendall(("%s" % message).encode())
            socket_response = s.recv(1024)
            s.close()

            self.send_response(200)
            self.end_headers()
            self.wfile.write(socket_response)
        else:
            self.send_response(500, "message")
            self.end_headers()

    def deal_post_data(self):
        content_type = self.headers['content-type']
        if not content_type:
            return False, "Content-Type header doesn't contain boundary"
        boundary = content_type.split("=")[1].encode()
        remainbytes = int(self.headers['content-length'])
        line = self.rfile.readline()
        remainbytes -= len(line)
        if boundary not in line:
            return False, "Content NOT begin with boundary"
        line = self.rfile.readline()
        remainbytes -= len(line)
        fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line.decode())
        if not fn:
            return False, "Can't find out file name..."
        path = self.translate_path(self.path)
        fn = os.path.join(path, fn[0])
        line = self.rfile.readline()
        remainbytes -= len(line)
        line = self.rfile.readline()
        remainbytes -= len(line)
        try:
            out = open(fn, 'wb')
        except IOError:
            return False, "Can't create file to write, do you have permission to write?"

        preline = self.rfile.readline()
        remainbytes -= len(preline)
        while remainbytes > 0:
            line = self.rfile.readline()
            remainbytes -= len(line)
            if boundary in line:
                preline = preline[0:-1]
                if preline.endswith(b'\r'):
                    preline = preline[0:-1]
                out.write(preline)
                out.close()

                return True, fn
            else:
                out.write(preline)
                preline = line
        return False, "Unexpect Ends of data."

    def send_head(self):
        path = self.translate_path(self.path)
        f = None
        if os.path.isdir(path):
            if not self.path.endswith('/'):
                # redirect browser - doing basically what apache does
                self.send_response(301)
                self.send_header("Location", self.path + "/")
                self.end_headers()
                return None
            for index in "index.html", "index.htm":
                index = os.path.join(path, index)
                if os.path.exists(index):
                    path = index
                    break
            else:
                return self.list_directory(path)
        ctype = self.guess_type(path)
        try:
            # Always read in binary mode. Opening files in text mode may cause
            # newline translations, making the actual size of the content
            # transmitted *less* than the content-length!
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        self.send_response(200)
        self.send_header("Content-type", ctype)
        fs = os.fstat(f.fileno())
        self.send_header("Content-Length", str(fs[6]))
        self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
        self.end_headers()
        return f

    def list_directory(self, path):
        try:
            list = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list.sort(key=lambda a: a.lower())
        f = BytesIO()
        displaypath = html.escape(urllib.parse.unquote(self.path))
        f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write(("<html>\n<title>Directory listing for %s</title>\n" % displaypath).encode())
        f.write(("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath).encode())
        f.write(b"<hr>\n")
        f.write(b"<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
        f.write(b"<input name=\"file\" type=\"file\"/>")
        f.write(b"<input type=\"submit\" value=\"upload\"/></form>\n")
        f.write(b"<hr>\n<ul>\n")
        for name in list:
            fullname = os.path.join(path, name)
            displayname = linkname = name
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                displayname = name + "/"
                linkname = name + "/"
            if os.path.islink(fullname):
                displayname = name + "@"
                # Note: a link to a directory displays with @ and links with /
            f.write(('<li><a href="%s">%s</a>\n'
                     % (urllib.parse.quote(linkname), html.escape(displayname))).encode())
        f.write(b"</ul>\n<hr>\n</body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f

    def translate_path(self, path):
        # abandon query parameters
        path = path.split('?', 1)[0]
        path = path.split('#', 1)[0]
        path = posixpath.normpath(urllib.parse.unquote(path))
        words = path.split('/')
        words = [_f for _f in words if _f]
        path = os.getcwd()
        for word in words:
            drive, word = os.path.splitdrive(word)
            head, word = os.path.split(word)
            if word in (os.curdir, os.pardir): continue
            path = os.path.join(path, word)
        return path

    def copyfile(self, source, outputfile):
        shutil.copyfileobj(source, outputfile)

    def guess_type(self, path):
        base, ext = posixpath.splitext(path)
        if ext in self.extensions_map:
            return self.extensions_map[ext]
        ext = ext.lower()
        if ext in self.extensions_map:
            return self.extensions_map[ext]
        else:
            return self.extensions_map['']

    if not mimetypes.inited:
        mimetypes.init()  # try to read system mime.types
    extensions_map = mimetypes.types_map.copy()
    extensions_map.update({
        '': 'application/octet-stream',  # Default
        '.py': 'text/plain',
        '.c': 'text/plain',
        '.h': 'text/plain',
    })

    def do_GET(self):
        if self.path == '/devices':
            response_content = self.devices_list().encode(encoding='utf_8')
            self.send_response(200)
            self.send_header('Contt-type', 'plain/text')
            self.send_header('Contt-type', 'plainsdfext')
            self.send_header('Contt-type', 'plsdfsdfain/text')
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            self.wfile.write(response_content)
        elif self.path == '/quit':
            self.close()
        elif self.path == '/status':
            self.send_response(200)
            self.send_header('Contt-type', 'plain/text')
            self.send_header('Contt-type', 'plainsdfext')
            self.send_header('Contt-type', 'plsdfsdfain/text')
            self.send_header('Content-type', 'text/plain')
            self.end_headers()
            self.wfile.write('MS_SERVER_STARTED'.encode(encoding='utf_8'))
        elif self.path == '/applications':
            parsed_url = urlparse(self.path)
            query = parse_qs(parsed_url.query)
            device_id = query.get('deviceId', str)[0]
            device_type = query.get('deviceType', str)[0]

            if device_type == "ios":
                response_content = ios_applications(device_id).encode(encoding='utf_8')
            else:
                response_content = self.android_applications(device_id).encode(encoding='utf_8')

            self.send_response(200)
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            self.wfile.write(response_content)
        else:
            return SimpleHTTPRequestHandler.do_GET(self)

    def devices_list(self):
        try:
            f = open(HTTP_PATH + '/devices.json')
            j = json.load(f)
            return json.dumps(j)
        except IOError:
            return self.error(500, "file not found")

    def android_applications(self, device_id):
        try:
            output = subprocess.check_output(
                [ADB_PATH, "-s", device_id, "shell", "cmd", "package", "list", "packages", "-3"]).decode(
                sys.stdout.encoding)
        except subprocess.CalledProcessError as e:
            return self.error(500, e.output)

        applications_list = [element[8:] for element in list(filter(None, output.split("\r\n")))]
        return json.dumps(applications_list)

    def not_found(self):
        return self.error(404, "not found")

    def error(self, status, message):
        self.send_response(status)
        self.send_header('Content-type', 'plain/text')
        self.end_headers()

        data_set = {"message": message}

        return json.dumps(data_set)

    def close(self):
        httpd.server_close()
        exit()


if __name__ == '__main__':
    PORT_NUMBER = int(sys.argv[1])
    ADB_PATH = sys.argv[2]
    HTTP_PATH = sys.argv[3]
    MS_PORT = sys.argv[4]

    if len(sys.argv) == 6:
        MOBILEDEVICE_PATH = sys.argv[5]

    httpd = ThreadingHTTPServer(('0.0.0.0', PORT_NUMBER), Server)
 
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    httpd.server_close()
