import argparse
import json
import os
import ray
import hytools as ht
from hytools.io.envi import WriteENVI
from hytools.brdf import calc_brdf_coeffs


def build_anc_mapping(anc_file, anc_names):
    return dict(zip(anc_names, [[anc_file, i] for i in range(len(anc_names))]))


def build_brdf_config(args):
    mask = [["ndi", {"band_1": args.ndi_band_1, "band_2": args.ndi_band_2, "min": args.ndi_min, "max": args.ndi_max}]]
    brdf = {
        "type": args.brdf_type,
        "grouped": False,
        "geometric": args.geometric,
        "volume": args.volume,
        "b/r": args.b_r,
        "h/b": args.h_b,
        "sample_perc": args.sample_perc,
        "calc_mask": mask,
        "apply_mask": mask,
        "solar_zn_type": args.solar_zn_type
    }
    if args.brdf_type == "universal":
        brdf["diagnostic_plots"] = False
        brdf["diagnostic_waves"] = []
    if args.brdf_type == "flex":
        brdf["interp_kind"] = "linear"
        brdf["bin_type"] = "dynamic"
        brdf["num_bins"] = args.num_bins
        brdf["ndvi_bin_min"] = args.ndvi_bin_min
        brdf["ndvi_bin_max"] = args.ndvi_bin_max
        brdf["ndvi_perc_min"] = args.ndvi_perc_min
        brdf["ndvi_perc_max"] = args.ndvi_perc_max
    return brdf


def export_brdf_corrected(hy_obj, args):
    header_dict = hy_obj.get_header()
    header_dict["data ignore value"] = hy_obj.no_data
    header_dict["data type"] = 4
    output_name = os.path.join(
        args["output_dir"],
        f"{os.path.splitext(os.path.basename(hy_obj.file_name))[0]}_{args['suffix']}"
    )
    writer = WriteENVI(output_name, header_dict)
    iterator = hy_obj.iterate(by="line", corrections=hy_obj.corrections)
    while not iterator.complete:
        line = iterator.read_next()
        writer.write_line(line, iterator.current_line)
    writer.close()


def export_brdf_coeffs(hy_obj, args):
    output_name = os.path.join(
        args["output_dir"],
        f"{os.path.splitext(os.path.basename(hy_obj.file_name))[0]}_brdf_coeffs_{args['suffix']}.json"
    )
    with open(output_name, "w") as outfile:
        json.dump(hy_obj.brdf, outfile)


def main():
    parser = argparse.ArgumentParser(description="BRDF correction for ENVI BIP images")
    parser.add_argument("image", type=str)
    parser.add_argument("anc_file", type=str)
    parser.add_argument("output_dir", type=str)
    parser.add_argument("--anc-map", type=str, default="")
    parser.add_argument("--anc-names", type=str, default="path_length,sensor_az,sensor_zn,solar_az,solar_zn,phase,slope,aspect,cosine_i,utc_time")
    parser.add_argument("--brdf-type", type=str, default="universal", choices=["universal", "flex"])
    parser.add_argument("--suffix", type=str, default="brdf")
    parser.add_argument("--num-cpus", type=int, default=1)
    parser.add_argument("--bad-bands-json", type=str, default="")
    parser.add_argument("--solar-zn-type", type=str, default="scene")
    parser.add_argument("--geometric", type=str, default="li_dense_r")
    parser.add_argument("--volume", type=str, default="ross_thick")
    parser.add_argument("--b-r", type=float, default=2.5)
    parser.add_argument("--h-b", type=float, default=2.0)
    parser.add_argument("--sample-perc", type=float, default=0.1)
    parser.add_argument("--ndi-band-1", type=float, default=850.0)
    parser.add_argument("--ndi-band-2", type=float, default=660.0)
    parser.add_argument("--ndi-min", type=float, default=0.05)
    parser.add_argument("--ndi-max", type=float, default=1.0)
    parser.add_argument("--num-bins", type=int, default=18)
    parser.add_argument("--ndvi-bin-min", type=float, default=0.05)
    parser.add_argument("--ndvi-bin-max", type=float, default=1.0)
    parser.add_argument("--ndvi-perc-min", type=float, default=10.0)
    parser.add_argument("--ndvi-perc-max", type=float, default=95.0)
    parser.add_argument("--export-coeffs", action="store_true")
    args = parser.parse_args()

    os.makedirs(args.output_dir, exist_ok=True)

    if args.anc_map:
        with open(args.anc_map, "r") as infile:
            anc_map = json.load(infile)
    else:
        anc_names = [x.strip() for x in args.anc_names.split(",") if x.strip()]
        anc_map = build_anc_mapping(args.anc_file, anc_names)

    config_dict = {
        "file_type": "envi",
        "input_files": [args.image],
        "anc_files": {args.image: anc_map},
        "corrections": ["brdf"],
        "brdf": build_brdf_config(args),
        "num_cpus": args.num_cpus
    }

    if args.bad_bands_json:
        config_dict["bad_bands"] = json.loads(args.bad_bands_json)

    if ray.is_initialized():
        ray.shutdown()
    ray.init(num_cpus=config_dict["num_cpus"])

    HyTools = ray.remote(ht.HyTools)
    actor = HyTools.remote()
    ray.get(actor.read_file.remote(args.image, "envi", anc_map))
    if "bad_bands" in config_dict:
        ray.get(actor.create_bad_bands.remote(config_dict["bad_bands"]))

    calc_brdf_coeffs([actor], config_dict)

    ray.get(actor.do.remote(export_brdf_corrected, {"output_dir": args.output_dir, "suffix": args.suffix}))
    if args.export_coeffs:
        ray.get(actor.do.remote(export_brdf_coeffs, {"output_dir": args.output_dir, "suffix": args.suffix}))

    ray.shutdown()


if __name__ == "__main__":
    main()
