import abc, sys, io
import numpy as np
import pandas as pd
from cvxopt import solvers, matrix
from numpy import linalg
from scipy.stats import multivariate_normal
from ..utils import create_logger, is_level_debug
from classifip.representations.voting import Scores

try:
    import matlab.engine
except Exception as e:
    print("MATLAB not installed in host.")

def is_sdp_symmetric(x):
    def is_pos_def(x):
        return np.all(np.linalg.eigvals(x) > 0)

    def check_symmetric(x, tol=1e-8):
        return np.allclose(x, x.T, atol=tol)

    return is_pos_def(x) and check_symmetric(x)

# First version maximality criterion
def inference_maximal_criterion(lower, upper, clazz):
    pairwise_comparison = []
    pairwise_indifferent = []
    # O(n^2), n: number of classes
    for idl, l in enumerate(lower):
        for idu, u in enumerate(upper):
            if idl != idu:
                if l - u > 0:
                    pairwise_comparison.append([clazz[idl], clazz[idu]])
                elif lower[idu] - upper[idl] <= 0:
                    pairwise_indifferent.append([clazz[idl], clazz[idu]])

    top_max = dict()
    down_min = dict()
    # O(l), l: number of pairwise comparison
    for pairwise in pairwise_comparison:
        if pairwise[0] not in down_min:
            if pairwise[1] in top_max: top_max.pop(pairwise[1], None)
            top_max[pairwise[0]] = 1
            down_min[pairwise[1]] = 0
        else:
            down_min[pairwise[1]] = 0

    for pairwise in pairwise_indifferent:
        if pairwise[0] in top_max or pairwise[1] in top_max:
            if pairwise[0] not in down_min and pairwise[0] not in top_max:
                top_max[pairwise[0]] = 1
            if pairwise[1] not in down_min and pairwise[1] not in top_max:
                top_max[pairwise[1]] = 1

    maximal_elements = list(top_max.keys())
    # If there is no maximals, so maximal elements are all classes.
    return clazz if len(maximal_elements) == 0 else maximal_elements

class DiscriminantAnalysis(metaclass=abc.ABCMeta):
    def __init__(self, init_matlab=False, add_path_matlab=None):
        """
        :param init_matlab: init engine matlab
        :param DEBUG: logger debug log for computation
        """
        # Starting matlab environment
        if init_matlab:
            self._eng = matlab.engine.start_matlab()
            self._add_path_matlab = [] if add_path_matlab is None else add_path_matlab
            for _in_path in self._add_path_matlab: self._eng.addpath(_in_path)
        else:
            self._eng = None
        self._N, self._p = None, None
        self._data = None
        self._ell = None
        self._clazz = None
        self._nb_clazz = None
        self._prior = dict()
        # icov: inverse covariance,
        # sdp: bool if it's semi-definite positive symmetric
        self._gp_mean, self._gp_sdp = dict(), dict()
        self._gp_cov, self._gp_icov = dict(), dict()
        self._mean_lower, self._mean_upper = None, None
        self.__nb_rebuild_opt = 0
        self._logger = None

    def get_data(self):
        return self._data

    def get_clazz(self):
        return self._clazz

    def get_ell(self):
        return self._ell

    def get_mean_by_clazz(self, clazz):
        if clazz not in self._gp_mean:
            self._gp_mean[clazz] = self.__mean_by_clazz(clazz)
        return self._gp_mean[clazz]

    def get_cov_by_clazz(self, clazz):
        if clazz not in self._gp_cov:
            cov_clazz = self._cov_by_clazz(clazz)
            self._gp_cov[clazz] = cov_clazz
            if linalg.cond(cov_clazz) < 1 / sys.float_info.epsilon:
                self._gp_icov[clazz] = linalg.inv(cov_clazz)
            else:  # computing pseudo-inverse of a singular covariance matrix
                self._gp_icov[clazz] = linalg.pinv(cov_clazz)

            # verify if pseudo-inverse matrix is semi-definite positive symmetric
            # @review: numerical tolerance 1e-7 all matrix are symmetric, is good so much decimal precision?
            self._gp_sdp[clazz] = is_sdp_symmetric(self._gp_icov[clazz])
        return self._gp_cov[clazz], self._gp_icov[clazz]

    def probability_density_gaussian(self, mean, cov, query, log_p=False):
        """
        This method calculate the multivariate Gaussian probability of event (query)
        when the covariance matrix is positive semi-definite matrix.

        This one also does take into consideration if the covariance matrix is singular,
        that means that the multivariate Gaussian is degenerate and the calculation must be
        done differently.
            f(x)= \det^{*}(2\pi*\Sigma)^{-\frac {1}{2}} * exp(-0.5*(x -\mu)^T \Sigma^{+} (x - \mu))
        where:
            det^{*} is the pseudo-determinant of Sigma, and
            Sigma^{+} is the generalized inverse

        :param mean: mean of
        :param cov: covariance matrix (positive semi-definite or singular)
        :param query: event of multivariate Gaussian probability
        :param log_p: boolean if it is log-probability or just probability
        :return: the probability of 'query' event
        """
        if log_p:
            probability = multivariate_normal.logpdf(query, mean=mean, cov=cov, allow_singular=True)
        else:
            probability = multivariate_normal.pdf(query, mean=mean, cov=cov, allow_singular=True)
        self._logger.debug("Computing the (log-)probability (%s, %s):", log_p, probability)
        return probability

    def fit_max_likelihood(self, query):
        means, probabilities = dict(), dict()
        for clazz in self._clazz:
            cov, inv = self.get_cov_by_clazz(clazz)
            means[clazz] = self.get_mean_by_clazz(clazz)
            probabilities[clazz] = self.probability_density_gaussian(means[clazz], cov, query)
        return means, probabilities

    def learn(self, learn_data_set=None, ell=2, X=None, y=None):
        """
        :param learn_data_set: (X, y): X matrix of features and y category by number instances
        :param ell: imprecise value for mean parameter
        :param X:
        :param y:
        :return:
        """
        assert ell > 10 ^ -6, "Using a positive value ELL, otherwise using precise method LDA/QDA."
        self._ell = ell
        self._gp_mean, self._gp_sdp = dict(), dict()
        self._gp_cov, self._gp_icov = dict(), dict()
        self._mean_lower, self._mean_upper = dict(), dict()

        # transformation of Arff data to feature matrix and vector category
        if learn_data_set is not None:
            learn_data_set = np.array(learn_data_set.data)
            X = learn_data_set[:, :-1]
            y = learn_data_set[:, -1]
        elif X is not None and y is not None:
            self._logger.info("Loading training data set from (X,y) couples.")
        else:
            raise Exception('Not training data set setting.')

        # Create data set in Panda frame structure
        self._N, self._p = X.shape
        assert self._N == len(y), "Size X and y is not equals."
        y = np.array(y) if type(y) is list else y
        self._data = pd.concat([pd.DataFrame(X, dtype="float64"), pd.Series(y, dtype="category")], axis=1)
        columns = ["x" + i for i in map(str, range(self._p))]  # create columns names
        columns.extend('y')
        self._data.columns = columns
        self._clazz = np.array(self._data.y.cat.categories.tolist())
        self._nb_clazz = len(self._clazz)

        # Estimation of imprecise/precise parameters
        for clazz in self._clazz:
            mean = self.get_mean_by_clazz(clazz)
            self.get_cov_by_clazz(clazz)
            nb_by_clazz = self._nb_by_clazz(clazz)
            self._prior[clazz] = nb_by_clazz / self._N
            self._mean_lower[clazz] = (-self._ell + nb_by_clazz * mean) / nb_by_clazz
            self._mean_upper[clazz] = (self._ell + nb_by_clazz * mean) / nb_by_clazz

    def evaluate(self, query, method="quadratic", criterion="maximality", log_probability=False):
        """
        This method is ..

        :param query: new unlabeled instance
        :param method: optimization method for computing optimal upper bound mean.
        :param criterion: interval_dominance if criterion decision performs with Interval dominance,
                otherwise maximality criterion
        :param log_probability: calculate the maximality decision with log probability
        :return: tuple composed of set-valued categories and (lower,upper) bound probabilities.
        """
        bounds = dict((clazz, dict()) for clazz in self._clazz)
        precise_probas = dict()
        eng_session = self._eng

        def __get_bounds(clazz, bound="inf"):
            return bounds[clazz][bound] if bound in bounds[clazz] else None

        def __put_bounds(probability, estimator, clazz, bound="inf"):
            bounds[clazz][bound] = (probability, estimator)

        def __probability(_self, query, clazz, inv, cov, mean_lower, mean_upper, bound="inf", log_p=False):

            if __get_bounds(clazz, bound) is None:
                q = -1 * (query.T @ inv)
                _self._logger.debug("[BOUND_ESTIMATION:%s] Q matrix: %s", bound, inv)
                _self._logger.debug("[BOUND_ESTIMATION:%s] q vector: %s", bound, q)
                _self._logger.debug("[BOUND_ESTIMATION:%s] Lower Bound: %s", bound, mean_lower)
                _self._logger.debug("[BOUND_ESTIMATION:%s] Upper Bound %s", bound, mean_upper)
                if bound == "inf":
                    estimator = _self.infimum_estimation(inv, q, mean_lower, mean_upper, eng_session, clazz)
                else:
                    estimator = _self.supremum_estimation(inv, q, mean_lower, mean_upper, clazz, method)
                prob = _self.probability_density_gaussian(np.array(estimator), cov, query, log_p=log_p)
                __put_bounds(prob, estimator, clazz, bound)
            return __get_bounds(clazz, bound)

        if criterion == "maximality":
            for clazz in self._clazz:
                mean_lower = self._mean_lower[clazz]
                mean_upper = self._mean_upper[clazz]
                cov, inv = self.get_cov_by_clazz(clazz)
                p_sub, estimator = __probability(self, query, clazz, inv, cov,
                                                 mean_lower, mean_upper,
                                                 bound='sup', log_p=log_probability)
                __put_bounds(p_sub, estimator, clazz, "sup")
                precise_probas[clazz] = self.probability_density_gaussian(self._gp_mean[clazz],
                                                                          cov, query,
                                                                          log_p=log_probability)

            C = set(self._clazz)
            Z = set([])
            while len(C - Z) > 0:
                max_clazz = max(precise_probas, key=precise_probas.get)
                mean_lower = self._mean_lower[max_clazz]
                mean_upper = self._mean_upper[max_clazz]
                cov, inv = self.get_cov_by_clazz(max_clazz)
                p_inf, _ = __probability(self, query, max_clazz, inv, cov,
                                         mean_lower, mean_upper,
                                         bound='inf', log_p=log_probability)
                nopt_clazz = set([])
                for clazz in C - {max_clazz}:
                    p_sup, _ = __get_bounds(clazz=clazz, bound="sup")

                    # if p_inf * self._prior[max_clazz] - p_sup * self._prior[clazz] <= 0: # precise 10e-18 instead 0
                    maximility = ((p_inf * self._prior[max_clazz] - p_sup * self._prior[clazz]) <= 0)
                    if log_probability:
                        maximility = (np.log(self._prior[max_clazz]) + p_inf <= p_sup + np.log(self._prior[clazz]))

                    self._logger.debug("Query: (%s) preferred/indifferent to (%s) according to maximality decision (%s)",
                                       max_clazz, clazz, maximility)
                    # (maximility:true) labels indifferent to maximal-label-choice for assessment maximility
                    if maximility:
                        nopt_clazz.add(clazz)
                    else:
                        precise_probas.pop(clazz, None)
                del precise_probas[max_clazz]
                Z.add(max_clazz)
                nopt_clazz.add(max_clazz)
                C = nopt_clazz.copy()

            return list(C), bounds

        elif criterion == "interval_dominance" or criterion == "maximality_v1":
            if log_probability:
                raise Exception("Criterion decision does not support with log-probability!!")

            lower, upper = [], []
            for clazz in self._clazz:
                mean_lower = self._mean_lower[clazz]
                mean_upper = self._mean_upper[clazz]
                cov, inv = self.get_cov_by_clazz(clazz)
                p_inf, _ = __probability(self, query, clazz, inv, cov, mean_lower, mean_upper, bound='inf')
                p_up, _ = __probability(self, query, clazz, inv, cov, mean_lower, mean_upper, bound='sup')
                lower.append(p_inf * self._prior[clazz])
                upper.append(p_up * self._prior[clazz])

            if criterion == "interval_dominance":
                score = Scores(np.c_[lower, upper])
                return self._clazz[(score.nc_intervaldom_decision() > 0)], bounds
            else:
                return inference_maximal_criterion(lower, upper, self._clazz), bounds
        else:
            raise Exception("Decision criterion not implemented yet or another bug!!")

    def get_bound_means(self, clazz):
        return self._mean_lower[clazz], self._mean_upper[clazz]

    def _nb_by_clazz(self, clazz):
        assert self._data is not None, "It's necessary to firstly declare a data set."
        return len(self._data[self._data.y == clazz])

    def __mean_by_clazz(self, clazz):
        return self._data[self._data.y == clazz].iloc[:, :-1].mean().as_matrix()

    def _cov_by_clazz(self, clazz):
        _sub_data = self._data[self._data.y == clazz].iloc[:, :-1]
        _n, _p = _sub_data.shape
        if _n > 1:
            return _sub_data.cov().as_matrix()
        else:
            ## Bug: Impossible to compute covariance of just ONE instance
            ## assuming an identity covariance matrix
            ## e.g. _sub_data.shape = (1, 8)
            raise Exception("it has only 1 sample in class, covariance is ill defined.")

    def supremum_estimation(self, Q, q, mean_lower, mean_upper, clazz, method="quadratic"):
        """
        .. warning::
            * this case does not convergence with quadratic method
                Q = np.array([[19.42941344,  -12.9899322, -5.1907171,   -0.25782677],
                              [-12.9899322,  15.97805787, 1.87087712,   -6.72150886],
                              [-5.1907171,   1.87087712,  36.99333345,  -16.21139038],
                              [-0.25782677,  -6.72150886, -16.21139038, 103.0762929]])
                q = np.array([-45.3553788, 26.52058282, -99.63769322, -61.59361441])
                mean_lower = np.array([4.94791667, 3.36875, 1.41666667, 0.19375])
                mean_upper = np.array([5.04375, 3.46458333, 1.5125, 0.28958333])

        :param Q:
        :param q:
        :param mean_lower:
        :param mean_upper:
        :param clazz:
        :param method:
        :return:
        """
        self._logger.debug("[iS-Inverse-Covariance-SDP] (%s, %s)", clazz, self._gp_sdp[clazz])
        if self._gp_sdp[clazz]:
            def __min_convex_qp(A, q, lower, upper, d):


