from __future__ import absolute_import
import hashlib

import numpy as nm
import warnings

import scipy.sparse as sps
import six
from six.moves import range

warnings.simplefilter('ignore', sps.SparseEfficiencyWarning)

from sfepy.base.base import output, get_default, assert_, try_imports
from sfepy.base.timing import Timer
from sfepy.solvers.solvers import LinearSolver

def solve(mtx, rhs, solver_class=None, solver_conf=None):
    """
    Solve the linear system with the matrix `mtx` and the right-hand side
    `rhs`.

    Convenience wrapper around the linear solver classes below.
    """
    solver_class = get_default(solver_class, ScipyDirect)
    solver_conf = get_default(solver_conf, {})

    solver = solver_class(solver_conf, mtx=mtx)
    solution = solver(rhs)

    return solution

def _get_cs_matrix_hash(mtx, chunk_size=100000):
    def _gen_array_chunks(arr):
        ii = 0
        while len(arr[ii:]):
            yield arr[ii:ii+chunk_size].tobytes()
            ii += chunk_size

    sha1 = hashlib.sha1()
    for chunk in _gen_array_chunks(mtx.indptr):
        sha1.update(chunk)
    for chunk in _gen_array_chunks(mtx.indices):
        sha1.update(chunk)
    for chunk in _gen_array_chunks(mtx.data):
        sha1.update(chunk)

    digest = sha1.hexdigest()
    return digest

def _is_new_matrix(mtx, mtx_digest, force_reuse=False):
    if not isinstance(mtx, sps.csr_matrix):
        return True, mtx_digest

    if force_reuse:
        return False, mtx_digest

    id0, digest0 = mtx_digest
    id1 = id(mtx)
    digest1 = _get_cs_matrix_hash(mtx)
    if (id1 == id0) and (digest1 == digest0):
        return False, (id1, digest1)

    return True, (id1, digest1)

def standard_call(call):
    """
    Decorator handling argument preparation and timing for linear solvers.
    """
    def _standard_call(self, rhs, x0=None, conf=None, eps_a=None, eps_r=None,
                       i_max=None, mtx=None, status=None, context=None,
                       **kwargs):
        timer = 
Timer(start=True)