#!/usr/bin/env python
# -*- coding: utf-8 -*-

''' Module for Individual with binary encoding.
'''
from math import log2
from itertools import accumulate
import logging

from .individual import IndividualBase
from ..mpiutil import MPIUtil

mpi = MPIUtil()


class BinaryIndividual(IndividualBase):
    '''
    Class for individual in population. Random solution will be initialized
    by default.

    :param ranges: value ranges for all entries in solution.
    :type ranges: tuple list

    :param eps: decrete precisions for binary encoding, default is 0.001.
    :type eps: float or float list (with the same length with ranges)

    .. Note:

        The decrete precisions for different components in varants may be
        adjusted automatically (possible precision loss) if eps and ranges
        are not appropriate.
    '''
    def __init__(self, ranges, eps=0.001):
        super(self.__class__, self).__init__(ranges, eps)

        # Lengths for all binary sequence in chromsome and adjusted decrete precisions.
        self.lengths = []

        for i, ((a, b), eps) in enumerate(zip(self.ranges, self.eps)):
            length = int(log2((b - a)/eps))
            precision = (b - a)/(2**length)
            self.lengths.append(length)
            self.precisions[i] = precision

        # The start and end indices for each gene segment for entries in solution.
        self.gene_indices = self._get_gene_indices()

        # Initialize individual randomly.
        self.init()

    def encode(self):
        ''' Encode solution to gene sequence in individual using different encoding.
        '''
        chromsome = []
        for var, (a, _), length, eps in zip(self.solution, self.ranges,
                                            self.lengths, self.precisions):
            chromsome.extend(self.binarize(var-a, eps, length))

        return chromsome

    def decode(self):
        ''' Decode gene sequence to solution of target function.
        '''
        solution =  [self.decimalize(self.chromsome[start: end], eps, lower_bound)
                     for (start, end), (lower_bound, _), eps in
                     zip(self.gene_indices, self.ranges, self.precisions)]
        return solution

    def _get_gene_indices(self):
        '''
        Helper function to get gene slice indices.
        '''
        end_indices = list(accumulate(self.lengths))
        start_indices = [0] + end_indices[: -1]
        return list(zip(start_indices, end_indices))

    @staticmethod
    def binarize(decimal, eps, length):
        ''' Helper function to convert a float to binary sequence.

        :param decimal: the decimal number to be converted
        :type decimal: float

        :param eps: the decrete precision of binary sequence
        :type eps: float

        :param length: the length of binary sequence.
        :type length: int
        '''
        n = int(decimal/eps)
        bin_str = '{:0>{}b}'.format(n, length)
        return [int(i) for i in bin_str]

    @staticmethod
    def decimalize(binary, eps, lower_bound):
        ''' Helper function to convert a binary sequence back to decimal number.

        :param binary: The binary list to be converted
        :type binary: list of int

        :param eps: the decrete precision of binary sequence
        :type eps: float

        :param lower_bound: the lower bound for decimal number
        :type lower_bound: float
        '''
        bin_str = ''.join([str(bit) for bit in binary])
        return lower_bound + int(bin_str, 2)*eps