from collections import defaultdict
from itertools import combinations

from django import forms
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.formtools.wizard.views import SessionWizardView
from django.shortcuts import render_to_response

from catmaid.control.classification import get_classification_links_qs, \
        link_existing_classification
from catmaid.models import ClassInstance, Project

from taggit.models import TaggedItem

TEMPLATES = {"settings": "catmaid/classification/admin_settings.html",
             "taggroups": "catmaid/classification/admin_setup_tag_groups.html",
             "confirmation": "catmaid/classification/admin_confirmation.html"}

class SettingsForm(forms.Form):
    add_supersets = forms.BooleanField(required=False, initial=True,
        help_text="This field indicates if a tag set should include only " \
            "strictly projects of an actually available tag set. Or if " \
            "projects of super sets should get added. Super sets are tag " \
            "sets that include the tag set under consideration.")
    respect_superset_graphs = forms.BooleanField(required=False,
        help_text="If projects of super sets are added, this setting indicates " \
            "if classification graphs linked to those projects should actually " \
            "be considered missing if they are not linked in non-super set " \
            "projects. When not checked, only classification graphs linked to " \
            "from non-super set projects can be considered missing (also by " \
            "super set projects).")

class TagGroupSelectionForm(forms.Form):
    tag_groups = forms.MultipleChoiceField(required=False,
        widget=forms.CheckboxSelectMultiple())

class ConfirmationForm(forms.Form):
    pass

def get_tag_sets(add_supersets=False, prefetch=True):
    tag_sets=defaultdict(set)
    tag_supersets=defaultdict(set)
    project_ids = list(Project.objects.all().values_list('id', flat=True))

    # Build tag index
    ct = ContentType.objects.get_for_model(Project)
    tag_links = TaggedItem.objects.filter(content_type=ct) \
        .values_list('object_id', 'tag__name')
    tag_index = defaultdict(set)
    for pid, t in tag_links:
        tag_index[pid].add(t)

    # Build up data structure that maps tag sets to
    # projects. These projects include projects with
    # tag supersets.
    for p, ts in tag_index.items():
        tag_sets[frozenset(ts)].add(p)

    if add_supersets:
        for a, b in combinations(tag_sets, 2):
            if a < b:
                tag_sets[a].update(tag_sets[b])
                tag_supersets[a].update(tag_sets[b])
            elif b < a:
                tag_sets[b].update(tag_sets[a])
                tag_supersets[b].update(tag_sets[a])

    return project_ids, tag_sets, tag_supersets

def generate_tag_groups(add_supersets=True, respect_superset_graphs=False):
    """ This creates a tag sets dictionary. It ignores projects without any
    tags.
    """
    project_ids, tag_sets, tag_supersets = get_tag_sets(add_supersets)

    # Get a query set that retrieves all CiCi links for all project ids at once
    workspace = settings.ONTOLOGY_DUMMY_PROJECT_ID

    # We are retrieving the classification links of *all* projects. The query
    # will therefore be much faster, if we get the inverse set of the empty
    # set instead of explicitely requesting the project_ids to be in
    # project_ids..
    links_qs = get_classification_links_qs(workspace, [], inverse=True)

    # Make sure the the project ids and the classification root ids are
    # prefetched
    links_qs = links_qs.select_related('class_instance_a__project__id',
        'class_instance_b__id')
    # Execute the query set to build a look up table
    projects_to_cls_links = {}
    for cici in links_qs:
        cls_links = projects_to_cls_links.get(cici.class_instance_a.project.id)
        if not cls_links:
            cls_links = set()
            projects_to_cls_links[cici.class_instance_a.project.id] = cls_links
        cls_links.add(cici.class_instance_b.id)

    # Test which groups of projects belonging to a particular tag group,
    # have non-uniform classification graph links
    # TODO: Test for independent *and* dependent workspace
    available_tag_groups = {}
    for tags, projects in tag_sets.items():
        differs = False
        cg_roots = set()
        projects_cgroots = {}
        # Collect all classification roots in this tag group
        for pid in projects:
            try:
                # Get set of CiCi links for current project ID
                croots = projects_to_cls_links[pid]
            except KeyError:
                # Use an empty set if there are no CiCi links for the
                # current project.
                croots = set()

            # Remember roots for this projects
            projects_cgroots[pid] = {
                'linked': croots,
                'missing': [],
                'workspace': workspace,
            }
            # Add classification graphs introduced by supersets to the expected
            # graphs in this tag set.
            if pid not in tag_supersets[tags] or respect_superset_graphs:
                    cg_roots.update(croots)
        # Check if there are updates needed for some projects
        num_differing = 0
        meta = []
        for pid in projects_cgroots:
            croots = projects_cgroots[pid]['linked']
            diff = cg_roots - croots
            if len(diff) > 0:
                differs = True
                projects_cgroots[pid]['missing'] = diff
                num_differing = num_differing + 1
                strdiff = ", ".join([str(cg) for cg in diff])
                meta.append("[PID: %s Missing: %s]" % (pid, strdiff))
        # If there is a difference, offer this tag group
        # for selection.
        if differs:
            # Generate a string representation of the tags and use
            # it as index for a project classification.
            taglist = list(tags)
            taglist.sort()
            name = ", ".join([t for t in taglist])
            # Fill data structure for later use
            available_tag_groups[name] = {
                'project_cgroots': projects_cgroots,
                'all_cgroots': cg_roots,
                'num_differing': num_differing,
                'meta': meta,
            }
    return available_tag_groups

