#
# Copyright (C) 2023, Inria
# GRAPHDECO research group, https://team.inria.fr/graphdeco
# All rights reserved.
#
# This software is free for non-commercial, research and evaluation use 
# under the terms of the LICENSE.md file.
#
# For inquiries contact  george.drettakis@inria.fr
#

from argparse import ArgumentParser, Namespace
import sys
import os


class GroupParams:
    pass


class ParamGroup:
    def __init__(self, parser: ArgumentParser, name: str, fill_none=False):
        group = parser.add_argument_group(name)
        for key, value in vars(self).items():
            shorthand = False
            if key.startswith("_"):
                shorthand = True
                key = key[1:]
            t = type(value)
            value = value if not fill_none else None
            if shorthand:
                if t == bool:
                    group.add_argument("--" + key, ("-" + key[0:1]), default=value, action="store_true")
                else:
                    group.add_argument("--" + key, ("-" + key[0:1]), default=value, type=t)
            else:
                if t == bool:
                    group.add_argument("--" + key, default=value, action="store_true")
                else:
                    group.add_argument("--" + key, default=value, type=t)

    def extract(self, args):
        group = GroupParams()
        for arg in vars(args).items():
            if arg[0] in vars(self) or ("_" + arg[0]) in vars(self):
                setattr(group, arg[0], arg[1])
        return group


class ModelParams(ParamGroup):
    def __init__(self, parser, sentinel=False):
        self.sh_degree = 3
        self._source_path = ""
        self._model_path = ""
        self._images = "images"
        self._resolution = -1
        self._white_background = False
        self.data_device = "cuda"
        self.eval = False
        self._kernel_size = 0.0
        # self.use_spatial_gaussian_bias = False
        self.ray_jitter = False
        self.resample_gt_image = False
        self.load_allres = False
        self.sample_more_highres = False
        self.use_decoupled_appearance = False
        # new add for scaffod GS
        self.feat_dim = 32
        # self.feat_dim = 64
        self.n_offsets = 10
        self.voxel_size = 0.001  # if voxel_size<=0, using 1nn dist
        self.update_depth = 3
        self.update_init_factor = 16
        self.update_hierachy_factor = 4
        self.use_feat_bank = False
        # self.use_kernel_gskernel = False
        self.appearance_dim = 0
        self.lowpoly = False
        self.ds = 1
        self.ratio = 1  # sampling the input point cloud
        self.undistorted = False
        self.add_opacity_dist = False  # no use
        self.add_cov_dist = False
        self.add_color_dist = False

        super().__init__(parser, "Loading Parameters", sentinel)

    def extract(self, args):
        g = super().extract(args)
        g.source_path = os.path.abspath(g.source_path)
        return g


class PipelineParams(ParamGroup):
    def __init__(self, parser):
        self.convert_SHs_python = False
        self.compute_cov3D_python = False
        self.compute_view2gaussian_python = False
        self.debug = False
        super().__init__(parser, "Pipeline Parameters")


class OptimizationParams(ParamGroup):
    def __init__(self, parser):
        self.iterations = 30_000

        self.position_lr_delay_mult = 0.01
        self.position_lr_max_steps = 30_000

        self.appearance_embeddings_lr = 0.001
        self.appearance_network_lr = 0.001
        self.percent_dense = 0.01
        self.lambda_dssim = 0.2
        self.lambda_distortion = 100
        self.lambda_depth_normal = 0.05
        self.distortion_from_iter = 15000
        self.depth_normal_from_iter = 15000
        self.densification_interval = 100
        self.opacity_reset_interval = 3000
        self.densify_from_iter = 500
        self.densify_until_iter = 15_000
        self.densify_grad_threshold = 0.0002
        ################
        self.position_lr_init = 0.0
        self.position_lr_final = 0.0

        self.feature_lr = 0.0075
        self.scaling_lr = 0.0007  # 0.007


        self.save_images_interval = 200

        self.offset_lr_init = 0.01
        self.offset_lr_final = 0.0001
        self.offset_lr_delay_mult = 0.01
        self.offset_lr_max_steps = 30_000

        self.gskernel_opacity_lr_init = 0.0002
        self.gskernel_opacity_lr_final = 0.00002
        self.gskernel_opacity_lr_delay_mult = 0.01
        self.gskernel_opacity_lr_max_steps = 30_000

        self.gskernel_cov_lr_init = 0.00001  # 0.004
        self.gskernel_cov_lr_final = 0.000001  # 0.004
        self.gskernel_cov_lr_delay_mult = 0.01
        self.gskernel_cov_lr_max_steps = 30_000

        self.gskernel_color_lr_init = 0.0008
        self.gskernel_color_lr_final = 0.00005
        self.gskernel_color_lr_delay_mult = 0.01
        self.gskernel_color_lr_max_steps = 30_000


        self.gskernel_featurebank_lr_init = 0.01
        self.gskernel_featurebank_lr_final = 0.00001
        self.gskernel_featurebank_lr_delay_mult = 0.01
        self.gskernel_featurebank_lr_max_steps = 30_000

        self.appearance_lr_init = 0.05
        self.appearance_lr_final = 0.0005
        self.appearance_lr_delay_mult = 0.01
        self.appearance_lr_max_steps = 30_000
        # for anchor densification
        self.start_stat = 500
        self.update_from = 1500
        self.update_interval = 100
        self.update_until = 15_000

        self.min_opacity = 0.005
        self.success_threshold = 0.8
        super().__init__(parser, "Optimization Parameters")


def get_combined_args(parser: ArgumentParser):
    cmdlne_string = sys.argv[1:]
    cfgfile_string = "Namespace()"
    args_cmdline = parser.parse_args(cmdlne_string)

    try:
        cfgfilepath = os.path.join(args_cmdline.model_path, "cfg_args")
        print("Looking for config file in", cfgfilepath)
        with open(cfgfilepath) as cfg_file:
            print("Config file found: {}".format(cfgfilepath))
            cfgfile_string = cfg_file.read()
    except TypeError:
        print("Config file not found at")
        pass
    args_cfgfile = eval(cfgfile_string)

    merged_dict = vars(args_cfgfile).copy()
    for k, v in vars(args_cmdline).items():
        if v != None:
            merged_dict[k] = v
    return Namespace(**merged_dict)
