""" 
Input/output class for the BModes output files
"""
import numpy as np
import pandas as pd
import os

try:
    from .file import File, WrongFormatError, BrokenFormatError
except:
    File=dict
    EmptyFileError    = type('EmptyFileError', (Exception,),{})
    WrongFormatError  = type('WrongFormatError', (Exception,),{})
    BrokenFormatError = type('BrokenFormatError', (Exception,),{})

class BModesOutFile(File):
    """ 
    Read/write a BModes output file. The object behaves as a dictionary.
    
    Main methods
    ------------
    - read, write, toDataFrame, keys
    
    Examples
    --------
        f = BModesOutFile('file.out')
        print(f.keys())
        print(f.toDataFrame().columns)  
    
    """

    @staticmethod
    def defaultExtensions():
        """ List of file extensions expected for this fileformat"""
        return ['.out']

    @staticmethod
    def formatName():
        """ Short string (~100 char) identifying the file format"""
        return 'BModes output file'

    def __init__(self, filename=None, **kwargs):
        """ Class constructor. If a `filename` is given, the file is read. """
        self.filename = filename
        if filename:
            self.read(**kwargs)

    def read(self, filename=None, **kwargs):
        """ Reads the file self.filename, or `filename` if provided """
        
        # --- Standard tests and exceptions (generic code)
        if filename:
            self.filename = filename
        if not self.filename:
            raise Exception('No filename provided')
        if not os.path.isfile(self.filename):
            raise OSError(2,'File not found:',self.filename)
        if os.stat(self.filename).st_size == 0:
            raise EmptyFileError('File is empty:',self.filename)
        # --- Calling (children) function to read
        self._read(**kwargs)

    def write(self, filename=None):
        """ Rewrite object to file, or write object to `filename` if provided """
        if filename:
            self.filename = filename
        if not self.filename:
            raise Exception('No filename provided')
        # Calling (children) function to write
        self._write()

    def _read(self):
        """ Reads self.filename and stores data into self. Self is (or behaves like) a dictionary"""
        # --- Example: 
        #self['data']=[]
        #with open(self.filename, 'r', errors="surrogateescape") as f:
        #    for i, line in enumerate(f):
        #        self['data'].append(line)

        with open(self.filename) as f:
            self['frequencies'] = []
            self['mode_shapes'] = []
            row_string = f.readline()
            if row_string.find('BModes')<0:
                raise WrongFormatError('This file was not generated by BModes, "BModes" is not found on first line')
            while row_string:
                row_string = f.readline()
                freq_id = row_string.find('freq = ')
                if freq_id > 0:
                    self['frequencies'].append(float(row_string[freq_id+7:freq_id+19]))
                    f.readline()
                    f.readline()
                    f.readline()
                    data=[]
                    while True:
                        row_data = f.readline()
                        if len(row_data.strip())==0 or row_data.find('===')==0:
                            break
                        else:
                            data.append(row_data.split())
                    self['mode_shapes'].append(np.asarray(data).astype(float))

    def _write(self):
        """ Writes to self.filename"""
        # --- Example:
        #with open(self.filename,'w') as f:
        #    f.write(self.toString)
        raise NotImplementedError()

    def toDataFrame(self):
        """ Returns object into one DataFrame, or a dictionary of DataFrames"""
        dfs={}
        cols =['span_loc','s-s disp','s-s slope','f-a disp','f-a slope','twist']
        for iMode,mode in enumerate(self['mode_shapes']):
            dfs['Mode_{}'.format(iMode+1)]= pd.DataFrame(data=mode,columns=cols)
        return dfs

    # --- Optional functions
    def __repr__(self):
        """ String that is written to screen when the user calls `print()` on the object. 
        Provide short and relevant information to save time for the user. 
        """
        s='<{} object>:\n'.format(type(self).__name__)
        s+='|Main attributes:\n'
        s+='| - filename: {}\n'.format(self.filename)
        # --- Example printing some relevant information for user
        s+='|Main keys:\n'
        s+='| - frequencies: {}\n'.format(self['frequencies'])
        s+='| - mode_shapes : {} shapes of shape {}x{}\n'.format(len(self['mode_shapes']), *self['mode_shapes'][0].shape)
        s+='|Main methods:\n'
        s+='| - read, write, toDataFrame, keys'
        return s

    def _get_modal_coefficients(x, y, deg=[2, 3, 4, 5, 6]):
        # Normalize x input
        xn = (x - x.min()) / (x.max() - x.min())
        # Get coefficients to 6th order polynomial
        p6 = np.polynomial.polynomial.polyfit(xn, y, deg)
        return p6

    def _identify(self):
        """ identify modes"""
        pass



if __name__ == '__main__':
    f = BModesOutFile('tests/example_files/BModesOut.out')
    df = f.toDataFrame()
    print(f)
    print(df)