class ClassificationAdminWizard(SessionWizardView):

    def get_template_names(self):
        return [TEMPLATES[self.steps.current]]

    def get_context_data(self, **kwargs):
        context = super(ClassificationAdminWizard, self).get_context_data(**kwargs)
        context['catmaid_url'] = settings.CATMAID_URL

        # The project links selection needs some extra context
        extra_context = {}
        if self.steps.current == "taggroups":
            tag_groups = generate_tag_groups(True, False)
            extra_context['num_tag_groups'] = len(self.get_tag_group_list(tag_groups))
        if self.steps.current == "confirmation":
            # Get all selected tag groups
            extra_context['tag_groups'] = self.get_selected_tag_groups()
        context.update(extra_context)

        return context

    def get_tag_group_list(self, available_tag_groups):
        """ Returns a list of tuples that represent the currently
        available tag groups.
        """
        # Create the tag group tuple list
        tag_group_list = []
        for eg, group in available_tag_groups.items():
            name = eg + " (" + str(group['num_differing']) + "/" + \
                str(len(group['project_cgroots'])) + " differ: " + \
                ", ".join(group['meta']) + ")"
            tag_group_list.append( (eg, name) )

        return tag_group_list

    def get_form(self, step=None, data=None, files=None):
        form = super(ClassificationAdminWizard, self).get_form(step, data, files)
        # Determine step if not given
        if step is None:
            step = self.steps.current
        if step == "taggroups":
            # Update the tag groups list and select all by default
            add_supersets = self.get_cleaned_data_for_step('settings')['add_supersets']
            respect_superset_graphs = self.get_cleaned_data_for_step('settings')['respect_superset_graphs']
            # Store all available tag groups to be usable in the result page
            self.available_tag_groups = generate_tag_groups(add_supersets,
                respect_superset_graphs)
            tag_groups_tuples = self.get_tag_group_list(self.available_tag_groups)
            form.fields["tag_groups"].choices = tag_groups_tuples
            form.fields['tag_groups'].initial = [tg[0] for tg in tag_groups_tuples]
        return form

    def get_selected_tag_groups(self):
        tag_group_ids = self.get_cleaned_data_for_step('taggroups')['tag_groups']
        selected_tag_groups = {}
        for tid in tag_group_ids:
            selected_tag_groups[tid] = self.available_tag_groups[tid]
        return selected_tag_groups

    def done(self, form_list, **kwargs):
        """ Will add all missing links, stored in the tag groups field.
        """
        tag_groups = self.get_selected_tag_groups()
        unified_tag_groups = {}
        num_added_links = 0
        failed_links = {}
        for eg, group in tag_groups.items():
            for pid, pdata in group['project_cgroots'].items():
                # Iterate missing links
                for ml in pdata['missing']:
                    try:
                        # Add missing link
                        wid = pdata['workspace']
                        oroot = ClassInstance.objects.get(pk=ml)
                        p = Project.objects.get(pk=pid)
                        link_existing_classification(wid, self.request.user, p, oroot)
                        unified_tag_groups[eg] = group
                        num_added_links = num_added_links + 1
                    except Exception as e:
                        failed_links[ml] = e

        # Show final page
        return render_to_response('catmaid/classification/admin_done.html', {
            'tag_groups': unified_tag_groups,
            'num_added_links': num_added_links,
            'failed_links': failed_links,
        })

def classification_admin_view(request, *args, **kwargs):
    """ Wraps the class based ClassificationAdminWizard view in
    a function based view.
    """
    forms = [("settings", SettingsForm),
             ("taggroups", TagGroupSelectionForm),
             ("confirmation", ConfirmationForm)]


# This program is free software; you can redistribute it and/or modify
# it under the terms of the (LGPL) GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library Lesser General Public License for more details at
# ( http://www.gnu.org/licenses/lgpl.html ).
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# written by: Jeff Ortel ( jortel@redhat.com )

"""
Date & time related suds Python library unit tests.

Implemented using the 'pytest' testing framework.

"""

if __name__ == "__main__":
    try:
        import pytest
        pytest.main(["--pyargs", __file__])
    except ImportError:
        print("'py.test' unit testing framework not available. Can not run "
            "'{}' directly as a script.".format(__file__))
    import sys
    sys.exit(-2)

import datetime
from suds.sax.date import Timezone
import suds.xsd.sxbuiltin
import tests

tests.setup_logging()

class Date(suds.xsd.sxbuiltin.XDate):
    def __init__(self):
        pass

class Time(suds.xsd.sxbuiltin.XTime):
    def __init__(self):
        pass

class DateTime(suds.xsd.sxbuiltin.XDateTime):
    def __init__(self):
        pass

class TestDate:

    def testSimple(self):
        ref = datetime.date(1941, 12, 7)
        s = '%.4d-%.2d-%.2d' % (ref.year, ref.month, ref.day)
        xdate = Date()
        d = xdate.translate(s)
        assert d == ref

    def testNegativeTimezone(self):
        self.equalsTimezone(-6)

    def testPositiveTimezone(self):
        self.equalsTimezone(6)

    def testUtcTimezone(self):
        Timezone.LOCAL = lambda tz: 0
        ref = datetime.date(1941, 12, 7)
        s = '%.4d-%.2d-%.2dZ' % (ref.year, ref.month, ref.day)
        xdate = Date()
        d = xdate.translate(s)
        assert d == ref

    def equalsTimezone(self, tz):
        Timezone.LOCAL = lambda cls: tz
        ref = datetime.date(1941, 12, 7)
        s = '%.4d-%.2d-%.2d%+.2d:00' % (ref.year, ref.month, ref.day, tz)
        xdate = Date()
        d = xdate.translate(s)
        assert d == ref

class TestTime:

    def testSimple(self):
        ref = datetime.time(10, 30, 22)
        s = '%.2d:%.2d:%.2d' % (ref.hour, ref.minute, ref.second)
        xtime = Time()
        t = xtime.translate(s)
        assert t == ref

    def testSimpleWithShortMicrosecond(self):
        ref = datetime.time(10, 30, 22, 34)
        s = '%.2d:%.2d:%.2d.%4.d' % (ref.hour, ref.minute, ref.second, ref.microsecond)
        xtime = Time()
        t = xtime.translate(s)
        assert t == ref

    def testSimpleWithMicrosecond(self):
        ref = datetime.time(10, 30, 22, 999999)
        s = '%.2d:%.2d:%.2d.%4.d' % (ref.hour, ref.minute, ref.second, ref.microsecond)
        xtime = Time()
        t = xtime.translate(s)
        assert t == ref

    def testSimpleWithLongMicrosecond(self):
        ref = datetime.time(10, 30, 22, 999999)
        s = '%.2d:%.2d:%.2d.%4.d' % (ref.hour, ref.minute, ref.second, int('999999999'))
        xtime = Time()
        t = xtime.translate(s)
        assert t == ref

    def testPositiveTimezone(self):
        self.equalsTimezone(6)

    def testNegativeTimezone(self):
        self.equalsTimezone(-6)

    def testUtcTimezone(self):
        Timezone.LOCAL = lambda cls: 0
        ref = datetime.time(10, 30, 22)
        s = '%.2d:%.2d:%.2dZ' % (ref.hour, ref.minute, ref.second)
        xtime = Time()
        t = xtime.translate(s)
        assert t == ref

    def equalsTimezone(self, tz):
        Timezone.LOCAL = lambda cls: tz
        ref = datetime.time(10, 30, 22)
        s = self.strTime(ref.hour, ref.minute, ref.second, tz)
        xtime = Time()
        t = xtime.translate(s)
        assert t == ref

    def testConvertNegativeToGreaterNegative(self):
        Timezone.LOCAL = lambda tz: -6
        ref = datetime.time(10, 30, 22)
        s = self.strTime(ref.hour, ref.minute, ref.second, -5)
        xtime = Time()
        t = xtime.translate(s)
        assert ref.hour-1 == t.hour
        assert ref.minute == t.minute
        assert ref.second == t.second

    def testConvertNegativeToLesserNegative(self):
        Timezone.LOCAL = lambda tz: -5
        ref = datetime.time(10, 30, 22)
        s = self.strTime(ref.hour, ref.minute, ref.second, -6)
        xtime = Time()
        t = xtime.translate(s)
        assert ref.hour+1 == t.hour
        assert ref.minute == t.minute
        assert ref.second == t.second

    def testConvertPositiveToGreaterPositive(self):
        Timezone.LOCAL = lambda tz: 3
        ref = datetime.time(10, 30, 22)
        s = self.strTime(ref.hour, ref.minute, ref.second, 2)
        xtime = Time()
        t = xtime.translate(s)
        assert ref.hour+1 == t.hour
        assert ref.minute == t.minute
        assert ref.second == t.second

    def testConvertPositiveToLesserPositive(self):
        Timezone.LOCAL = lambda tz: 2
        ref = datetime.time(10, 30, 22)
        s = self.strTime(ref.hour, ref.minute, ref.second, 3)
        xtime = Time()
        t = xtime.translate(s)
        assert ref.hour-1 == t.hour
        assert ref.minute == t.minute
        assert ref.second == t.second

    def testConvertPositiveToNegative(self):
        Timezone.LOCAL = lambda tz: -6
        ref = datetime.time(10, 30, 22)
        s = self.strTime(ref.hour, ref.minute, ref.second, 3)
        xtime = Time()
        t = xtime.translate(s)
        assert ref.hour-9 == t.hour
        assert ref.minute == t.minute
        assert ref.second == t.second

    def testConvertNegativeToPositive(self):
        Timezone.LOCAL = lambda tz: 3
        ref = datetime.time(10, 30, 22)
        s = self.strTime(ref.hour, ref.minute, ref.second, -6)
        xtime = Time()
        t = xtime.translate(s)
        assert ref.hour+9 == t.hour
        assert ref.minute == t.minute
        assert ref.second == t.second

    def testConvertNegativeToUtc(self):
        Timezone.LOCAL = lambda tz: 0
        ref = datetime.time(10, 30, 22)
        s = self.strTime(ref.hour, ref.minute, ref.second, -6)
        xtime = Time()
        t = xtime.translate(s)
        assert ref.hour+6 == t.hour
        assert ref.minute == t.minute
        assert ref.second == t.second

    def testConvertPositiveToUtc(self):
        Timezone.LOCAL = lambda tz: 0
        ref = datetime.time(10, 30, 22)
        s = self.strTime(ref.hour, ref.minute, ref.second, 3)
        xtime = Time()
        t = xtime.translate(s)
        assert ref.hour-3 == t.hour
        assert ref.minute == t.minute
        assert ref.second == t.second

    def testConvertUtcToPositive(self):
        Timezone.LOCAL = lambda tz: 3
        ref = datetime.time(10, 30, 22)
        s = '%.2d:%.2d:%.2dZ' % (ref.hour, ref.minute, ref.second)
        xtime = Time()
        t = xtime.translate(s)
        assert ref.hour+3 == t.hour
        assert ref.minute == t.minute
        assert ref.second == t.second

    def testConvertUtcToNegative(self):
        Timezone.LOCAL = lambda tz: -6
        ref = datetime.time(10, 30, 22)
        s = '%.2d:%.2d:%.2dZ' % (ref.hour, ref.minute, ref.second)
        xtime = Time()
        t = xtime.translate(s)
        assert ref.hour-6 == t.hour
        assert ref.minute == t.minute
        assert ref.second == t.second

    def strTime(self, h, m, s, offset):
        return '%.2d:%.2d:%.2d%+.2d:00' % (h, m, s, offset)

