import csv
import math
import os
import sys
import time

import geopandas as gpd
import pandas as pd
from shapely.geometry import Point
from shapely.wkb import dumps
from shapely.wkb import loads

from ricco.util import pinyin
from ricco.util import read_and_rename
import warnings
warnings.filterwarnings('ignore', 'Geometry is in a geographic CRS', UserWarning)

def point_to_geo(df, lng, lat, delt=1):
    df = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df[lng], df[lat]))
    df.crs =  'epsg:4326'
    if delt == 1:
        del df[lng]
        del df[lat]
    return df

# def point_to_geo(df, lng, lat, delt=1):
#     # 转为地理坐标
#     df['geometry'] = gpd.GeoSeries(list(zip(df[lng], df[lat]))).apply(Point)  # 识别经纬度，转换点数据
#     df = gpd.GeoDataFrame(df)  # 转换Geodataframe格式
#     df.crs = 'epsg:4326'  # 定义坐标系WGS84
#     if delt == 1:
#         del df[lng]
#         del df[lat]
#     return df


def city_epsgcode(city):
    citydict = {'上海': 32651, '南京': 32650, '合肥': 32650, '重庆': 32648, '宁波': 32651, '杭州': 32651, '济南': 32650, '沈阳': 32651,
                '广州': 32649, '北京': 32650, '昆明': 32648, '成都': 32648, '青岛': 32651, '长沙': 32649}
    if city in citydict:
        epsgcode = citydict[city]
    else:
        epsgcode = 32651
        print("报错：target文件名不含城市名 or 城市不在 citydict 字典里，请补充; 目前默认投影坐标系为 WGS 84_UTM zone 51N")
    return epsgcode


def projection(gdf, code):
    gdf_prj = gdf.to_crs(epsg=code)
    return gdf_prj


def point_to_geom(df, lng, lat, delt=0):
    # 转为地理坐标
    gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df[lng], df[lat]))
    gdf.crs = 'epsg:4326'  # 定义坐标系WGS84
    if delt == 1:
        del gdf[lng]
        del gdf[lat]
    return gdf


def geo_centroid(df, geom):
    df[geom] = df[geom].apply(lambda x: loads(x, hex=True))
    df = gpd.GeoDataFrame(df, crs='epsg:4326')
    df["lng"] = df.centroid.x
    df["lat"] = df.centroid.y
    df[geom] = df[geom].apply(lambda x: dumps(x, hex=True))
    return df


def min_distance(point, lines):
    return lines.distance(point).min()


def outformat(df, save_file, shp=0):
    if shp == 1:
        # df['geometry'] = df['geometry'].apply(lambda x: loads(x, hex=True))
        gdf = gpd.GeoDataFrame(df, crs='epsg:4326')
        try:
            gdf.to_file(save_file.replace('.csv', '.shp'), encoding='utf-8-sig')
        except:
            gdf.columns = [pinyin(i) for i in gdf.columns]
            gdf.to_file(save_file.replace('.csv', '.shp'), encoding='utf-8')
            print('已将列名转为汉语拼音进行转换')
        print('\n保存路径：', os.path.abspath(save_file.replace('.csv', '.shp')))
        gdf['geometry'] = gdf['geometry'].apply(lambda x: dumps(x, hex=True))
        df = pd.DataFrame(gdf)
    elif shp == 0:
        df = df.drop('geometry', axis=1)
    else:
        df['geometry'] = df['geometry'].apply(lambda x: dumps(x, hex=True))
    return df


def geom_center_point(file):
    df_target = read_and_rename(file)
    df_target = df_target.reset_index()
    df_target = geo_centroid(df_target, "geometry")
    df_target.to_csv(file)


def s_join_list(house_data_buffer, poi_data):
    spacial_join_result = gpd.sjoin(house_data_buffer, poi_data, how='right', op='intersects')
    merge_t = spacial_join_result.groupby(['order']).count()['key'].to_frame().reset_index().rename(
        columns={'key': 'counts'})
    merge_t = merge_t.loc[merge_t["counts"] > 0]
    return merge_t


