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

#
#  Copyright 2019 The FATE Authors. All Rights Reserved.
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.


import numpy as np

from federatedml.framework.weights import ListWeights, TransferableWeights
from federatedml.util import LOGGER, paillier_check, ipcl_operator


class LinearModelWeights(ListWeights):
    def __init__(self, l, fit_intercept, raise_overflow_error=True):
        l = np.array(l)
        if l.shape != (0,) and not paillier_check.is_paillier_encrypted_number(l):
            if np.max(np.abs(l)) > 1e8:
                if raise_overflow_error:
                    raise RuntimeError(
                        "The model weights are overflow, please check if the input data has been normalized")
                else:
                    LOGGER.warning(
                        f"LinearModelWeights contains entry greater than 1e8.")
        super().__init__(l)
        self.fit_intercept = fit_intercept
        self.raise_overflow_error = raise_overflow_error

    def for_remote(self):
        return TransferableWeights(self._weights, self.__class__, self.fit_intercept)

    @property
    def coef_(self):
        if self.fit_intercept:
            if paillier_check.is_single_ipcl_encrypted_number(self._weights):
                coeffs = ipcl_operator.get_coeffs(self._weights.item(0))
                return np.array(coeffs)

            return np.array(self._weights[:-1])
        return np.array(self._weights)

    @property
    def intercept_(self):
        if self.fit_intercept:
            if paillier_check.is_single_ipcl_encrypted_number(self._weights):
                return ipcl_operator.get_intercept(self._weights.item(0))

            return 0.0 if len(self._weights) == 0 else self._weights[-1]
        return 0.0

    def binary_op(self, other: 'LinearModelWeights', func, inplace):
        if inplace:
            for k, v in enumerate(self._weights):
                self._weights[k] = func(self._weights[k], other._weights[k])
            return self
        else:
            _w = []
            for k, v in enumerate(self._weights):
                _w.append(func(self._weights[k], other._weights[k]))
            return LinearModelWeights(_w, self.fit_intercept, self.raise_overflow_error)

    def map_values(self, func, inplace):
        if paillier_check.is_single_ipcl_encrypted_number(self._weights):
            if inplace:
                self._weights = np.array(func(self.unboxed.item(0)))
                return self
            else:
                _w = func(self.unboxed.item(0))
                return LinearModelWeights(_w, self.fit_intercept)

        if inplace:
            for k, v in enumerate(self._weights):
                self._weights[k] = func(v)
            return self
        else:
            _w = []
            for v in self._weights:
                _w.append(func(v))
            return LinearModelWeights(_w, self.fit_intercept)

    def __repr__(self):
        return f"weights: {self.coef_}, intercept: {self.intercept_}"