class TestDateTime:

    def testSimple(self):
        Timezone.LOCAL = lambda tz: 0
        ref = datetime.datetime(1941, 12, 7, 10, 30, 22)
        s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2d' \
            % (ref.year,
               ref.month,
               ref.day,
               ref.hour,
               ref.minute,
               ref.second)
        xdt = DateTime()
        t = xdt.translate(s)
        assert t == ref

    def testOverflow(self):
        Timezone.LOCAL = lambda tz: -2
        ref = datetime.datetime(1, 1, 1, 0, 0, 0)
        s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ' \
            % (ref.year,
               ref.month,
               ref.day,
               ref.hour,
               ref.minute,
               ref.second)
        xdt = DateTime()
        t = xdt.translate(s)
        assert t == ref

    def testSimpleWithMicrosecond(self):
        Timezone.LOCAL = lambda tz: 0
        ref = datetime.datetime(1941, 12, 7, 10, 30, 22, 454)
        s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.4d' \
            % (ref.year,
               ref.month,
               ref.day,
               ref.hour,
               ref.minute,
               ref.second,
               ref.microsecond)
        xdt = DateTime()
        t = xdt.translate(s)
        assert t == ref

    def testPositiveTimezone(self):
        self.equalsTimezone(6)

    def testNegativeTimezone(self):
        self.equalsTimezone(-6)

    def testUtcTimezone(self):
        Timezone.LOCAL = lambda tz: 0
        ref = datetime.datetime(1941, 12, 7, 10, 30, 22)
        s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2d' \
            % (ref.year,
               ref.month,
               ref.day,
               ref.hour,
               ref.minute,
               ref.second)
        xdt = DateTime()
        t = xdt.translate(s)
        assert t == ref

    def equalsTimezone(self, tz):
        Timezone.LOCAL = lambda cls: tz
        ref = datetime.datetime(1941, 12, 7, 10, 30, 22)
        s = self.strDateTime(
                ref.year,
                ref.month,
                ref.day,
                ref.hour,
                ref.minute,
                ref.second,
                tz)
        xdt = DateTime()


# -*- coding: utf-8 -*-

# Qt Worker for Turpial

import os
import Queue

from PyQt4.QtCore import QThread
from PyQt4.QtCore import pyqtSignal

from turpial.ui.base import BROADCAST_ACCOUNT

from libturpial.api.core import Core
from libturpial.api.models.status import Status
from libturpial.api.models.column import Column
from libturpial.common.tools import get_account_id_from, get_column_slug_from

