/*
 * Copyright 2012 ZXing authors
 *
 * 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.
 */

use crate::Exceptions;
use crate::common::Result;

use super::ModulusGF;

/**
 * @author Sean Owen
 */
#[derive(Clone, Debug)]
pub struct ModulusPoly {
    field: &'static ModulusGF,
    coefficients: Vec<u32>,
}
impl ModulusPoly {
    pub fn new(field: &'static ModulusGF, coefficients: Vec<u32>) -> Result<ModulusPoly> {
        if coefficients.is_empty() {
            return Err(Exceptions::ILLEGAL_ARGUMENT);
        }
        let orig_coefs = coefficients.clone();
        let mut coefficients = coefficients;
        let coefficientsLength = coefficients.len();
        if coefficientsLength > 1 && coefficients[0] == 0 {
            // Leading term must be non-zero for anything except the constant polynomial "0"
            let mut firstNonZero = 1;
            while firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0 {
                firstNonZero += 1;
            }
            if firstNonZero == coefficientsLength {
                coefficients = vec![0];
            } else {
                coefficients = vec![0u32; coefficientsLength - firstNonZero];
                coefficients[..].copy_from_slice(&orig_coefs[firstNonZero..]);
                // System.arraycopy(coefficients,
                //     firstNonZero,
                //     this.coefficients,
                //     0,
                //     this.coefficients.length);
            }
        }
        // } else {
        //     coefficients = coefficients;
        // }

        Ok(ModulusPoly {
            field,
            coefficients,
            // zero: Some(Self::getZero(field.clone())),
            // one: Some(Self::getOne(field.clone())),
        })
    }

    pub fn getCoefficients(&self) -> &[u32] {
        &self.coefficients
    }

    /**
     * @return degree of this polynomial
     */
    pub fn getDegree(&self) -> u32 {
        self.coefficients.len() as u32 - 1
    }

    /**
     * @return true iff this polynomial is the monomial "0"
     */
    pub fn isZero(&self) -> bool {
        self.coefficients[0] == 0
    }

    /**
     * @return coefficient of x^degree term in this polynomial
     */
    pub fn getCoefficient(&self, degree: usize) -> u32 {
        self.coefficients[self.coefficients.len() - 1 - degree]
    }

    /**
     * @return evaluation of this polynomial at a given point
     */
    pub fn evaluateAt(&self, a: u32) -> u32 {
        if a == 0 {
            // Just return the x^0 coefficient
            return self.getCoefficient(0);
        }
        if a == 1 {
            // Just the sum of the coefficients
            let mut result = 0;
            for coefficient in self.coefficients.iter() {
                // for (int coefficient : coefficients) {
                result = self.field.add(result, *coefficient);
            }
            return result;
        }
        let mut result = self.coefficients[0];
        let size = self.coefficients.len();
        for i in 1..size {
            // for (int i = 1; i < size; i++) {
            result = self
                .field
                .add(self.field.multiply(a, result), self.coefficients[i]);
        }
        result
    }

