class Worker(threading.Thread):
    def __init__(self, location, job, queue):
        self.location = location
        self.queue = queue
        self.job = job
        threading.Thread.__init__(self)
        self.log = logging.getLogger(__name__)
        self.log.info("log initialized")
        self.config = Config()
        self.log.info("config initialized")
        self.database = Database(self.config)
        self.log.info("database initialized")

    def setup_meta(self):
        self.log.debug("setup meta")
        os.makedirs(self.location, exist_ok=True)
        if not os.path.exists(self.location + "/meta"):
            cmdline = "git clone https://github.com/aparcar/meta-imagebuilder.git ."
            proc = subprocess.Popen(
                cmdline.split(" "),
                cwd=self.location,
                stdout=subprocess.PIPE,
                shell=False,
            )

            _, errors = proc.communicate()
            return_code = proc.returncode

            if return_code != 0:
                self.log.error("failed to setup meta ImageBuilder")
                exit()

        self.log.info("meta ImageBuilder successfully setup")

    def write_log(self, path, stdout=None, stderr=None):
        with open(path, "a") as log_file:
            log_file.write("### BUILD COMMAND:\n\n")
            for key, value in self.params.items():
                log_file.write("{}={}\n".format(key.upper(), str(value)))
            log_file.write("sh meta\n")
            if stdout:
                log_file.write("\n\n### STDOUT:\n\n" + stdout)
            if stderr:
                log_file.write("\n\n### STDERR:\n\n" + stderr)

    # build image
    def build(self):
        self.log.debug("create and parse manifest")

        # fail path in case of erros
        fail_log_path = self.config.get_folder(
            "download_folder") + "/faillogs/faillog-{}.txt".format(
                self.params["request_hash"])

        self.image = Image(self.params)

        # first determine the resulting manifest hash
        return_code, manifest_content, errors = self.run_meta("manifest")

        if return_code == 0:
            self.image.params["manifest_hash"] = get_hash(manifest_content, 15)

            manifest_pattern = r"(.+) - (.+)\n"
            manifest_packages = dict(
                re.findall(manifest_pattern, manifest_content))
            self.database.add_manifest_packages(
                self.image.params["manifest_hash"], manifest_packages)
            self.log.info("successfully parsed manifest")
        else:
            self.log.error("couldn't determine manifest")
            self.write_log(fail_log_path, stderr=errors)
            self.database.set_image_requests_status(
                self.params["request_hash"], "manifest_fail")
            return False

        # set directory where image is stored on server
        self.image.set_image_dir()
        self.log.debug("dir %s", self.image.params["dir"])

        # calculate hash based on resulted manifest
        self.image.params["image_hash"] = get_hash(
            " ".join(self.image.as_array("manifest_hash")), 15)

        # set log path in case of success
        success_log_path = self.image.params[
            "dir"] + "/buildlog-{}.txt".format(self.params["image_hash"])

        # set build_status ahead, if stuff goes wrong it will be changed
        self.build_status = "created"

        # check if image already exists
        if not self.image.created():
            self.log.info("build image")
            with tempfile.TemporaryDirectory(
                    dir=self.config.get_folder("tempdir")) as build_dir:
                # now actually build the image with manifest hash as EXTRA_IMAGE_NAME
                self.params["worker"] = self.location
                self.params["BIN_DIR"] = build_dir
                self.params["j"] = str(os.cpu_count())
                self.params["EXTRA_IMAGE_NAME"] = self.params["manifest_hash"]
                # if uci defaults are added, at least at parts of the hash to time image name
                if self.params["defaults_hash"]:
                    defaults_dir = build_dir + "/files/etc/uci-defaults/"
                    # create folder to store uci defaults
                    os.makedirs(defaults_dir)
                    # request defaults content from database
                    defaults_content = self.database.get_defaults(
                        self.params["defaults_hash"])
                    with open(defaults_dir + "99-server-defaults",
                              "w") as defaults_file:
                        defaults_file.write(
                            defaults_content
                        )  # TODO check if special encoding is required

                    # tell ImageBuilder to integrate files
                    self.params["FILES"] = build_dir + "/files/"
                    self.params["EXTRA_IMAGE_NAME"] += "-" + self.params[
                        "defaults_hash"][:6]

                # download is already performed for manifest creation
                self.params["NO_DOWNLOAD"] = "1"

                build_start = time.time()
                return_code, buildlog, errors = self.run_meta("image")
                self.image.params["build_seconds"] = int(time.time() -
                                                         build_start)

                if return_code == 0:
                    # create folder in advance
                    os.makedirs(self.image.params["dir"], exist_ok=True)

                    self.log.debug(os.listdir(build_dir))

                    for filename in os.listdir(build_dir):
                        if os.path.exists(self.image.params["dir"] + "/" +
                                          filename):
                            break
                        shutil.move(build_dir + "/" + filename,
                                    self.image.params["dir"])

                    # possible sysupgrade names, ordered by likeliness
                    possible_sysupgrade_files = [
                        "*-squashfs-sysupgrade.bin",
                        "*-squashfs-sysupgrade.tar", "*-squashfs.trx",
                        "*-squashfs.chk", "*-squashfs.bin",
                        "*-squashfs-sdcard.img.gz", "*-combined-squashfs*",
                        "*.img.gz"
                    ]

                    sysupgrade = None

                    for sysupgrade_file in possible_sysupgrade_files:
                        sysupgrade = glob.glob(self.image.params["dir"] + "/" +
                                               sysupgrade_file)
                        if sysupgrade:
                            break

                    if not sysupgrade:
                        self.log.debug("sysupgrade not found")
                        if buildlog.find("too big") != -1:
                            self.log.warning("created image was to big")
                            self.database.set_image_requests_status(
                                self.params["request_hash"], "imagesize_fail")
                            self.write_log(fail_log_path, buildlog, errors)
                            return False
                        else:
                            self.build_status = "no_sysupgrade"
                            self.image.params["sysupgrade"] = ""
                    else:
                        self.image.params["sysupgrade"] = os.path.basename(
                            sysupgrade[0])

                    self.write_log(success_log_path, buildlog)
                    self.database.add_image(self.image.get_params())
                    self.log.info("build successfull")
                else:
                    self.log.info("build failed")
                    self.database.set_image_requests_status(
                        self.params["request_hash"], 'build_fail')
                    self.write_log(fail_log_path, buildlog, errors)
                    return False

        self.log.info("link request %s to image %s",
                      self.params["request_hash"], self.params["image_hash"])
        self.database.done_build_job(self.params["request_hash"],
                                     self.image.params["image_hash"],
                                     self.build_status)
        return True

    def run(self):
        self.setup_meta()

        while True:
            self.params = self.queue.get()
            self.version_config = self.config.version(self.params["distro"],
                                                      self.params["version"])
            if self.job == "image":
                self.build()
            elif self.job == "update":
                self.info()
                self.parse_packages()

    def info(self):
        self.parse_info()
        if os.path.exists(
                os.path.join(self.location, "imagebuilder",
                             self.params["distro"], self.params["version"],
                             self.params["target"], self.params["subtarget"],
                             "target/linux", self.params["target"],
                             "base-files/lib/upgrade/platform.sh")):
            self.log.info("%s target is supported", self.params["target"])
            self.database.insert_supported(self.params)

    def run_meta(self, cmd):
        cmdline = ["sh", "meta", cmd]
        env = os.environ.copy()

        if "parent_version" in self.version_config:
            self.params["IB_VERSION"] = self.version_config["parent_version"]
        if "repos" in self.version_config:
            self.params["REPOS"] = self.version_config["repos"]

        for key, value in self.params.items():
            env[key.upper()] = str(
                value)  # TODO convert meta script to Makefile

        proc = subprocess.Popen(cmdline,
                                cwd=self.location,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                shell=False,
                                env=env)

        output, errors = proc.communicate()
        return_code = proc.returncode
        output = output.decode('utf-8')
        errors = errors.decode('utf-8')

        return (return_code, output, errors)

    def parse_info(self):
        self.log.debug("parse info")

        return_code, output, errors = self.run_meta("info")

        if return_code == 0:
            default_packages_pattern = r"(.*\n)*Default Packages: (.+)\n"
            default_packages = re.match(default_packages_pattern, output,
                                        re.M).group(2)
            logging.debug("default packages: %s", default_packages)

            profiles_pattern = r"(.+):\n    (.+)\n    Packages: (.*)\n"
            profiles = re.findall(profiles_pattern, output)
            if not profiles:
                profiles = []
            self.database.insert_profiles(
                {
                    "distro": self.params["distro"],
                    "version": self.params["version"],
                    "target": self.params["target"],
                    "subtarget": self.params["subtarget"]
                }, default_packages, profiles)
        else:
            logging.error("could not receive profiles")
            return False

    def parse_packages(self):
        self.log.info("receive packages")

        return_code, output, errors = self.run_meta("package_list")

        if return_code == 0:
            packages = re.findall(r"(.+?) - (.+?) - .*\n", output)
            self.log.info("found {} packages".format(len(packages)))
            self.database.insert_packages_available(
                {
                    "distro": self.params["distro"],
                    "version": self.params["version"],
                    "target": self.params["target"],
                    "subtarget": self.params["subtarget"]
                }, packages)
        else:
            self.log.warning("could not receive packages")