class CoreWorker(QThread):

    ready = pyqtSignal(object)
    status_updated = pyqtSignal(object, str)
    status_broadcasted = pyqtSignal(object)
    status_repeated = pyqtSignal(object, str, str, str)
    status_deleted = pyqtSignal(object, str, str, str)
    status_pushed_to_queue = pyqtSignal(str)
    status_poped_from_queue = pyqtSignal(object)
    status_deleted_from_queue = pyqtSignal()
    queue_cleared = pyqtSignal()
    status_posted_from_queue = pyqtSignal(object, str, str)
    message_deleted = pyqtSignal(object, str, str, str)
    message_sent = pyqtSignal(object, str)
    column_updated = pyqtSignal(object, tuple)
    account_saved = pyqtSignal(str)
    account_loaded = pyqtSignal()
    account_deleted = pyqtSignal()
    column_saved = pyqtSignal(str)
    column_deleted = pyqtSignal(str)
    status_marked_as_favorite = pyqtSignal(object, str, str, str)
    status_unmarked_as_favorite = pyqtSignal(object, str, str, str)
    fetched_user_profile = pyqtSignal(object, str)
    urls_shorted = pyqtSignal(object)
    media_uploaded = pyqtSignal(object)
    friends_list_updated = pyqtSignal()
    user_muted = pyqtSignal(str)
    user_unmuted = pyqtSignal(str)
    user_blocked = pyqtSignal(object)
    user_reported_as_spam = pyqtSignal(object)
    user_followed = pyqtSignal(object, str)
    user_unfollowed = pyqtSignal(object, str)
    exception_raised = pyqtSignal(object)
    status_from_conversation = pyqtSignal(object, str, str)
    fetched_profile_image = pyqtSignal(object)
    fetched_avatar = pyqtSignal(object, str)
    fetched_image_preview = pyqtSignal(object)
    cache_deleted = pyqtSignal()

    ERROR = -1
    LOADING = 0
    READY = 1

    def __init__(self):
        QThread.__init__(self)
        self.queue = Queue.Queue()
        self.exit_ = False
        self.status = self.LOADING
        #self.core = Core()

        #self.queue_path = os.path.join(self.core.config.basedir, 'queue')
        #if not os.path.isfile(self.queue_path):
        #    open(self.queue_path, 'w').close()
        self.core = None
        self.restart()

    #def __del__(self):
    #    self.wait()

    def restart(self):
        self.register(self.login, (), self.__after_login, None)

    def __get_from_queue(self, index=0):
        lines = open(self.queue_path).readlines()
        if not lines:
            return None

        row = lines[index].strip()
        account_id, message = row.split("\1")
        del lines[index]

        open(self.queue_path, 'w').writelines(lines)
        status = Status()
        status.account_id = account_id
        status.text = self.__unescape_queue_message(message)
        return status

    def __get_column_num_from_id(self, column_id):
        column_key = None
        for i in range(1, len(self.get_registered_columns()) + 1):
            column_num = "column%s" % i
            stored_id = self.core.config.read('Columns', column_num)
            if stored_id == column_id:
                column_key = column_num
            else:
                i += 1
        return column_key

    def __escape_queue_message(self, message):
        message = message.replace('\n', '\0')
        return message

    def __unescape_queue_message(self, message):
        message = message.replace('\0', '\n')
        return message

    #================================================================
    # Core methods
    #================================================================

    def login(self):
        self.core = Core()
        # FIXME: Dirty hack that must be fixed in libturpial
        self.core.config.cfg.optionxform = str
        self.core.config.load()
        self.queue_path = os.path.join(self.core.config.basedir, 'queue')
        if not os.path.isfile(self.queue_path):
            open(self.queue_path, 'w').close()

    def get_update_interval(self):
        return self.core.get_update_interval()

    def get_statuses_per_column(self):
        return self.core.get_max_statuses_per_column()

    def get_proxy_configuration(self):
        return self.core.config.read_section('Proxy')

    def get_socket_timeout(self):
        return self.core.get_socket_timeout()

    # Custom config
    def get_minimize_on_close(self):
        return self.core.config.read('General', 'minimize-on-close', boolean=True)

    def set_minimize_on_close(self, value):
        self.core.config.read('General', 'minimize-on-close', value)

    def get_show_user_avatars(self):
        return self.core.config.read('Advanced', 'show-user-avatars', boolean=True)

    def get_sound_on_login(self):
        return self.core.config.read('Sounds', 'login', boolean=True)

    def get_sound_on_updates(self):
        return self.core.config.read('Sounds', 'updates', boolean=True)

    def get_notify_on_updates(self):
        return self.core.config.read('Notifications', 'updates', boolean=True)

    def get_notify_on_actions(self):
        return self.core.config.read('Notifications', 'actions', boolean=True)

    def get_window_size(self):
        size = self.core.config.read('Window', 'size')
        return int(size.split(',')[0]), int(size.split(',')[1])

    def set_window_size(self, width, height):
        window_size = "%s,%s" % (width, height)
        self.core.config.write('Window', 'size', window_size)

    def get_default_browser(self):
        return self.core.config.read('Browser', 'cmd')

    def get_queue_interval(self):
        return int(self.core.config.read('General', 'queue-interval'))

    def get_inline_preview(self):
        return self.core.config.read('General', 'inline-preview', boolean=True)

    def set_inline_preview(self, value):
        self.core.config.write('General', 'inline-preview', value)

    def get_show_images_in_browser(self):
        return self.core.config.read('General', 'show-images-in-browser', boolean=True)

    def set_show_images_in_browser(self, value):
        self.core.config.write('General', 'show-images-in-browser', value)

    def get_update_interval_per_column(self, column_id):
        interval = self.core.config.read('Updates', column_id)
        return interval if interval is None else int(interval)

    def set_update_interval_per_column(self, column_id, interval):
        self.core.config.write('Updates', column_id, interval)

    def get_show_notifications_in_column(self, column_id):
        return self.core.config.read('Notifications', column_id, boolean=True)

    def set_show_notifications_in_column(self, column_id, value):
        self.core.config.write('Notifications', column_id, 'on' if value else 'off')

    def get_theme(self):
        return self.core.config.read('General', 'theme')

    def get_font_size(self):
        return int(self.core.config.read('General', 'font-size'))

    def get_cache_size(self):
        return self.core.get_cache_size()

    def delete_cache(self):
        self.core.delete_cache()

    def read_config(self):
        return self.core.get_config()

    def update_config(self, new_config):
        for section in new_config:
            for option in new_config[section]:
                self.core.write_config_value(section, option, new_config[section][option])

    def write_config_option(self, section, option, value):
        self.core.config.write(section, option, value)

    def read_section(self, section):
        return self.core.config.read_section(section)

    def write_section(self, section, items):
        self.core.config.write_section(section, items)

    def add_config_option(self, section, option, default_value):
        self.core.register_new_config_option(section, option, default_value)

    # TODO: Implement this in libturpial
    def remove_config_option(self, section, option):
        if section in self.core.config._ConfigBase__config:
            if option in self.core.config._ConfigBase__config[section]:
                del self.core.config._ConfigBase__config[section][option]

        if section in self.core.config.extra_sections:
            if option in self.core.config.extra_sections[section]:
                del self.core.config.extra_sections[section][option]

        self.core.config.cfg.remove_option(section, option)

        _fd = open(self.core.config.configpath, 'w')
        self.core.config.cfg.write(_fd)
        _fd.close()

    # TODO: Implement this in libturpial
    def remove_section(self, section):
        if section in self.core.config._ConfigBase__config:
            del self.core.config._ConfigBase__config[section]

        if section in self.core.config.extra_sections:
            del self.core.config.extra_sections[section]

        self.core.config.cfg.remove_section(section)

        _fd = open(self.core.config.configpath, 'w')
        self.core.config.cfg.write(_fd)
        _fd.close()

    def sanitize_search_columns(self):
        i = 1
        columns = []
        while True:
            column_num = "column%s" % i
            column_id = self.core.config.read('Columns', column_num)
            if column_id is None:
                break
            elif column_id is not None and column_id.find('search:') >= 0:
                column_id = column_id.replace('search:', 'search>')
                self.core.config.write('Columns', column_num, column_id)
            columns.append(column_id)
            i += 1
        return columns

    def get_shorten_url_service(self):
        return self.core.get_shorten_url_service()

    def get_upload_media_service(self):
        return self.core.get_upload_media_service()

    def get_available_columns(self):
        return self.core.available_columns()

    def get_all_accounts(self):
        return self.core.registered_accounts()

    def get_all_columns(self):
        return self.core.all_columns()

    def get_registered_accounts(self):
        return self.core.registered_accounts()

    def get_available_short_url_services(self):
        return self.core.available_short_url_services()

    def get_available_upload_media_services(self):
        return self.core.available_upload_media_services()

    def get_registered_columns(self):
        return self.core.registered_columns_by_order()

    def is_muted(self, username):
        return self.core.is_muted(username)

    def load_friends_list(self):
        return self.core.load_all_friends_list()

    def save_account(self, account):
        account_id = self.core.register_account(account)
        self.__after_save_account(account_id)

    # FIXME: Remove this after implement this in libturpial
    def load_account(self, account_id, trigger_signal=True):
        if trigger_signal:
            self.register(self.core.account_manager.load, (account_id),
                self.__after_load_account)
        else:
            self.core.account_manager.load(account_id)
            self.__after_load_account()

    def delete_account(self, account_id):
        # FIXME: Implement try/except
        for col in self.get_registered_columns():
            if col.account_id == account_id:
                self.delete_column(col.id_)
        self.core.unregister_account(str(account_id), True)
        self.__after_delete_account()

    def save_column(self, column_id):
        reg_column_id = self.core.register_column(column_id)
        notify = 'on' if self.get_notify_on_updates() else 'off'
        self.write_config_option('Notifications', column_id, notify)
        self.write_config_option('Updates', column_id, self.get_update_interval())
        self.__after_save_column(reg_column_id)

    def delete_column(self, column_id):
        deleted_column = self.core.unregister_column(column_id)
        self.remove_config_option('Notifications', column_id)
        self.remove_config_option('Updates', column_id)
        self.__after_delete_column(column_id)

    def get_column_statuses(self, column, last_id):
        count = self.core.get_max_statuses_per_column()
        self.register(self.core.get_column_statuses, (column.account_id,
            column.slug, count, last_id), self.__after_update_column,
            (column, count))

    def update_status(self, account_id, message, in_reply_to_id=None):
        self.register(self.core.update_status, (account_id,
            message, in_reply_to_id), self.__after_update_status, account_id)

    def update_status_with_media(self, account_id, message, media, in_reply_to_id=None):
        if self.get_upload_media_service() != 'pic.twitter.com':
            media_url = self.core.upload_media(account_id, media)
            message = "%s %s" % (message, media_url)
            self.register(self.core.update_status, (account_id,
                message, in_reply_to_id), self.__after_update_status, account_id)
        else:
            self.register(self.core.update_status, (account_id,
                message, in_reply_to_id, media), self.__after_update_status, account_id)

    def broadcast_status(self, accounts, message):
        self.register(self.core.broadcast_status, (accounts, message),
            self.__after_broadcast_status)

    def repeat_status(self, column_id, account_id, status_id):
        self.register(self.core.repeat_status, (account_id, status_id),
            self.__after_repeat_status, (column_id, account_id, status_id))

    def delete_status(self, column_id, account_id, status_id):
        self.register(self.core.destroy_status, (account_id, status_id),
            self.__after_delete_status, (column_id, account_id, status_id))

    def delete_direct_message(self, column_id, account_id, status_id):
        self.register(self.core.destroy_direct_message, (account_id, status_id),
            self.__after_delete_direct_message, (column_id, account_id, status_id))

    def send_direct_message(self, account_id, username, message):
        self.register(self.core.send_direct_message, (account_id, username,
            message), self.__after_send_direct_message, account_id)

    def mark_status_as_favorite(self, column_id, account_id, status_id):
        self.register(self.core.mark_status_as_favorite, (account_id, status_id),
            self.__after_mark_status_as_favorite, (column_id, account_id, status_id))

    def unmark_status_as_favorite(self, column_id, account_id, status_id):
        self.register(self.core.unmark_status_as_favorite, (account_id, status_id),
            self.__after_unmark_status_as_favorite, (column_id, account_id, status_id))

    def get_user_profile(self, account_id, user_profile=None):
        self.register(self.core.get_user_profile, (account_id, user_profile),
            self.__after_get_user_profile, account_id)

    def short_urls(self, message):
        self.register(self.core.short_url_in_message, (message),
            self.__after_short_urls)

    def upload_media(self, account_id, filepath):
        self.register(self.core.upload_media, (account_id, filepath),
            self.__after_upload_media)

    def get_friends_list(self):
        self.register(self.core.get_all_friends_list, None,
            self.__after_get_friends_list)

    def mute(self, username):
        self.register(self.core.mute, username, self.__after_mute_user)

    def unmute(self, username):
        self.register(self.core.unmute, username, self.__after_unmute_user)

    def block(self, account_id, username):
        self.register(self.core.block, (account_id, username), self.__after_block_user)

    def report_as_spam(self, account_id, username):
        self.register(self.core.report_as_spam, (account_id, username),
            self.__after_report_user_as_spam)

    def follow(self, account_id, username):
        self.register(self.core.follow, (account_id, username),
            self.__after_follow_user, account_id)

    def unfollow(self, account_id, username):
        self.register(self.core.unfollow, (account_id, username),
            self.__after_unfollow_user, account_id)

    def get_status_from_conversation(self, account_id, status_id, column_id, status_root_id):
        self.register(self.core.get_single_status, (account_id, status_id),
            self.__after_get_status_from_conversation, (column_id, status_root_id))

    def get_profile_image(self, account_id, username):
        self.register(self.core.get_profile_image, (account_id, username, False),
            self.__after_get_profile_image)

    def get_avatar_from_status(self, status):
        self.register(self.core.get_status_avatar, (status),
            self.__after_get_avatar_from_status, status.username)

    def get_image_preview(self, preview_service, url):
        self.register(preview_service.do_service, (url),
            self.__after_get_image_preview)

    def push_status_to_queue(self, account_id, message):
        fd = open(self.queue_path, 'a+')
        message = self.__escape_queue_message(message)
        row = "%s\1%s\n" % (account_id, message)
        fd.write(row.encode('utf-8'))
        fd.close()
        self.__after_push_status_to_queue(account_id)

    def pop_status_from_queue(self):
        status = self.__get_from_queue()
        self.__after_pop_status_from_queue(status)

    def delete_status_from_queue(self, index=0):
        status = self.__get_from_queue(index)
        self.__after_delete_status_from_queue()

    def list_statuses_queue(self):
        statuses = []
        lines = []
        if os.path.exists(self.queue_path):
            lines = open(self.queue_path).readlines()
        for line in lines:
            account_id, message = line.strip().split("\1")
            status = Status()
            status.account_id = account_id
            status.text = self.__unescape_queue_message(message)
            statuses.append(status)
        return statuses

    def clear_statuses_queue(self):
        open(self.queue_path, 'w').writelines([])
        self.__after_clear_queue()

    def post_status_from_queue(self, account_id, message):
        if account_id == BROADCAST_ACCOUNT:
            self.register(self.core.broadcast_status, (None, message),
                self.__after_post_status_from_queue, (account_id, message))
        else:
            self.register(self.core.update_status, (account_id, message),
                self.__after_post_status_from_queue, (account_id, message))

    def delete_cache(self):
        self.register(self.core.delete_cache, None, self.__after_delete_cache)

    def list_filters(self):
        return self.core.list_filters()

    def save_filters(self, filters):
        self.core.save_filters(filters)

    def restore_config(self):
        self.core.delete_current_config()

    def filter_statuses(self, statuses):
        return self.core.filter_statuses(statuses)

    #================================================================
    # Callbacks
    #================================================================

    def __after_login(self, response):
        self.ready.emit(response)

    def __after_save_account(self, account_id):
        self.account_saved.emit(account_id)

    def __after_load_account(self, response=None):
        self.account_loaded.emit()

    def __after_delete_account(self):
        self.account_deleted.emit()

    def __after_save_column(self, column_id):
        self.column_saved.emit(column_id)

    def __after_delete_column(self, column_id):
        self.column_deleted.emit(column_id)

    def __after_update_column(self, response, data):
        self.column_updated.emit(response, data)

    def __after_update_status(self, response, account_id):
        self.status_updated.emit(response, account_id)

    def __after_broadcast_status(self, response):
        self.status_broadcasted.emit(response)

    def __after_repeat_status(self, response, args):
        column_id = args[0]
        account_id = args[1]
        status_id = args[2]
        self.status_repeated.emit(response, column_id, account_id, status_id)

    def __after_delete_status(self, response, args):
        column_id = args[0]
        account_id = args[1]
        status_id = args[2]
        self.status_deleted.emit(response, column_id, account_id, status_id)

    def __after_delete_direct_message(self, response, args):
        column_id = args[0]
        account_id = args[1]
        status_id = args[2]
        self.message_deleted.emit(response, column_id, account_id, status_id)

    def __after_send_direct_message(self, response, account_id):
        self.message_sent.emit(response, account_id)

    def __after_mark_status_as_favorite(self, response, args):
        column_id = args[0]
        account_id = args[1]
        status_id = args[2]
        self.status_marked_as_favorite.emit(response, column_id, account_id, status_id)

    def __after_unmark_status_as_favorite(self, response, args):
        column_id = args[0]
        account_id = args[1]
        status_id = args[2]
        self.status_unmarked_as_favorite.emit(response, column_id, account_id, status_id)

    def __after_get_user_profile(self, response, account_id):
        self.fetched_user_profile.emit(response, account_id)

    def __after_short_urls(self, response):
        self.urls_shorted.emit(response)

    def __after_upload_media(self, response):
        self.media_uploaded.emit(response)

    def __after_get_friends_list(self, response):
        self.friends_list_updated.emit()

    def __after_mute_user(self, response):
        self.user_muted.emit(response)

    def __after_unmute_user(self, response):
        self.user_unmuted.emit(response)

    def __after_block_user(self, response):
        self.user_blocked.emit(response)

    def __after_report_user_as_spam(self, response):
        self.user_reported_as_spam.emit(response)

    def __after_follow_user(self, response, account_id):
        self.user_followed.emit(response, account_id)

    def __after_unfollow_user(self, response, account_id):
        self.user_unfollowed.emit(response, account_id)

    def __after_get_status_from_conversation(self, response, args):
        column_id = args[0]
        status_root_id = args[1]
        self.status_from_conversation.emit(response, column_id, status_root_id)

    def __after_get_profile_image(self, response):
        self.fetched_profile_image.emit(response)

    def __after_get_avatar_from_status(self, response, args):
        username = args
        self.fetched_avatar.emit(response, username)

    def __after_get_image_preview(self, response):
        self.fetched_image_preview.emit(response)

    def __after_push_status_to_queue(self, account_id):
        self.status_pushed_to_queue.emit(account_id)

    def __after_pop_status_from_queue(self, status):
        self.status_poped_from_queue.emit(status)

    def __after_delete_status_from_queue(self):
        self.status_deleted_from_queue.emit()

    def __after_clear_queue(self):
        self.queue_cleared.emit()

    def __after_post_status_from_queue(self, response, args):
        account_id = args[0]
        message = args[1]
        self.status_posted_from_queue.emit(response, account_id, message)

    def __after_delete_cache(self, response=None):
        self.cache_deleted.emit()

    #================================================================
    # Worker methods
    #================================================================

    def register(self, funct, args, callback, user_data=None):
        self.queue.put((funct, args, callback, user_data))

    def quit(self):
        self.exit_ = True

    def run(self):
        while not self.exit_:
            try:
                req = self.queue.get(True, 0.3)
            except Queue.Empty:
                continue
            except:
                continue

            (funct, args, callback, user_data) = req

            try:
                if type(args) == tuple:
                    rtn = funct(*args)
                elif args:
                    rtn = funct(args)
                else:


# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------------
# flv.py - parser for flash video files
# -----------------------------------------------------------------------------
# $Id: flv.py 4041 2009-05-22 14:59:59Z tack $
#
# -----------------------------------------------------------------------------
# kaa-Metadata - Media Metadata for Python
# Copyright (C) 2003-2006 Thomas Schueppel, Dirk Meyer
#
# First Edition: Dirk Meyer <dischi@freevo.org>
# Maintainer:    Dirk Meyer <dischi@freevo.org>
#
# Please see the file AUTHORS for a complete list of authors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------------

from __future__ import absolute_import

__all__ = ['Parser']

# python imports
#import sys
import struct
import logging

# import kaa_metadata.video core
from . import core

# get logging object
log = logging.getLogger('metadata')

FLV_TAG_TYPE_AUDIO = 0x08
FLV_TAG_TYPE_VIDEO = 0x09
FLV_TAG_TYPE_META  = 0x12

# audio flags
FLV_AUDIO_CHANNEL_MASK = 0x01
FLV_AUDIO_SAMPLERATE_MASK = 0x0c
FLV_AUDIO_CODECID_MASK = 0xf0

FLV_AUDIO_SAMPLERATE_OFFSET = 2
FLV_AUDIO_CODECID_OFFSET = 4
FLV_AUDIO_CODECID = ( 0x0001, 0x0002, 0x0055, 0x0001 )

# video flags
FLV_VIDEO_CODECID_MASK = 0x0f
FLV_VIDEO_CODECID = ( 'FLV1', 'MSS1', 'VP60') # wild guess

FLV_DATA_TYPE_NUMBER =     0x00
FLV_DATA_TYPE_BOOL =       0x01
FLV_DATA_TYPE_STRING =     0x02
FLV_DATA_TYPE_OBJECT =     0x03
FLC_DATA_TYPE_CLIP =       0x04
FLV_DATA_TYPE_REFERENCE =  0x07
FLV_DATA_TYPE_ECMARRAY =   0x08
FLV_DATA_TYPE_ENDOBJECT =  0x09
FLV_DATA_TYPE_ARRAY =      0x0a
FLV_DATA_TYPE_DATE =       0x0b
FLV_DATA_TYPE_LONGSTRING = 0x0c

FLVINFO = {
    'creator': 'copyright',
}

class FlashVideo(core.AVContainer):
    """
    Experimental parser for Flash videos. It requires certain flags to
    be set to report video resolutions and in most cases it does not
    provide that information.
    """
    table_mapping = { 'FLVINFO' : FLVINFO }

    def __init__(self, in_file):
        core.AVContainer.__init__(self)
        self.mime = 'video/flv'
        self.type = 'Flash Video'
        self.copyright = None
        self.width = None
        self.height = None
        self.length = None
        data = in_file.read(13)
        if len(data) < 13 or struct.unpack('>3sBBII', data)[0] != 'FLV':
            raise core.ParseError()

        for _ in range(10):
            if self.audio and self.video:
                break
            data = in_file.read(11)
            if len(data) < 11:
                break
            chunk = struct.unpack('>BH4BI', data)
            size = (chunk[1] << 8) + chunk[2]

            if chunk[0] == FLV_TAG_TYPE_AUDIO:
                flags = ord(in_file.read(1))
                if not self.audio:
                    self.audio.append(self.get_audio_type(flags))
                in_file.seek(size - 1, 1)

            elif chunk[0] == FLV_TAG_TYPE_VIDEO:
                flags = ord(in_file.read(1))
                if not self.video:
                    self.video.append(self.get_video_type(flags))
                in_file.seek(size - 1, 1)

            elif chunk[0] == FLV_TAG_TYPE_META:
                log.info('metadata %s', str(chunk))
                metadata = in_file.read(size)
                self.add_metadata(metadata)
            else:
                log.info('unkown %s', str(chunk))
                in_file.seek(size, 1)
            in_file.seek(4, 1)

    @staticmethod
    def get_audio_type(flags):
        "Create an audio stream in the media"
        a = core.AudioStream()
        a.channels = (flags & FLV_AUDIO_CHANNEL_MASK) + 1
        srate = (flags & FLV_AUDIO_SAMPLERATE_MASK)
        a.samplerate = (44100 <<
                        (srate >> FLV_AUDIO_SAMPLERATE_OFFSET) >> 3)
        codec = ((flags & FLV_AUDIO_CODECID_MASK)
                 >> FLV_AUDIO_CODECID_OFFSET)
        if codec < len(FLV_AUDIO_CODECID):
            a.codec = FLV_AUDIO_CODECID[codec]
        return a

    @staticmethod
    def get_video_type(flags):
        "Create a video stream in the media"
        v = core.VideoStream()
        codec = (flags & FLV_VIDEO_CODECID_MASK) - 2
        if codec < len(FLV_VIDEO_CODECID):
            v.codec = FLV_VIDEO_CODECID[codec]
        # width and height are in the meta packet, but I have
        # no in_file with such a packet inside. So maybe we have
        # to decode some parts of the video.
        return v

    def add_metadata(self, metadata):
        'Tries to put metadata in the stream'
        try:
            while metadata:
                length, value = self._parse_value(metadata)
                if isinstance(value, dict):
                    log.info('metadata: %s', value)
                    if value.get('creator'):
                        self.copyright = value.get('creator')
                    if value.get('width'):
                        self.width = value.get('width')
                    if value.get('height'):
                        self.height = value.get('height')
                    if value.get('duration'):
                        self.length = value.get('duration')
                    self._appendtable('FLVINFO', value)
                if not length:
                    # parse error
                    break
                metadata = metadata[length:]
        except (IndexError, struct.error, TypeError):
            #log.exception('Error after parsing flv header')
            pass

    def _parse_value(self, data):
        """
        Parse the next metadata value.
        """
        if ord(data[0]) == FLV_DATA_TYPE_NUMBER:
            value = struct.unpack('>d', data[1:9])[0]
            return 9, value
        if ord(data[0]) == FLV_DATA_TYPE_BOOL:
            return 2, bool(data[1])
        if ord(data[0]) == FLV_DATA_TYPE_STRING:
            length = (ord(data[1]) << 8) + ord(data[2])
            return (length + 3), data[3:(length + 3)]
        if ord(data[0]) == FLV_DATA_TYPE_ECMARRAY:
            init_length = len(data)
            num = struct.unpack('>I', data[1:5])[0]
            data = data[5:]
            result = {}
            for _ in range(num):
                length = (ord(data[0]) << 8) + ord(data[1])
                key = data[2:(length + 2)]
                data = data[(length + 2):]
                length, value = self._parse_value(data)
                if not length:
                    return 0, result
                result[key] = value
                data = data[length:]


"""Test file descriptor operations

Made for Jython.
"""
import errno
import os
import sys
import tempfile
import test.test_support as test_support
import unittest

class TestFilenoTestCase(unittest.TestCase):

    def setUp(self):
        self.filename = tempfile.mktemp()
        self.fp = open(self.filename, 'w+')
        self.fd = self.fp.fileno()

    def tearDown(self):
        if self.fp:
            self.fp.close()
        os.remove(self.filename)

    def test_ftruncate(self):
        self.fp.write('jython filenos')
        self.fp.flush()
        os.fsync(self.fd)
        self.assertEqual(os.path.getsize(self.filename), 14)
        os.ftruncate(self.fd, 8)
        self.assertEqual(os.path.getsize(self.filename), 8)
        os.ftruncate(self.fd, 0)
        self.assertEqual(os.path.getsize(self.filename), 0)

        self.fp.close()
        raises(IOError, 9, os.ftruncate, self.fd, 0)

    def test_lseek(self):
        self.assertEqual(os.lseek(self.fd, 0, 1), 0)
        os.write(self.fd, 'jython filenos')
        os.lseek(self.fd, 7, 0)
        self.assertEqual(os.read(self.fd, 7), 'filenos')
        self.fp.close()
        raises(OSError, 9, os.lseek, self.fd, 0, 1)

    def test_read(self):
        self.fp.write('jython filenos')
        self.fp.flush()
        self.fp.seek(0)
        self.assertEqual(os.read(self.fd, 7), 'jython ')
        self.assertEqual(os.read(self.fd, 99), 'filenos')
        self.fp.close()
        raises(OSError, 9, os.read, self.fd, 1)

    def test_write(self):
        os.write(self.fd, 'jython filenos')
        self.fp.seek(0)
        self.assertEqual(self.fp.read(), 'jython filenos')
        self.fp.close()
        raises(OSError, 9, os.write, self.fd, 'The Larch')