def target_split_calc_list(gdf_target, df_poi, num_per_part, tcode, R=0):
    n_split = math.ceil(len(gdf_target) / num_per_part)
    print('\n数据将被拆成%s部分；' % n_split)
    lenth = len(gdf_target) / n_split
    print('每一部分约有%s行。\n' % int(lenth))
    modified = (R + 250) * 360 / 31544206
    joined_mt = pd.DataFrame()
    for i in range(n_split):
        sys.stdout.write('\r正在进行第%s/%s部分.........' % (i, n_split))
        sys.stdout.flush()
        # 数据行数最大最小值
        low = round(i * lenth)
        high = round(lenth * (i + 1))
        # 分割数据集
        gdf_part = gdf_target[(gdf_target.index >= low) & (gdf_target.index < high)]
        gdf_part = gdf_part.reset_index().drop('index', axis=1)
        bd = gdf_part.total_bounds
        # print('边界坐标：', bd)
        lng_min, lat_min, lng_max, lat_max = bd[0] - modified, bd[1] - modified, bd[2] + modified, bd[3] + modified
        # 根据边界经纬度删选POI数据
        df_poi_filter = df_poi[(df_poi['lng'] >= lng_min)
                               & (df_poi['lng'] <= lng_max)
                               & (df_poi['lat'] >= lat_min)
                               & (df_poi['lat'] <= lat_max)]
        df_poi_filter = pd.DataFrame(df_poi_filter).reset_index().drop('index', axis=1)
        if len(df_poi_filter) >= 1:
            # print('\nstep2：**********************格式转换***************************')
            if 'geometry' in df_poi_filter.columns:
                df_poi_filter["geometry"] = df_poi_filter["geometry"].apply(
                    lambda x: loads(x, hex=True))
                gdf_poi_filter = gpd.GeoDataFrame(df_poi_filter, crs='epsg:4326')
            else:
                gdf_poi_filter = point_to_geom(df_poi_filter, 'lng', 'lat')
            gdf_poi_filter = projection(gdf_poi_filter, tcode)
            gdf_part = projection(gdf_part, tcode)
            if R > 0:
                gdf_part["geometry"] = gdf_part.buffer(R)
            join_result = s_join_list(gdf_part, gdf_poi_filter)
        else:
            join_result = pd.DataFrame()
        joined_mt = joined_mt.append(join_result, sort=True)
        joined_mt = joined_mt.drop_duplicates()
    print('\n合并后表格：', joined_mt.shape)
    return joined_mt