    pub fn add(&self, other: ModulusPoly) -> Result<ModulusPoly> {
        if self.field != other.field {
            return Err(Exceptions::illegal_argument_with(
                "ModulusPolys do not have same ModulusGF field",
            ));
        }
        if self.isZero() {
            return Ok(other);
        }
        if other.isZero() {
            return Ok(self.clone());
        }

        let mut smallerCoefficients = &self.coefficients;
        let mut largerCoefficients = &other.coefficients;
        if smallerCoefficients.len() > largerCoefficients.len() {
            std::mem::swap(&mut smallerCoefficients, &mut largerCoefficients);
        }
        let mut sumDiff = vec![0_u32; largerCoefficients.len()];
        let lengthDiff = largerCoefficients.len() - smallerCoefficients.len();
        // Copy high-order terms only found in higher-degree polynomial's coefficients
        sumDiff[..lengthDiff].copy_from_slice(&largerCoefficients[..lengthDiff]);
        // System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff);

        for i in lengthDiff..largerCoefficients.len() {
            // for (int i = lengthDiff; i < largerCoefficients.length; i++) {
            sumDiff[i] = self
                .field
                .add(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
        }

        ModulusPoly::new(self.field, sumDiff)
    }

    pub fn subtract(&self, other: ModulusPoly) -> Result<ModulusPoly> {
        if self.field != other.field {
            return Err(Exceptions::illegal_argument_with(
                "ModulusPolys do not have same ModulusGF field",
            ));
        }
        if other.isZero() {
            return Ok(self.clone());
        };
        self.add(other.negative())
    }

    pub fn multiply(&self, other: ModulusPoly) -> Result<ModulusPoly> {
        if !(self.field == other.field) {
            return Err(Exceptions::illegal_argument_with(
                "ModulusPolys do not have same ModulusGF field",
            ));
        }
        if self.isZero() || other.isZero() {
            return Ok(Self::getZero(self.field));
        }
        let aCoefficients = &self.coefficients;
        let aLength = aCoefficients.len();
        let bCoefficients = &other.coefficients;
        let bLength = bCoefficients.len();
        let mut product = vec![0u32; aLength + bLength - 1];
        for i in 0..aLength {
            // for (int i = 0; i < aLength; i++) {
            let aCoeff = aCoefficients[i];
            for j in 0..bLength {
                // for (int j = 0; j < bLength; j++) {
                product[i + j] = self.field.add(
                    product[i + j],
                    self.field.multiply(aCoeff, bCoefficients[j]),
                );
            }
        }

        ModulusPoly::new(self.field, product)
    }

    pub fn negative(&self) -> ModulusPoly {
        let size = self.coefficients.len();
        let mut negativeCoefficients = vec![0u32; size];
        for (i, neg_coef) in negativeCoefficients.iter_mut().enumerate().take(size) {
            // for (int i = 0; i < size; i++) {
            *neg_coef = self.field.subtract(0, self.coefficients[i]);
        }

        ModulusPoly::new(self.field, negativeCoefficients)
            .expect("should always generate with known goods")
    }

    pub fn multiplyByScaler(&self, scalar: u32) -> ModulusPoly {
        if scalar == 0 {
            return Self::getZero(self.field);
        }
        if scalar == 1 {
            return self.clone();
        }
        let size = self.coefficients.len();
        let mut product = vec![0u32; size];
        for (i, prod) in product.iter_mut().enumerate().take(size) {
            // for (int i = 0; i < size; i++) {
            *prod = self.field.multiply(self.coefficients[i], scalar);
        }

        ModulusPoly::new(self.field, product).expect("should always generate with known goods")
    }

    pub fn multiplyByMonomial(&self, degree: usize, coefficient: u32) -> ModulusPoly {
        if coefficient == 0 {
            return Self::getZero(self.field);
        }
        let size = self.coefficients.len();
        let mut product = vec![0u32; size + degree];
        for (i, prod) in product.iter_mut().enumerate().take(size) {
            // for (int i = 0; i < size; i++) {
            *prod = self.field.multiply(self.coefficients[i], coefficient);
        }

        ModulusPoly::new(self.field, product).expect("should always generate with known goods")
    }

    pub fn getZero(field: &'static ModulusGF) -> ModulusPoly {
        ModulusPoly::new(field, vec![0]).expect("should always generate with known goods")
    }

    pub fn getOne(field: &'static ModulusGF) -> ModulusPoly {
        ModulusPoly::new(field, vec![1]).expect("should always generate with known goods")
    }

    pub fn buildMonomial(
        field: &'static ModulusGF,
        degree: usize,
        coefficient: u32,
    ) -> ModulusPoly {
        // if degree < 0 {
        //   throw new IllegalArgumentException();
        // }
        if coefficient == 0 {
            return Self::getZero(field);
        }
        let mut coefficients = vec![0_u32; degree + 1];
        coefficients[0] = coefficient;

        ModulusPoly::new(field, coefficients).expect("should always generate with known goods")
    }
}