class TestOsOpenTestCase(unittest.TestCase):

    def setUp(self):
        self.filename = tempfile.mktemp()
        self.dir = None
        self.fd = None

    def tearDown(self):
        if self.fd:
            try:
                os.close(self.fd)
            except:
                pass
        if os.path.exists(self.filename):
            os.remove(self.filename)
        if self.dir:
            os.rmdir(self.dir)

    def test_open(self):
        # XXX: assert the mode of the file
        self.fd = os.open(self.filename, os.O_WRONLY | os.O_CREAT)
        self.assert_(os.path.exists(self.filename))
        os.write(self.fd, 'jython')
        os.close(self.fd)

        self.fd = os.open(self.filename, os.O_WRONLY | os.O_APPEND)
        os.write(self.fd, ' filenos')
        os.close(self.fd)
        fp = open(self.filename)
        self.assertEquals(fp.read(), 'jython filenos')
        fp.close()

        # falls back to read only without O_WRONLY/O_RDWR
        self.fd = os.open(self.filename, os.O_APPEND)
        raises(OSError, 9, os.write, self.fd, 'new')
        # Acts as append on windows (seeks to the end)
        os.lseek(self.fd, 0, 0)
        self.assertEquals(os.read(self.fd, len('jython filenos')), 'jython filenos')
        os.close(self.fd)

        # falls back to read only without O_WRONLY/O_RDWR
        self.fd = os.open(self.filename, os.O_CREAT)
        raises(OSError, 9, os.write, self.fd, 'new')
        self.assertEquals(os.read(self.fd, len('jython filenos')), 'jython filenos')
        os.close(self.fd)

        # interpreted as RDWR
        self.fd = os.open(self.filename, os.O_RDONLY | os.O_RDWR)
        os.write(self.fd, 'test')
        os.lseek(self.fd, 0, 0)
        self.assertEquals(os.read(self.fd, 4), 'test')
        os.close(self.fd)

    def test_open_truncate(self):
        fp = open(self.filename, 'w')
        fp.write('hello')
        fp.close()

        self.assertEquals(os.path.getsize(self.filename), 5)
        self.fd = os.open(self.filename, os.O_TRUNC | os.O_RDWR)
        self.assertEquals(os.path.getsize(self.filename), 0)
        os.write(self.fd, 'truncated')
        os.lseek(self.fd, 0, 0)
        self.assertEquals(os.read(self.fd, len('truncated')), 'truncated')
        os.close(self.fd)

        self.fd = os.open(self.filename, os.O_TRUNC | os.O_WRONLY)
        self.assertEquals(os.path.getsize(self.filename), 0)
        os.write(self.fd, 'write only truncated')
        raises(OSError, 9, os.read, self.fd, 99)
        os.close(self.fd)

        fd = open(self.filename)
        self.assertEquals(fd.read(), 'write only truncated')
        fd.close()

        # Both fail on Windows, errno 22
        """
        # falls back to read only without O_WRONLY/O_RDWR, but truncates
        self.fd = os.open(self.filename, os.O_TRUNC)
        self.assertEquals(os.path.getsize(self.filename), 0)
        raises(OSError, 9, os.write, self.fd, 'new')
        self.assertEquals(os.read(self.fd, 99), '')
        os.close(self.fd)

        fp = open(self.filename, 'w')
        fp.write('and ')
        fp.close()
        self.assertEquals(os.path.getsize(self.filename), 4)

        # append with no write falls back to read, but still truncates
        self.fd = os.open(self.filename, os.O_TRUNC | os.O_APPEND)
        self.assertEquals(os.path.getsize(self.filename), 0)
        raises(OSError, 9, os.write, self.fd, 'new')
        os.close(self.fd)

        fp = open(self.filename, 'w')
        fp.write('and ')
        fp.close()
        self.assertEquals(os.path.getsize(self.filename), 4)
        """

    def test_open_exclusive(self):
        self.assert_(not os.path.exists(self.filename))
        # fails without O_CREAT
        raises(OSError, (2, self.filename), os.open, self.filename, os.O_EXCL)
        self.assert_(not os.path.exists(self.filename))

        # creates, read only
        self.fd = os.open(self.filename, os.O_EXCL | os.O_CREAT)
        self.assert_(os.path.exists(self.filename))
        raises(OSError, 9, os.write, self.fd, 'jython')
        self.assertEquals(os.read(self.fd, 99), '')
        os.close(self.fd)

        # not exclusive unless creating
        os.close(os.open(self.filename, os.O_EXCL))
        raises(OSError, (17, self.filename),
               os.open, self.filename, os.O_CREAT | os.O_EXCL)
        raises(OSError, (17, self.filename),
               os.open, self.filename, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
        raises(OSError, (17, self.filename),
               os.open, self.filename, os.O_CREAT | os.O_RDWR | os.O_EXCL)

        os.remove(self.filename)
        self.fd = os.open(self.filename, os.O_EXCL | os.O_RDWR | os.O_CREAT)
        os.write(self.fd, 'exclusive')
        os.lseek(self.fd, 0, 0)
        self.assertEquals(os.read(self.fd, len('exclusive')), 'exclusive')

    def test_open_sync(self):
        if not hasattr(os, 'O_SYNC'):
            return

        # Just ensure this works
        self.fd = os.open(self.filename, os.O_SYNC | os.O_WRONLY | os.O_CREAT)
        self.assert_(os.path.exists(self.filename))
        os.write(self.fd, 'jython')
        raises(OSError, 9, os.read, self.fd, 99)
        os.close(self.fd)
        os.remove(self.filename)

        self.fd = os.open(self.filename, os.O_SYNC | os.O_RDWR | os.O_CREAT)
        self.assert_(os.path.exists(self.filename))
        os.write(self.fd, 'jython')
        os.lseek(self.fd, 0, 0)
        self.assertEquals(os.read(self.fd, len('jython')), 'jython')
        os.close(self.fd)

    def test_open_sync_dir(self):
        if not hasattr(os, 'O_SYNC'):
            return

        self.dir = tempfile.mkdtemp()
        try:
            self.fd = os.open(self.dir, os.O_SYNC | os.O_RDWR)
        except OSError, ose:
            assert ose.errno == errno.EISDIR, ose.errno

    def test_bad_open(self):
        for mode in (os.O_WRONLY, os.O_WRONLY, os.O_RDWR):
            raises(OSError, (2, self.filename), os.open, self.filename, mode)

        open(self.filename, 'w').close()

        raises(OSError, (22, self.filename),
               os.open, self.filename, os.O_WRONLY | os.O_RDWR)

class TestOsFdopenTestCase(unittest.TestCase):

    def setUp(self):
        self.filename = tempfile.mktemp()
        self.fd = None

    def tearDown(self):
        if self.fd:
            try:
                os.close(self.fd)
            except:
                pass
        if os.path.exists(self.filename):
            os.remove(self.filename)

    def test_fdopen(self):
        origw_fp = open(self.filename, 'w')
        origw = origw_fp.fileno()
        origr_fp = open(self.filename, 'r')
        origr = origr_fp.fileno()

        # Mode must begin with rwa
        raises(ValueError, "invalid file mode 'b'",
               os.fdopen, origr, 'b')

        # Refuse modes the original file doesn't support
        # XXX: allowed on windows CPython
        """
        raises(OSError, '[Errno 22] Invalid argument',
               os.fdopen, origw, 'r')
        raises(OSError, '[Errno 22] Invalid argument',
               os.fdopen, origr, 'w')
               """

        fp = os.fdopen(origw, 'w')
        fp.write('fdopen')
        # Windows CPython doesn't raise an exception here
        #raises(IOError, '[Errno 9] Bad file descriptor',
        #       fp.read, 7)
        fp.close()

        fp = os.fdopen(origr)
        self.assertEquals(fp.read(), 'fdopen')
        # Windows CPython raises IOError [Errno 0] Error
        #raises(IOError, '[Errno 9] Bad file descriptor',
        #       fp.write, 'test')
        raises(IOError, None,
               fp.write, 'test')
        fp.close()

        # Windows CPython raises OSError [Errno 0] Error for both these
        #raises(OSError, '[Errno 9] Bad file descriptor',
        #       os.fdopen, origw, 'w')
        #raises(OSError, '[Errno 9] Bad file descriptor',
        #       os.fdopen, origr, 'r')
        raises(OSError, None,
               os.fdopen, origw, 'w')
        raises(OSError, None,
               os.fdopen, origr, 'r')

        # These all raise IO/OSErrors on FreeBSD
        try:
            origw_fp.close()
        except:
            pass
        try:
            origr_fp.close()
        except:
            pass
        try:
            os.close(origw)
        except:
            pass
        try:
            os.close(origr)
        except:
            pass

def raises(exc, expected, callable, *args):
    """Ensure the specified call raises exc.

    expected is compared against the exception message if not None. It
    can be a str, an errno or a 2 item tuple of errno/filename. The
    latter two being for comparison against EnvironmentErrors.
    """
    if expected:
        if isinstance(expected, str):
            msg = expected
        else:
            errno = expected[0] if isinstance(expected, tuple) else expected
            msg = '[Errno %d] %s' % (errno, os.strerror(errno))
            if isinstance(expected, tuple):
                msg += ': %r' % expected[1]
    try:
        callable(*args)
    except exc, val:
        if expected and str(val) != msg:
            raise test_support.TestFailed(
                "Message %r, expected %r" % (str(val), msg))
    else:


# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from openerp import models, fields, api, _
from openerp.exceptions import UserError
from openerp.tools.float_utils import float_is_zero

from datetime import datetime

import logging
_logger = logging.getLogger(__name__)

class AccountMove2ExportMove(models.Model):
    _inherit = 'account.move'

    move_created = fields.Boolean('Export Move Created')
    move_error = fields.Boolean('Move Error')
    move_error_message = fields.Text('Error Message')

    def st_compute_debit_credit(self, move, lines, export_config):
        overall_forward = export_config.overall_forward.code
        sum_forward = export_config.sum_forward.code
        debit_forward = export_config.debit_forward.code
        credit_forward = export_config.credit_forward.code
        wage_through = export_config.wage_through.code
        debit = 0.0
        debit_accounts = []
        credit = 0.0
        credit_accounts = []
        invoice = self.env['account.invoice'].search([('move_id', '=', move.id)])
        for line in lines:
            if line.debit:
                debit += line.debit
                debit_accounts.append(line.account_id.code)
            if line.credit:
                credit += line.credit
                credit_accounts.append(line.account_id.code)
        if overall_forward or sum_forward or debit_forward or credit_forward or wage_through in debit_accounts:
            debit_forwards = []
            if overall_forward in debit_accounts:
                debit_forwards.append(overall_forward)
            if sum_forward in debit_accounts:
                debit_forwards.append(sum_forward)
            if debit_forward in debit_accounts:
                debit_forwards.append(debit_forward)
            if credit_forward in debit_accounts:
                debit_forwards.append(credit_forward)
            if wage_through in debit_accounts:
                debit_forwards.append(wage_through)
            if debit_forwards:
                debit_accounts = []
                debit_accounts.append(debit_forwards[0])
        if overall_forward or sum_forward or debit_forward or credit_forward or wage_through in debit_accounts:
            credit_forwards = []
            if overall_forward in credit_accounts:
                credit_forwards.append(overall_forward)
            if sum_forward in credit_accounts:
                debit_accounts.append(sum_forward)
            if debit_forward in credit_accounts:
                credit_forwards.append(debit_forward)
            if credit_forward in credit_accounts:
                credit_forwards.append(credit_forward)
            if wage_through in credit_accounts:
                credit_forwards.append(wage_through)
            if credit_forwards:
                credit_accounts =[]
                credit_accounts.append(credit_forwards[0])
        if overall_forward or sum_forward or debit_forward or credit_forward or wage_through not in debit_accounts:
            if invoice and invoice.type in ['out_invoice', 'in_refund']:
                debit_accounts = []
                if not invoice.account_id.code in debit_accounts:
                    debit_accounts.append(invoice.account_id.code)
            if invoice and invoice.type in ['out_refund', 'in_invoice']:
                credit_accounts = []
                if not invoice.account_id.code in debit_accounts:
                    credit_accounts.append(invoice.account_id.code)
            for da in debit_accounts:
                account = self.env['account.account'].search([('code', '=', da)])
                if account.user_type_id.type in ('receivable', 'payable'):
                    debit_accounts =[]
                    debit_accounts.append(account.code)
            for ca in credit_accounts:
                account = self.env['account.account'].search([('code', '=', ca)])
                if account.user_type_id.type in ('receivable', 'payable'):
                    credit_accounts = []
                    credit_accounts.append(account.code)
        totals = {
            'debit': debit,
            'debit_accounts': sorted(set(debit_accounts)),
            'credit': credit,
            'credit_accounts': sorted(set(credit_accounts))
        }
        return totals

    def st_create_tax_additions(self, line):
        tax_bkey, tax_pc, tax_account, tax_account_refund = '', 0.0, '', ''
        if line.tax_ids and len(line.tax_ids) == 1:
            tax = self.env['export.key'].search(
                [('vat_code', '=', line.tax_ids.id)], limit=1)
            tax_bkey = tax.export_key
            tax_pc = tax.vat_code.amount
            tax_account = tax.vat_code.account_id.code
            tax_account_refund = tax.vat_code.refund_account_id.code
        return tax_bkey, tax_pc, tax_account, tax_account_refund

    def st_check_account(self, line):
        auto_account_id = self.env['export.auto.account'].search(
            [('account_id', '=', line.account_id.id)])
        tax_bkey, tax_pc, tax_account, tax_account_refund =\
            self.st_create_tax_additions(line)
        if auto_account_id:
            if float_is_zero(tax_pc, precision_rounding=2):
                tax_pc = auto_account_id.vat_code.amount
        return bool(auto_account_id), tax_bkey, tax_pc, tax_account, tax_account_refund

    def st_calculate_amount(self, move, line, tax_pc, account, export_config):
        overall_forward = export_config.overall_forward.code
        sum_forward = export_config.sum_forward.code
        debit_forward = export_config.debit_forward.code
        credit_forward = export_config.credit_forward.code
        amount = 0.0
        if line.debit:
            amount += line.debit
        if line.credit:
            amount += line.credit
        if tax_pc:
            if overall_forward == account['account']:
                amount = amount
            elif sum_forward == account['account']:
                amount = amount
            elif debit_forward == account['account']:
                amount = amount
            elif credit_forward == account['account']:
                amount = amount
            else:
                gross_amount = amount * (1 + (tax_pc / 100))
                tax = gross_amount - amount
                amount += tax
        amount = move.currency_id.round(amount)
        return amount

    def st_build_line(self, account, move, res, line, invoice, export_config):
        auto_account, tax_bkey, tax_pc, tax_account, tax_account_refund =\
            self.st_check_account(line)
        res['name'] = 'New'
        res['date'] = move.date
        res['currency'] = move.currency_id.name
        res['dc_sign'] = account['sign']
        res['amount'] = self.st_calculate_amount(move, line, tax_pc, account, export_config)
        if auto_account:
            res['bkey'] = ''
        else:
            res['bkey'] = tax_bkey
        res['account_offset'] = line.account_id.code
        res['slip1'] = move.name.replace(' !@#$%^&*()[]{};:,./<>?\|`~-=_+', '')
        if export_config.maturity_slip2:
            if line.date_maturity:
                if line.date_maturity[:4] < '1900':
                    res['slip2'] = ''
                else:
                    res['slip2'] = fields.Date.from_string(line.date_maturity).strftime('%Y%m%d')
            else:
                res['slip2'] = ''
        else:
            res['slip2'] = ''
        res['booking_date'] = fields.Date.from_string(move.date).strftime('%Y%m%d')
        res['account'] = account['account']
        res['cost1'] = ''
        res['cost2'] = ''
        res['cost_quant'] = ''
        res['discount'] = ''
        res['bookingtext'] = line.name
        if invoice.partner_id.vat and export_config.eu_fiscal_position == invoice.fiscal_position_id:
            res['vat_id'] = invoice.partner_id.vat
        else:
            res['vat_id'] = ''
        res['eu_tax'] = ''
        res['base_cur_amount'] = ''
        res['base_cur_code'] = ''
        res['exchange_rate'] = ''
        res['company_id'] = move.company_id.id
        res['account_move'] = move.id
        res['move_date'] = move.date
        return res

    def st_generate_account(self, move, lines, export_config):
        totals = self.st_compute_debit_credit(move, lines, export_config)
        account = False
        sign = False
        for line in lines:
            if len(totals['debit_accounts']) == 1 and not line.tax_ids:
                account = totals['debit_accounts'][0]
                sign = 's'
            if len(totals['credit_accounts']) == 1 and not line.tax_ids:
                account = totals['credit_accounts'][0]
                sign = 'h'
        return {
            'account': account,
            'sign': sign
        }

    def st_generate_hash(self, res):
        return "%s-%s-%s-%s-%s-%s" % (
            res['dc_sign'],
            res.get('bkey', 'False'),
            res['account'],
            res['account_offset'],
            res.get('cost1', 'False'),
            res.get('cost2', 'False')
        )

    def st_create_group(self, booking):
        line2 = {}
        for b in booking:
            tmp = self.st_generate_hash(b)
            if tmp in line2:
                am = line2[tmp]['amount'] + b['amount']
                bt = line2[tmp]['bookingtext'] + ' ' + b['bookingtext']
                line2[tmp]['amount'] = am
                line2[tmp]['bookingtext'] = bt
            else:
                line2[tmp] = b
        booking = []
        for key, val in line2.items():
            booking.append(val.copy())
        return booking

    def st_generate_line(self, move, tax_accounts, export_logger, export_errors, export_count, export_moves, export_config):
        booking = []
        res = {}
        move_error = 0
        log = []
        invoice = self.env['account.invoice'].search([('move_id', '=', move.id)])
        if move.amount:
            move_lines = move.line_ids
            account = self.st_generate_account(move, move_lines, export_config)
            export_count += 1
            if account['account'] == False:
                error = _('%s has no / to many different accounts in debit / credit and can not exported') % move.name
                log.append(error)
                export_logger.append(error)
                export_errors += 1
                move_error += 1
                move.write({'move_error': True, 'move_error_message': '\n'.join(log)})
                return export_errors, export_count, export_moves, export_logger
            for line in move_lines:
                if export_config.do_checks and invoice:
                    auto_account_id = self.env['export.auto.account'].search([('account_id', '=', line.account_id.id)])
                    tax_id = auto_account_id.vat_code
                    line_tax_id = self.env['account.tax'].search([('id', '=', line.tax_ids.id)])
                    if tax_id and tax_id.id != line.tax_ids.id:
                        line_tax_id = self.env['account.tax'].search([('id', '=', line.tax_ids.id)])
                        error1 = _('Tax Code "%s" of the Auto Account is not the same as Tax Code "%s" in the Account Line "%s" of Move "%s"') % (tax_id.description, line_tax_id.description, line.name, move.name)
                        log.append(error1)
                        export_logger.append(error1)
                        export_errors += 1
                        move_error +=1
                        continue
                if account['sign'] == 's' and line.debit:
                    account['sign'] = 'h'
                if account['sign'] == 'h' and line.credit:
                    account['sign'] = 's'
                res = self.st_build_line(account, move, res, line, invoice, export_config)
                if res['account_offset'] != res['account']:
                    if res['account_offset'] in tax_accounts:
                        continue
                    elif res['account'] in tax_accounts:
                        continue
                    else:
                        booking.append(res.copy())
                        export_moves += 1
            if not booking:
                move_error += 1
                export_errors += 1
                error2 = _('Move %s can not be exported, because it is empty.') % (move.name)
                log.append(error2)
                export_logger.append(error2)
            grouped_lines = self.st_create_group(booking)
            """
            amount_check_s = 0.0
            amount_check_h = 0.0
            for gl in grouped_lines: