import re
import logging
from regexp import *

logger = logging.getLogger(__name__)
dump = logging.getLogger(__name__ + 'dump')


fh = logging.FileHandler('simulations_discrard.log')
formatter = logging.Formatter("%(asctime)s - %(name)s -\
%(levelname)s - %(message)s")
fh.setFormatter(formatter)
dump.addHandler(fh)
dump.propagate = False
dump.setLevel(logging.INFO)


class CorruptedData(Exception):
    def __init__(self, message, data, *args):
        super(CorruptedData, self).__init__(message)
        # clean the data form empty keys
        data_celaned = {k: v for k, v in data.items() if v != []}
        for x in args:
            data_celaned['{}_damage'.format(x)] = True
        self.parsed_data = data_celaned


class EnergyError(Exception):
    def __init__(self, message, data, *args):
        super(EnergyError, self).__init__(message)
        # clean the data form empty keys
        data_celaned = {k: v for k, v in data.items() if v != []}
        for x in args:
            data_celaned['{}_damage'.format(x)] = True
        self.parsed_data = data_celaned


def find_calculations(text, verbose=False):
    """
    given a full file check if it is a bfgs calculation or not
    if it is true it splits the text in a vector of single calculation

    output:

    if_bfgs, [('scf','text'), ('bfgs','text'),..]

    if verbose is True the output will be:
        if_bfgs, [('scf','text'), ('bfgs','text'),..], verbose_dict

    verbose_dict:
        bfgs_error = bool
        bfgs_converged = bool
        --- if bfgs_converged = True
        energy < value
        force < value
        cell < value
        -------------
        bfgs_final_scf = bool

    """
    bfgs_data = {}
    verbose_dict = {}
    for x in bfgs_set:
        data = re.findall(bfgs_set[x], text, re.MULTILINE)
        bfgs_data[x[2:]] = data

    # check if a BFGS calculation is present.
    if len(bfgs_data['start']) == 1:
        if_bfgs = True
    else:
        if_bfgs = False
        if verbose:
            return if_bfgs, [('scf', text)], verbose_dict
        else:
            return if_bfgs, [('scf', text)]

    # split the data in several calculation:
    split_data = []
    # first scf calculation
    tmp = re.split(bfgs_set['r_start'], text, flags=re.MULTILINE)
    split_data.append(('scf', tmp[0]))
    tmp = tmp[1]
    # final scf calculation
    if len(bfgs_data['final_scf']) == 1:
        tmp = re.split(bfgs_set['r_final_scf'], tmp, flags=re.MULTILINE)
        scf_last_text = tmp[1]
        # data for next step
        tmp = tmp[0]
    # final set of coordinate:
    # this is useless because those data are founded again in the last scf step
    if len(bfgs_data['end']) == 1:
        tmp = re.split(bfgs_set['r_end'], tmp, flags=re.MULTILINE)
        # enable this line to get data from 'end of bfgs' to 'a final scf'
        # last_coordinate = tmp[1]
        # data for next step
        tmp = tmp[0]
    # discard the convergence part if present
    if len(bfgs_data['bfgs_converged']) >= 1:
        tmp = re.split(bfgs_set['r_bfgs_converged'], tmp, re.MULTILINE)
        tmp = tmp[0]
    # division of all the other steps
    # this is done in the "number of scf cycles line"
    # I am open to pull requests
    bfgs_text = re.split(bfgs_set['r_bfgs_split'], tmp,
                         flags=re.MULTILINE)

    # remove first line if not too long( usually it is just a set of blank
    # spaces)
    # the first line is composed by
    #     BFGS Geometry Optimization*
    #
    #     number of scf cycles*    =   1
    if len(bfgs_text[0]) < 15:
        logger.info('removed head')
        dump.debug('removed --%r--', bfgs_text[0])
        bfgs_text.pop(0)
    # add the part removed by reg-exp
    bfgs_text = ['     number of scf cycles' + x for x in bfgs_text]

    # put all the split data in the right vector
    for x in bfgs_text:
        split_data.append(('bfgs', x))

    if len(bfgs_data['end']) == 1:
        verbose_dict['bfgs_error'] = False
        verbose_dict['bfgs_converged'] = True if \
            len(bfgs_data['bfgs_converged']) >= 1 else False
        logger.debug(verbose_dict)
        logger.debug(bfgs_data)
        if verbose_dict['bfgs_converged']:
            bfgs_data['criteria'] = bfgs_data['criteria'][0]
            verbose_dict[bfgs_data['criteria'][0]] = bfgs_data['criteria'][1]
            verbose_dict[bfgs_data['criteria'][2]] = bfgs_data['criteria'][3]
            verbose_dict[bfgs_data['criteria'][4]] = bfgs_data['criteria'][5]
    else:
        verbose_dict['bfgs_error'] = True

    if len(bfgs_data['final_scf']) == 1:
        split_data.append(('scf', scf_last_text))

    if verbose:
        return if_bfgs, split_data, verbose_dict
    else:
        return if_bfgs, split_data


def scf_in(text, scf_out_feeder=False):
    """
    given data of SCF calculation output return all the data of that simulation
    raise several error if something went wrong,
    """
    simulation = {}
    simulation['kind'] = 'scf'
    for x in scf_input:
        data = re.findall(scf_input[x], text, re.MULTILINE)
        if len(data) == 1:
            simulation[x[2:]] = data.pop()
        else:
            simulation[x[2:]] = data

    # normalization of cell description
    cell_side = simulation.pop('cell_side')
    simulation['cell_side'] = [x[1:] for x in cell_side]

    # normalization of atom description
    simulation['atom_description'] = {}
    for x, y in zip(simulation.pop('dspecies'),
                    simulation.pop('pseudopotential')):
        if x[0] == y[1]:
            pseudo_name = y[2].split('/')[-1]
            simulation['atom_description'][y[1]] = (y[0], x[1], x[2],
                                                    pseudo_name)
        else:
            raise ValueError('Inconsistency in QE output')
    if len(simulation['atom_description']) < int(simulation['nspecies']):
        dump.info('some atom are not well described')
        dump.debug(text)
        raise CorruptedData('some atom are not well described', simulation,