def circum_pio_num_geo_aoilist(target, AOI, shp=0, num_per_part=150, R=0):
    '''
    :param target: 目标点、线、面面数据，csv文件，须有geometry字段；如果没有geometry，则自动利用经纬度（lng,lat）字段合成geometry
    :param AOI: 主要为栅格面数据和点数据设计，csv文件，须有geometry字段，或经纬度（lng,lat）字段
    :param shp: 是否生成shp文件，默认0不生成，并不含geometry，1则生成shp，其他则不生成shp, 但保留geometry
    '''
    start_all = time.perf_counter()
    print('AOI:', AOI)
    print('step1：读取数据.')
    tcity = os.path.splitext(os.path.basename(target))[0][:2]
    tcode = city_epsgcode(tcity)
    poi_name = os.path.splitext(os.path.basename(AOI))[0].split('_')[-1]
    save_file = os.path.basename(target).replace('.csv', '_' + poi_name + 'list.csv')
    if num_per_part == 0:
        if R == 0:
            num_per_part = 150
        else:
            num_per_part = 23000 - 5900 * (math.log10(R))

    df_target = read_and_rename(target)
    df_target = df_target.reset_index()
    df_target['key'] = 'point_' + df_target['index'].astype('str')
    # target 可以为点线面数据，所以统一用geometry字段计算
    if 'geometry' not in df_target.columns:
        df_target = point_to_geom(df_target, "lng", "lat")
    df_target = df_target[['key', 'geometry']]
    # df_poi 可以为点线面数据，所以统一用geometry字段计算，但用需要lng，lat字段筛选边界
    # 但目前df_poi主要为点，或250米栅格大小的数据
    df_poi = read_and_rename(AOI)
    if 'grid_id' not in df_poi.columns:
        df_poi = df_poi.rename(columns={"name": "grid_id", "名称": "grid_id"})
    df_poi = df_poi.reset_index().rename(columns={'index': 'order'})
    df_poi['order'] = df_poi['order'].astype('str')
    df_poi_grid = df_poi[['order', 'grid_id']]  # 预留数据
    if "lng" not in df_poi.columns:
        df_poi = geo_centroid(df_poi, "geometry")
    df_poi = df_poi[['lng', 'lat', "order", "geometry"]]
    print('step2：格式转换...')
    df_target['geometry'] = df_target['geometry'].apply(lambda x: loads(x, hex=True))
    gdf_target = gpd.GeoDataFrame(df_target)
    gdf_target.crs = 'epsg:4326'
    joined_mt = target_split_calc_list(gdf_target, df_poi, num_per_part, tcode, R)
    joined_mt.drop_duplicates(subset=["order"]).reset_index(drop=True)
    print('合并后表格：', joined_mt.shape)
    if joined_mt.empty:
        print("请注意：数据集为空")
        joined_mt.to_csv(save_file, encoding='utf-8-sig')
        return save_file
    buffer_result = pd.merge(df_poi, joined_mt, left_on="order", right_on="order", how='right')  # 字段匹配
    buffer_result = pd.merge(buffer_result, df_poi_grid, on="order", how='left').drop('order', axis=1)  # 字段匹配
    buffer_result = buffer_result.set_index('grid_id').rename(columns={'counts': poi_name + '_num'})
    buffer_result = outformat(buffer_result, save_file, shp)
    buffer_result = buffer_result.reset_index()
    buffer_result.to_csv(save_file, encoding='utf-8-sig')  # 保存成csv
    print('\n保存路径：', os.path.abspath(save_file))
    end_all = time.perf_counter()
    print('All process has finished. Run time is %s\n\n' % round(end_all - start_all, 2))
    return save_file


def s_join(house_data_buffer, poi_data, mode, var):
    house_data_buffer.crs = 'epsg:4326'
    poi_data.crs = 'epsg:4326'
    spacial_join_result = gpd.sjoin(house_data_buffer, poi_data, how='left', op='intersects')
    merge_t = spacial_join_result.groupby(['key']).count()['order'].to_frame().reset_index().rename(
        columns={'order': 'counts'})
    for mm in mode:
        if mm == 'sum':
            ss = spacial_join_result.groupby(['key'])[var].sum().reset_index()
        if mm == 'mean':
            ss = spacial_join_result.groupby(['key'])[var].mean().reset_index()
        ss.rename(columns={i: i + '_' + mm for i in var}, inplace=True)
        merge_t = merge_t.merge(ss)
    return merge_t


# def s_join(house_data_buffer, poi_data, mode, var):
#     house_data_buffer.crs =  'epsg:4326'
#     poi_data.crs =  'epsg:4326'
#     spacial_join_result = gpd.sjoin(house_data_buffer, poi_data, how='left', op='intersects')  # 空间连接
#     merge_t = spacial_join_result.groupby(['key']).count()['order'].to_frame().reset_index().rename(
#         columns={'order': 'counts'})
#     for mm in mode:
#         if mm == 'sum':
#             ss = spacial_join_result.groupby(['key'])[var].sum().reset_index()  #
#         if mm == 'mean':
#             ss = spacial_join_result.groupby(['key'])[var].mean().reset_index()
#         ss.rename(columns={i: i + '_' + mm for i in var}, inplace=True)
#         merge_t = merge_t.merge(ss)
#     return merge_t


