# Copyright (c) 2023, ETH Zurich and UNC Chapel Hill.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#
#     * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of
#       its contributors may be used to endorse or promote products derived
#       from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de)

import re
import argparse
import requests
from lxml.html import soupparser


MAX_REQUEST_TRIALS = 10


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("--lib_path", required=True)
    args = parser.parse_args()
    return args


def request_trial(func, *args, **kwargs):
    for i in range(MAX_REQUEST_TRIALS):
        try:
            response = func(*args, **kwargs)
        except:
            continue
        else:
            return response

    raise SystemError


def main():
    args = parse_args()

    ##########################################################################
    # Header file
    ##########################################################################

    with open(args.lib_path + ".h", "w") as f:
        f.write("#include <vector>\n")
        f.write("#include <string>\n")
        f.write("#include <unordered_map>\n\n")
        f.write("// { make1 : ({ model1 : sensor-width in mm }, ...), ... }\n")
        f.write("typedef std::vector<std::pair<std::string, float>> make_specs_t;\n")
        f.write("typedef std::unordered_map<std::string, make_specs_t> camera_specs_t;;\n\n")
        f.write("camera_specs_t InitializeCameraSpecs();\n\n")

    ##########################################################################
    # Source file
    ##########################################################################

    makes_response = requests.get("http://www.digicamdb.com")
    makes_tree = soupparser.fromstring(makes_response.text)
    makes_node = makes_tree.find(".//select[@id=\"select_brand\"]")
    makes = [b.attrib["value"] for b in makes_node.iter("option")]

    with open(args.lib_path + ".cc", "w") as f:
        f.write("camera_specs_t InitializeCameraSpecs() {\n")
        f.write("  camera_specs_t specs;\n\n")
        for make in makes:
            f.write("  {\n")
            f.write("    auto& make_specs = specs[\"%s\"];\n" % make.lower().replace(" ", ""))

            models_response = request_trial(
                requests.post,
                "http://www.digicamdb.com/inc/ajax.php",
                data={"b": make, "role": "header_search"})

            models_tree = soupparser.fromstring(models_response.text)
            models_code = ""
            num_models = 0
            for model_node in models_tree.iter("option"):
                model = model_node.attrib.get("value")
                model_name = model_node.text
                if model is None:
                    continue

                url = "http://www.digicamdb.com/specs/{0}_{1}" \
                                            .format(make, model)
                specs_response = request_trial(requests.get, url)

                specs_tree = soupparser.fromstring(specs_response.text)
                for spec in specs_tree.findall(".//td[@class=\"info_key\"]"):
                    if spec.text.strip() == "Sensor:":
                        sensor_text = spec.find("..").find("./td[@class=\"bold\"]")
                        sensor_text = sensor_text.text.strip()
                        m = re.match(".*?([\d.]+) x ([\d.]+).*?", sensor_text)
                        sensor_width = m.group(1)
                        data = (model_name.lower().replace(" ", ""),
                                float(sensor_width.replace(" ", "")))
                        models_code += "    make_specs.emplace_back(\"%s\", %.4ff);\n" % data

                        print(make, model_name)
                        print("   ", sensor_text)

                        num_models += 1

            f.write("    make_specs.reserve(%d);\n" % num_models)
            f.write(models_code)
            f.write("  }\n\n")

        f.write("  return specs;\n")
        f.write("}\n")


if __name__ == "__main__":
    main()
