def read_ij_rois(folder):
    '''
    Sequentially load all imagej '.roi'-files in a folder into a pandas
    dataframe structure.
    '''
    import pandas as pd
    from glob import glob
    from read_roi import read_roi_file
    
    rois_imagej = {}
    for roifile in glob(folder + '*.roi'):
        rois_imagej.update(read_roi_file(roifile))
    df = pd.DataFrame.from_dict(rois_imagej)
    df = df.transpose()
    return df


def coordlists_to_contour(list_x, list_y):
    '''
    This function converts coordinates from imported imagej rois
    to a format that is used by opencv contours. Inputs are
    separate lists of x and y coordinates.
    '''
    import numpy as np

    if len(list_x) != len(list_y):
        print('error: input lists must be the same length!')
    contour = np.zeros([len(list_x), 1, 2])
    for i, (x, y) in enumerate(zip(list_x, list_y)):
        contour[i, 0, 0] = list_x[i]
        contour[i, 0, 1] = list_y[i]
    return contour.astype(int)


def generate_roidata(df):
    '''
    A pandas dataframe (loaded with 'read_ij_rois') containing raw info
    of imagej '.roi'-files is extended with additional columns that
    contain generated/derived data.
    '''
    

    def gen_category(df, filenamecolumn='name', 
                     roicategories=['apical_outline', 
                                    'basal_outline', 'cell']):
        '''Generate new column with roi category from roi names'''
        roitype = []
        for name in df[filenamecolumn]:
            for category in roicategories:
                if category in name:
                    roitype.append(category)
        df['roitype'] = roitype  # create new data column in dataframe
        return df


    def gen_zplane(df, filenamecolumn='name'):
        '''Generate new column with z plane info from roi names'''
        z = []
        for name in df[filenamecolumn]:
            segment = name.split('_')[-2]  # 2nd segment from the end between underscores contains z info
            segment = segment.split('z')[-1]  # Ignore the z in this element
            z.append(int(segment))  # convert to number
        df['z'] = z  # create new data column in dataframe
        return df
    

    def gen_substack(df, filenamecolumn='name'):
        '''Generate new column with substack info from roi names'''
        substack = []
        for name in df[filenamecolumn]:
            segment = name.split('_')[-1]  # 1st segment from the end between underscores contains substack info
            segment = segment.split('sub')[-1]  # Ignore the sub in this element
            substack.append(int(segment))  # convert to number
        df['substack'] = substack  # create new data column in dataframe
        return df
    

    def gen_roicenter(df):
        '''Calculate the coordinates of the roi center'''
        import numpy as np
        
        cnt_mean_x = []
        for xlist in df['x']:
            cnt_mean_x.append(int(np.asarray(xlist).mean()))
        df['x_center'] = cnt_mean_x
        
        cnt_mean_y = []
        for ylist in df['y']:
            cnt_mean_y.append(int(np.asarray(ylist).mean()))
        df['y_center'] = cnt_mean_y
        return df


    def get_outline_contours(df, roitypecolumn='apical_outline'):
        '''
        Create a list with opencv contours for a group of rois inside
        a pandas dataframe.
        '''
        cnt_list = []
        mask = (df['roitype'] == roitypecolumn)
        for cnt_x, cnt_y in zip(df[mask]['x'], df[mask]['y']):
            cnt_list.append(coordlists_to_contour(cnt_x, cnt_y))
        return cnt_list


    def gen_distances(df, bordertypes=['apical_outline',  'basal_outline'],
                      newcolumns=['apical_distance', 'basal_distance']):
        '''
        Calculate the distances from the roi center to apical and basal OE outlines.
        OE thickness is summed from the two distances.
        Relative distance basal/apical and apical/basal are calculated.
        All calculated values are added to the dataframe as new columns.
        '''
        for roitype, column in zip(bordertypes, newcolumns):
            cnt_list = get_outline_contours(df, roitypecolumn=roitype)
            df[column] = calc_distance(df, cnt_list)

        df['OE_thickness'] = abs(df[newcolumns[0]]) + abs(df[newcolumns[1]])
        df['relpos_ba'] = abs(df[newcolumns[1]]) / (abs(df[newcolumns[0]]) + abs(df[newcolumns[1]]))
        df['relpos_ab'] = abs(df[newcolumns[0]]) / (abs(df[newcolumns[0]]) + abs(df[newcolumns[1]]))
        return df


    def calc_distance(df, contours):
        '''
        Calculate the distance of roi centers to a opencv contour of
        OE border.
        '''
        from cv2 import pointPolygonTest

        distance = []
        for z, cx, cy in zip(df.z, df.x_center, df.y_center):
            distance.append(pointPolygonTest(contours[z-1], (cx,cy), True))
        return distance


    def gen_angles(df):
        '''Calculate clockwise angle between rois and center of the OE.'''
        # Create lists of apical boundary central coordinates.
        apicalcxy_list = []
        mask = (df['roitype'] == 'apical_outline')
        for cx, cy in zip(df[mask]['x_center'], df[mask]['y_center']):
            apicalcxy_list.append((cx,cy))

        # Create lists of basal boundary central coordinates.
        basalcxy_list = []
        mask = (df['roitype'] == 'basal_outline')
        for cx, cy in zip(df[mask]['x_center'], df[mask]['y_center']):
            basalcxy_list.append((cx,cy))
        
        # Calculate the mean of the apical/basal outline.
        mask = (df['roitype'] == 'apical_outline')
        mean_apical_center = (df[mask]['x_center'].mean(), df[mask]['y_center'].mean())
        mask = (df['roitype'] == 'basal_outline')
        mean_basal_center = (df[mask]['x_center'].mean(), df[mask]['y_center'].mean())

        # Measure the angle to the mean coordinates of apical/basal boundary center
        angle_list = []
        for z, cx, cy in zip(df.z, df.x_center, df.y_center):
            angle = get_angle((cx,cy), p1=mean_apical_center, p2=mean_basal_center)
            angle_list.append(int(angle))
        df['angle_to_apicalcenter'] = angle_list
        return df


    def get_angle(p0, p1=[0,0], p2=None):
        '''
        Compute angle (in degrees) for p0p1p2 corner
        Inputs:
            p0,p1,p2 - points in the form of [x,y]
        '''
        import numpy as np
        p1 = np.array(p1)
        if p2 is None:
            p2 = p1 + np.array([1, 0])
        v0 = np.array(p0) - np.array(p1)
        v1 = np.array(p2) - np.array(p1)
    
        angle = np.math.atan2(np.linalg.det([v0,v1]),np.dot(v0,v1))
        return np.degrees(angle)


    def check_position(df):
        '''
        Check if cell positions are in the defined boundaries of the OE.
        '''
        mask = (df['apical_distance'] > 0) & (df['roitype'] == 'cell')
        if len(df[mask]) > 0:
            print('WARNING: {} roi(s) with coordinates outside the OE boundaries detected!'.format(len(df[mask])))


    gen_category(df)
    gen_zplane(df)
    gen_substack(df)
    gen_roicenter(df)
    gen_distances(df)
    check_position(df)
    gen_angles(df)
    return df


def process_multiple_folders(folder_list, roifolder='allrois/'):
    '''
    Batch processing (loading rois and generating data)
    of multiple folders (list of folder each as
    "/home/sample1/"). The subfolder containing the imagej
    roi-files can be specified.
    '''
    import pandas as pd
    
    df_merged = []
    for i,folder in enumerate(folder_list):
        print('- Processing folder {}'.format(folder.split('/')[-2]))
        df = read_ij_rois(folder + roifolder)
        df = generate_roidata(df)
        ## label origin in new column
        df['folder'] = folder
        df['sample'] = folder.split('/')[-2]
        df['animal'] = i + 1
        ## merge each df into a common df
        df_merged.append(df)
    return pd.concat(df_merged)


def correct_leftright(df, sidetocorrect='rs'):
    '''
    The olfactory organ samples originate from left and right
    sides of tadpoles. The clockwise angle of cell positions
    needs to be corrected for this.
    The angles calculated for the indicated side are inverted
    to get the mirrored result.
    '''
    # the last two characters (rs/ls) of the folder name indicate side
    df['sample_side'] = [samplename[-2:] for samplename in df['sample']]
    # check if sample is rightsided
    mask = ((df['sample_side'] == sidetocorrect) & (df['roitype'] == 'cell'))
    mirrored_angles = -1 * df[mask]['angle_to_apicalcenter']
    df.loc[mask,'angle_to_apicalcenter'] = mirrored_angles
    return df