def target_split_calc(gdf_target, df_poi, num_per_part, tcode, mode, var, R=0):
    n_split = math.ceil(len(gdf_target) / num_per_part)
    print('\n数据将被拆成%s部分；' % n_split)
    lenth = len(gdf_target) / n_split
    print('每一部分约有%s行。\n' % int(lenth))
    modified = (R + 250) * 360 / 31544206
    # 以防poi面数据或线数据过大过长，增加一定的modified，暂定250，即栅格的长短
    joined_mt = pd.DataFrame()
    for i in range(n_split):
        sys.stdout.write('\r正在进行第%s/%s部分.........' % (i, n_split))
        sys.stdout.flush()
        # 数据行数最大最小值
        low = round(i * lenth)
        high = round(lenth * (i + 1))
        # 分割数据集
        gdf_part = gdf_target[(gdf_target.index >= low) & (gdf_target.index < high)]
        gdf_part = gdf_part.reset_index().drop('index', axis=1)
        bd = gdf_part.total_bounds
        # print('边界坐标：', bd)
        lng_min, lat_min, lng_max, lat_max = bd[0] - modified, bd[1] - modified, bd[2] + modified, bd[3] + modified
        # 根据边界经纬度删选POI数据
        df_poi_filter = df_poi[(df_poi['lng'] >= lng_min)
                               & (df_poi['lng'] <= lng_max)
                               & (df_poi['lat'] >= lat_min)
                               & (df_poi['lat'] <= lat_max)]
        df_poi_filter = pd.DataFrame(df_poi_filter).reset_index().drop('index', axis=1)
        if len(df_poi_filter) >= 1:
            # print('\nstep2：**********************格式转换***************************')
            if 'geometry' in df_poi_filter.columns:
                df_poi_filter["geometry"] = df_poi_filter["geometry"].apply(lambda x: loads(x, hex=True))
                gdf_poi_filter = gpd.GeoDataFrame(df_poi_filter, crs='epsg:4326')
            else:
                gdf_poi_filter = point_to_geom(df_poi_filter, 'lng', 'lat')  # 转换Geodataframe格式
            gdf_poi_filter = projection(gdf_poi_filter, tcode)
            gdf_part = projection(gdf_part, tcode)
            if R > 0:
                gdf_part["geometry"] = gdf_part.buffer(R)
            join_result = s_join(gdf_part, gdf_poi_filter, mode, var)
        else:
            join_result = pd.DataFrame(gdf_part['key'])
        joined_mt = joined_mt.append(join_result, sort=True)
    print('\n合并后表格：', joined_mt.shape)
    return joined_mt


