import argparse
import pdal
import os, glob
import json
import pandas as pd
from tqdm import tqdm

class Tiling:
    """
    The tiling operation on las files is done by the pdal splitter filter.
    """
    def __init__(self, input_folder, output_folder, tile_size=10, tile_buffer=0, do_mapping_to_ply=False, do_tile_index=False):
        self.input_folder = input_folder
        self.output_folder = output_folder
        self.output_folder_ply = output_folder + "_ply"
        self.tile_size = tile_size
        self.tile_buffer = tile_buffer
        self.do_mapping_to_ply = do_mapping_to_ply
        self.do_tile_index = do_tile_index
 

    def do_tiling_of_single_file(self, file):
        """
        This function will tile a single file into smaller files
        """

        # get a name for the file 
        file_name_base = os.path.basename(file)
        file_name = os.path.splitext(file_name_base)[0]

        # create a folder for the file
        file_folder = os.path.join(self.output_folder, file_name)
        if not os.path.exists(file_folder):
            os.makedirs(file_folder)

        # create a pipeline for the file
        data = {
            "pipeline":[
                { 
                    "filename":file,
                    #"spatialreference":"EPSG:25832" 
                },
                { 
                    "type":"filters.splitter", 
                    "length":str(self.tile_size), 
                    "buffer":str(self.tile_buffer) 
                },
                {
                    "type":"writers.ply",
                    "storage_mode":"little endian",
                    "filename":file_folder + "/#.ply" 
                  
                }
            ]
        }
        # do the pdal things
        json_string = json.dumps(data)
        pipeline = pdal.Pipeline(json_string)
        tile = pipeline.execute()

        # convert the tiles to ply
        if self.do_mapping_to_ply:
            self.rename_files_in_the_folder_and_extend_with_digits(file_folder)

        # get the tile index
        if self.do_tile_index:
            self.get_tile_index(file_folder)


    def do_tiling_of_files_in_folder(self):
        """
        This function will tile all the files in a folder
        """
        
        # create a destination folder for all the tiles
        if not os.path.exists(self.output_folder):
            os.makedirs(self.output_folder)

        # inform if the folder exists and is not empty
        if os.listdir(self.output_folder):
            print("The output folder is not empty. This might cause problems.")

        # get all the files in the input folder (ply format assummed)
        files = glob.glob(self.input_folder + "/*.ply") 

        # loop through all the files
        for file in tqdm(files):
            self.do_tiling_of_single_file(file)

    def convert_single_file_from_las_to_ply(self, file):
        """
        This function will convert a single file from las to ply
        """

        # get a name for the file 
        file_name_base = os.path.splitext(file)[0]

        # create a pipeline for the file
        data = {
            "pipeline":[
                { # read input data
                    "type":"readers.ply",
                    "filename":file,
                    #"spatialreference":"EPSG:25832" 
                },
                {
                    "type":"writers.ply",
                    "filename":file_name_base +"#.ply" 
                }
            ]
        }
        # do the pdal things
        pipeline = pdal.Pipeline(json.dumps(data))
        pipeline.execute()

    def convert_files_in_folder_from_las_to_ply(self, folder=None):
        """
        This function will convert all the files in a folder from las to ply
        """

        # get all the files in the folder and subfolders las or laz
        files = glob.glob(folder + "/*.las") + glob.glob(folder + "/*.laz")

        # loop through all the files
        for file in tqdm(files):
            self.convert_single_file_from_las_to_ply(file)

    def get_tile_index(self, folder=None):
        """
        This function will create a tile index for all the tiles in a folder
        """
        tile_index = pd.DataFrame(columns=['tile', 'x', 'y'])
        files = glob.glob(os.path.join(folder, "*.ply"))

        for i, ply in tqdm(enumerate(files), total=len(files)):
            T = int(os.path.split(ply)[1].split('.')[0])
            reader = {"type":f"readers{os.path.splitext(ply)[1]}", "filename":ply}
            stats =  {"type":"filters.stats", "dimensions":"X,Y"}
            JSON = json.dumps([reader, stats])
            pipeline = pdal.Pipeline(JSON)
            pipeline.execute()
            X = pipeline.metadata['metadata']['filters.stats']['statistic'][0]['average']
            Y = pipeline.metadata['metadata']['filters.stats']['statistic'][1]['average']
            tile_index.loc[i, :] = [T, X, Y]   

        tile_index.to_csv(os.path.join(folder, 'tile_index.dat'), index=False, header=False, sep=' ')
    
    def remove_files_in_folder(self, folder=None, file_type=None):
        """
        This function will remove all the files in a folder of a certain type
        """
        files = glob.glob(folder + "/*." + file_type)
        for file in files:
            os.remove(file)

    def three_digits(self, number):
        """
        This function will add leading zeros to a number
        """
        if number < 10:
            return "00" + str(number)
        elif number < 100:
            return "0" + str(number)
        else:
            return str(number)

    def rename_files_in_the_folder_and_extend_with_digits(self, folder=None):
        """
        This function will rename all the files in a folder
        """
        files = glob.glob(folder + "/*.ply")
        for i, file in enumerate(files):
            os.rename(file, os.path.join(folder, self.three_digits(i) + ".ply"))

    def run(self):
        # self.convert_files_in_folder_from_las_to_ply(self.input_folder)
        self.do_tiling_of_files_in_folder()


def main(input_folder, output_folder, tile_size=10, tile_buffer=0, do_mapping_to_ply=False, do_tile_index=False):
    """
    This function will tile all the files in a folder
    """

    tiling = Tiling(input_folder, output_folder, tile_size, tile_buffer, do_mapping_to_ply, do_tile_index)
    tiling.run()


if __name__ == "__main__":
    # read command line arguments
    parser = argparse.ArgumentParser(description="Tiling. Needs a folder with ply files.")
    parser.add_argument(
        "-i",
        "--input_folder",
        type=str,
        help="Input folder containing las files",
        required=True,
    )
    parser.add_argument(
        "-o",
        "--output_folder",
        type=str,
        help="Output folder containing las files",
        required=True,
    )   
    parser.add_argument(
        "-t",
        "--tile_size",
        type=int,
        help="Tile size in meters",
        required=False,
        default=10,
    )
    parser.add_argument(
        "-b",
        "--tile_buffer",
        type=int,
        help="Tile buffer in meters",
        required=False,
        default=0,
    )
    parser.add_argument(
        "-m",
        "--do_mapping_to_ply",
        type=bool,
        help="Do mapping to ply",
        required=False,
        default=True,
    )
    parser.add_argument(
        "-g",
        "--do_tile_index",
        type=bool,
        help="Get tile index",
        required=False,
        default=True,
    )

    args = parser.parse_args()

    main(
        args.input_folder, 
        args.output_folder, 
        args.tile_size, 
        args.tile_buffer, 
        args.do_mapping_to_ply,
        args.do_tile_index
        )