def circum_pio_num_geo_aoi(target, POI, shp=0, mode=[], var=[], num_per_part=150, R=0):
    '''
    :param target: 目标面数据，csv文件，须有geometry字段；如果没有geometry，则自动利用经纬度合成geometry
    :param POI: 点数据csv文件，须有经纬度；如果没有经纬度，则自动利用geometry字段计算中心点
    :param shp: 是否生成shp文件，默认0不生成，并不含geometry，1则生成shp，其他则不生成shp, 但保留geometry
    :param mode: 可选sum，mean
    :param var: 计算mode的烈面，列表格式
    '''
    start_all = time.perf_counter()
    print('POI:', POI)
    print('step1：读取数据.')
    tcity = os.path.splitext(os.path.basename(target))[0][:2]
    tcode = city_epsgcode(tcity)
    poi_name = os.path.splitext(os.path.basename(POI))[0].split('_')[-1]
    if R > 0:
        save_file = 'r' + str(R) + '_' + os.path.basename(target).replace('.csv', '_' + poi_name + '.csv')
    else:
        save_file = os.path.basename(target).replace('.csv', '_' + poi_name + '.csv')
    if num_per_part == 0:
        if R == 0:
            num_per_part = 150
        else:
            num_per_part = 23000 - 5900 * (math.log10(R))
    df_target = read_and_rename(target)
    df_target = df_target.reset_index()
    df_target['key'] = 'point_' + df_target['index'].astype('str')
    if 'grid_id' not in df_target.columns:
        df_target = df_target.rename(columns={"name": "grid_id", "名称": "grid_id"})
    df_target_grid = df_target[['key', 'grid_id']]  # 预留数据
    if 'geometry' not in df_target.columns:
        df_target = point_to_geom(df_target, "lng", "lat")
    df_target = df_target[['key', 'geometry']]
    df_poi = read_and_rename(POI)
    if 'lat' not in df_poi.columns:
        df_poi = geo_centroid(df_poi, "geometry")
    if mode == []:
        df_poi = df_poi[['lng', 'lat']].reset_index().rename(columns={'index': 'order'})
    else:
        df_poi = df_poi[['lng', 'lat'] + var].reset_index().rename(columns={'index': 'order'})
    df_poi['order'] = df_poi['order'].astype('str')
    print('step2：格式转换...')
    df_target['geometry'] = df_target['geometry'].apply(lambda x: loads(x, hex=True))
    gdf_target = gpd.GeoDataFrame(df_target)
    gdf_target.crs = 'epsg:4326'
    joined_mt = target_split_calc(gdf_target, df_poi, num_per_part, tcode, mode, var, R)
    print('合并后表格：', joined_mt.shape)
    if joined_mt.empty:
        print("请注意：数据集为空")
        joined_mt.to_csv(save_file, encoding='utf-8-sig')
        return save_file

    df_target = df_target[['key', 'geometry']]
    buffer_result = pd.merge(df_target, joined_mt, left_on='key', right_on='key', how='left')
    buffer_result = pd.merge(buffer_result, df_target_grid, on='key', how='left').drop('key', axis=1)
    buffer_result["counts"] = buffer_result["counts"].fillna(0)
    buffer_result = buffer_result.set_index('grid_id').rename(columns={'counts': poi_name + '_num'})
    buffer_result = outformat(buffer_result, save_file, shp)
    buffer_result.to_csv(save_file, encoding='utf-8-sig')  # 保存成csv
    print('\n保存路径：', os.path.abspath(save_file))
    end_all = time.perf_counter()
    print('All process has finished. Run time is %s\n\n' % round(end_all - start_all, 2))
    return save_file


def nearest_neighbor(target, POI):
    # 近邻分析：离最近POI的距离
    print('POI:', POI)
    print('step1：读取数据.')

    tcity = os.path.splitext(os.path.basename(target))[0][:2]
    tcode = city_epsgcode(tcity)
    poi_name = os.path.splitext(os.path.basename(POI))[0].split('_')[-1]
    save_file = target.replace('.csv', '_nearest_') + poi_name + ".csv"

    df_target = read_and_rename(target)
    df_poi = read_and_rename(POI)
    print('step2：格式转换...')
    gdf_target = point_to_geom(df_target, 'lng', 'lat')  # 转换Geodataframe格式
    gdf_poi = point_to_geom(df_poi, 'lng', 'lat')  # 转换Geodataframe格式
    print('step3：投影..........')
    gdf_target = projection(gdf_target, tcode)
    gdf_poi = projection(gdf_poi, tcode)
    v_name = 'near_%s_dist' % poi_name
    print('step4：近邻分析........')
    gdf_target[v_name] = gdf_target.geometry.apply(lambda x: min_distance(x, gdf_poi))
    df_target = outformat(gdf_target, save_file)
    df_target.to_csv(save_file, encoding='utf-8-sig', index=False)
    print('All process has finished.')
    return save_file


def nearest_neighbor_csv_geo(target, POI, shp=0):
    # 近邻分析：离最近POI的距离
    tcity = os.path.splitext(os.path.basename(target))[0][:2]
    tcode = city_epsgcode(tcity)
    poi_name = os.path.splitext(os.path.basename(POI))[0].split('_')[-1]
    print('step1：读取数据.')
    df_target = read_and_rename(target)
    df_poi = read_and_rename(POI)
    print('step2：格式转换...')
    if 'lat' not in df_target.columns:
        df_target = geo_centroid(df_target, "geometry")
    gdf_target = point_to_geom(df_target, 'lng', 'lat')  # 转换Geodataframe格式

    df_poi['geometry'] = df_poi['geometry'].apply(lambda x: loads(x, hex=True))
    gdf_poi = gpd.GeoDataFrame(df_poi)
    gdf_poi.crs =  'epsg:4326'

    print('step3：投影..........')
    gdf_target = projection(gdf_target, tcode)
    gdf_poi = projection(gdf_poi, tcode)
    v_name = 'near_%s_dist' % poi_name
    print('step4：近邻分析........')
    gdf_target[v_name] = gdf_target.geometry.apply(lambda x: min_distance(x, gdf_poi))

    save_file = target.replace('.csv', '_nearest_') + poi_name + ".csv"
    df_target = outformat(gdf_target, save_file, shp)
    df_target.to_csv(save_file, encoding='utf-8-sig', index=False)
    print('All process has finished.')
    return save_file


def mark_tags(point_csv, polygon_csv, col_list=[], save=1):
    ppp = read_and_rename(point_csv)
    for i in col_list:
        if i in ppp.columns:
            ppp.rename(columns={i: str(i) + '_origon'}, inplace=True)
    ppp = point_to_geo(ppp, 'lng', 'lat', delt=0)
    ppp.crs = 'epsg:4326'
    df_lable = read_and_rename(polygon_csv)
    if col_list != []:
        df_lable = df_lable[col_list + ['geometry']]
    df_lable['geometry'] = df_lable['geometry'].apply(lambda x: loads(x, hex=True))
    df_lable = gpd.GeoDataFrame(df_lable)
    df_lable.crs = 'epsg:4326'
    final_file = gpd.sjoin(ppp, df_lable, how='left', op='intersects').drop('index_right', axis=1)
    final_file['geometry'] = final_file['geometry'].apply(lambda x: dumps(x, hex=True))
    if save == 1:
        final_file.to_csv(point_csv, index=0, encoding='GBK')
    return final_file


def mark_tags_df(point_df, polygon_df, col_list=[]):
    col_dict = {'经度': 'lng', '纬度': 'lat', 'lon': 'lng', 'lon_WGS': 'lng', 'lat_WGS': 'lat', 'longitude': 'lng',
                'latitude': 'lat'}
    ppp = point_df.rename(columns=col_dict)
    for i in col_list:
        if i in ppp.columns:
            ppp.rename(columns={i: str(i) + '_origon'}, inplace=True)
    if 'lat' in ppp.columns:
        ppp = point_to_geo(ppp, 'lng', 'lat', delt=0)
    else:
        ppp['geometry'] = ppp['geometry'].apply(lambda x: loads(x, hex=True))
        ppp = gpd.GeoDataFrame(ppp)
    ppp.crs = 'epsg:4326'
    df_lable = polygon_df.rename(columns=col_dict)
    if col_list != []:
        df_lable = df_lable[col_list + ['geometry']]
    df_lable['geometry'] = df_lable['geometry'].apply(lambda x: loads(x, hex=True))
    df_lable = gpd.GeoDataFrame(df_lable)
    df_lable.crs = 'epsg:4326'
    final_file = gpd.sjoin(ppp, df_lable, how='left', op='intersects').drop('index_right', axis=1)
    final_file = final_file.drop('geometry', axis=1)
    return final_file
