/*
 * Copyright 2007 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 std::fmt;

use crate::{
    Exceptions,
    common::{BitMatrix, Result},
    qrcode::cpp_port::Type,
};

use super::{ErrorCorrectionLevel, FormatInformation};

use once_cell::sync::Lazy;

pub type VersionRef = &'static Version;

pub static VERSIONS: Lazy<Box<[Version]>> = Lazy::new(Version::buildVersions);
pub static MICRO_VERSIONS: Lazy<Box<[Version]>> = Lazy::new(Version::build_micro_versions);
pub static MODEL1_VERSIONS: Lazy<Box<[Version]>> = Lazy::new(Version::build_model1_versions);
pub static RMQR_VERSIONS: Lazy<Box<[Version]>> = Lazy::new(Version::build_rmqr_versions);

/**
 * See ISO 18004:2006 Annex D.
 * Element i represents the raw version bits that specify version i + 7
 */
pub const VERSION_DECODE_INFO: [u32; 34] = [
    0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6, 0x0C762, 0x0D847, 0x0E60D, 0x0F928, 0x10B78,
    0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683, 0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB,
    0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250, 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B,
    0x2542E, 0x26A64, 0x27541, 0x28C69,
];

// const VERSIONS: &'static[Version] = &Version::buildVersions();
/**
 * See ISO 18004:2006 Annex D
 *
 * @author Sean Owen
 */
#[derive(Debug)]
pub struct Version {
    //   private static final Version[] VERSIONS = buildVersions();
    versionNumber: u32,
    alignmentPatternCenters: Box<[u32]>,
    ecBlocks: Box<[ECBlocks]>,
    totalCodewords: u32,
    pub(crate) qr_type: Type,
}
impl Version {
    pub(super) fn new(
        versionNumber: u32,
        alignmentPatternCenters: Box<[u32]>,
        ecBlocks: [ECBlocks; 4],
    ) -> Self {
        let mut total = 0;
        let ecCodewords = ecBlocks[1].getECCodewordsPerBlock();
        let ecbArray = ecBlocks[1].getECBlocks();
        // let mut i = 0;
        for ecb in ecbArray {
            // while i < ecbArray.len() {
            total += ecb.getCount() * (ecb.getDataCodewords() + ecCodewords);
            // i += 1;
        }

        Self {
            versionNumber,
            alignmentPatternCenters,
            qr_type: if ecBlocks[0].getECCodewordsPerBlock() != 0 {
                Type::Model2
            } else {
                Type::RectMicro
            },
            ecBlocks: Box::new(ecBlocks),
            totalCodewords: total,
        }
    }

    pub(super) fn without_alignment_patterns(
        versionNumber: u32,
        ecBlocks: Box<[ECBlocks]>,
    ) -> Self {
        let mut total = 0;
        let ecCodewords = ecBlocks[0].getECCodewordsPerBlock();
        let ecbArray = ecBlocks[0].getECBlocks();
        for ecb_array_element in ecbArray {
            total +=
                ecb_array_element.getCount() * (ecb_array_element.getDataCodewords() + ecCodewords);
        }

        let symbol_type = if ecBlocks[0].getECCodewordsPerBlock() < 7
            || ecBlocks[0].getECCodewordsPerBlock() == 8
        {
            Type::Micro
        } else {
            Type::Model1
        };

        Self {
            versionNumber,
            alignmentPatternCenters: Box::default(),
            ecBlocks,
            totalCodewords: total,
            qr_type: symbol_type,
        }
    }

    pub const fn getVersionNumber(&self) -> u32 {
        self.versionNumber
    }

    pub const fn getAlignmentPatternCenters(&self) -> &[u32] {
        &self.alignmentPatternCenters
    }

    pub const fn getTotalCodewords(&self) -> u32 {
        self.totalCodewords
    }

    pub fn getDimensionForVersion(&self) -> u32 {
        Self::DimensionOfVersion(self.versionNumber, self.qr_type == Type::Micro)
        // 17 + 4 * self.versionNumber
    }

    pub const fn getECBlocksForLevel(&self, ecLevel: ErrorCorrectionLevel) -> &ECBlocks {
        if ecLevel.get_ordinal() as usize >= self.ecBlocks.len() {
            return &self.ecBlocks[ecLevel.get_ordinal() as usize % self.ecBlocks.len()];
        }
        &self.ecBlocks[ecLevel.get_ordinal() as usize]
    }

    /**
     * <p>Deduces version information purely from QR Code dimensions.</p>
     *
     * @param dimension dimension in modules
     * @return Version for a QR Code of that dimension
     * @throws FormatException if dimension is not 1 mod 4 or dimension less than 17
     */
    pub fn getProvisionalVersionForDimension(dimension: u32) -> Result<VersionRef> {
        if dimension % 4 != 1 || dimension < 17 {
            return Err(Exceptions::format_with("dimension incorrect"));
        }
        Self::getVersionForNumber((dimension - 17) / 4)
    }

    pub fn getVersionForNumber(versionNumber: u32) -> Result<VersionRef> {
        if !(1..=40).contains(&versionNumber) {
            return Err(Exceptions::illegal_argument_with("version out of spec"));
        }
        Ok(&VERSIONS[versionNumber as usize - 1])
    }

    pub fn decodeVersionInformation(versionBits: u32) -> Result<VersionRef> {
        let mut bestDifference = u32::MAX;
        let mut bestVersion = 0;
        for i in 0..VERSION_DECODE_INFO.len() as u32 {
            let targetVersion = VERSION_DECODE_INFO[i as usize];
            // Do the version info bits match exactly? done.
            if targetVersion == versionBits {
                return Self::getVersionForNumber(i + 7);
            }
            // Otherwise see if this is the closest to a real version info bit string
            // we have seen so far
            let bitsDifference = FormatInformation::numBitsDiffering(versionBits, targetVersion);
            if bitsDifference < bestDifference {
                bestVersion = i + 7;
                bestDifference = bitsDifference;
            }
        }
        // We can tolerate up to 3 bits of error since no two version info codewords will
        // differ in less than 8 bits.
        if bestDifference <= 3 {
            return Self::getVersionForNumber(bestVersion);
        }
        // If we didn't find a close enough match, fail
        Err(Exceptions::NOT_FOUND)
    }

    /**
     * See ISO 18004:2006 Annex E
     */
    pub fn buildFunctionPattern(&self) -> Result<BitMatrix> {
        if self.isRMQR() {
            let size = Version::SymbolSize(self.versionNumber, Type::RectMicro);
            let mut bitMatrix = BitMatrix::new(size.x as u32, size.y as u32)?;

            // Set edge timing patterns
            bitMatrix.setRegion(0, 0, size.x as u32, 1)?; // Top
            bitMatrix.setRegion(0, (size.y - 1) as u32, size.x as u32, 1)?; // Bottom
            bitMatrix.setRegion(0, 1, 1, (size.y - 2) as u32)?; // Left
            bitMatrix.setRegion((size.x - 1) as u32, 1, 1, (size.y - 2) as u32)?; // Right

            // Set vertical timing and alignment patterns
            let max = self.alignmentPatternCenters.len(); // Same as vertical timing column
            for x in 0..max {
                // for (size_t x = 0; x < max; ++x) {
                let cx = self.alignmentPatternCenters[x];
                bitMatrix.setRegion(cx - 1, 1, 3, 2)?; // Top alignment pattern
                bitMatrix.setRegion(cx - 1, (size.y - 3) as u32, 3, 2)?; // Bottom alignment pattern
                bitMatrix.setRegion(cx, 3, 1, (size.y - 6) as u32)?; // Vertical timing pattern
            }

            // Top left finder pattern + separator
            bitMatrix.setRegion(1, 1, 8 - 1, 8 - 1 - u32::from(size.y == 7))?; // R7 finder bottom flush with edge
            // Top left format
            bitMatrix.setRegion(8, 1, 3, 5)?;
            bitMatrix.setRegion(11, 1, 1, 3)?;

            // Bottom right finder subpattern
            bitMatrix.setRegion((size.x - 5) as u32, (size.y - 5) as u32, 5 - 1, 5 - 1)?;
            // Bottom right format
            bitMatrix.setRegion((size.x - 8) as u32, (size.y - 6) as u32, 3, 5)?;
            bitMatrix.setRegion((size.x - 5) as u32, (size.y - 6) as u32, 3, 1)?;

            // Top right corner finder
            bitMatrix.set((size.x - 2) as u32, 1);
            if size.y > 9 {
                // Bottom left corner finder
                bitMatrix.set(1, (size.y - 2) as u32);
            }

            return Ok(bitMatrix);
        }

        let dimension = self.getDimensionForVersion();
        let mut bitMatrix = BitMatrix::with_single_dimension(dimension)?;

        // Top left finder pattern + separator + format
        bitMatrix.setRegion(0, 0, 9, 9)?;

        if self.qr_type != Type::Micro {
            // Top right finder pattern + separator + format
            bitMatrix.setRegion(dimension - 8, 0, 8, 9)?;
            // Bottom left finder pattern + separator + format
            bitMatrix.setRegion(0, dimension - 8, 9, 8)?;

            // Alignment patterns
            let max = self.alignmentPatternCenters.len();
            for x in 0..max {
                let i = self.alignmentPatternCenters[x] - 2;
                for y in 0..max {
                    if (x != 0 || (y != 0 && y != max - 1)) && (x != max - 1 || y != 0) {
                        bitMatrix.setRegion(self.alignmentPatternCenters[y] - 2, i, 5, 5)?;
                    }
                    // else no o alignment patterns near the three finder patterns
                }
            }

            // Vertical timing pattern
            bitMatrix.setRegion(6, 9, 1, dimension - 17)?;
            // Horizontal timing pattern
            bitMatrix.setRegion(9, 6, dimension - 17, 1)?;

            if self.versionNumber > 6 {
                // Version info, top right
                bitMatrix.setRegion(dimension - 11, 0, 3, 6)?;
                // Version info, bottom left
                bitMatrix.setRegion(0, dimension - 11, 6, 3)?;
            }
        } else {
            // Vertical timing pattern
            bitMatrix.setRegion(9, 0, dimension - 9, 1)?;

            // Horizontal timing pattern
            bitMatrix.setRegion(0, 9, 1, dimension - 9)?;
        }

        Ok(bitMatrix)
    }
}

impl fmt::Display for Version {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.versionNumber)
    }
}

/**
 * <p>Encapsulates a set of error-correction blocks in one symbol version. Most versions will
 * use blocks of differing sizes within one version, so, this encapsulates the parameters for
 * each set of blocks. It also holds the number of error-correction codewords per block since it
 * will be the same across all blocks within one version.</p>
 */
#[derive(Debug, Clone)]
pub struct ECBlocks {
    ecCodewordsPerBlock: u32,
    ecBlocks: Box<[ECB]>,
}

impl ECBlocks {
    pub const fn new(ecCodewordsPerBlock: u32, ecBlocks: Box<[ECB]>) -> Self {
        Self {
            ecCodewordsPerBlock,
            ecBlocks,
        }
    }

    pub const fn getECCodewordsPerBlock(&self) -> u32 {
        self.ecCodewordsPerBlock
    }

    pub fn getNumBlocks(&self) -> u32 {
        let mut total = 0;
        for ecBlock in self.ecBlocks.iter() {
            total += ecBlock.getCount();
        }
        total
    }

    pub fn getTotalECCodewords(&self) -> u32 {
        self.ecCodewordsPerBlock * self.getNumBlocks()
    }

    pub fn getECBlocks(&self) -> &[ECB] {
        &self.ecBlocks
    }
}

/**
 * <p>Encapsulates the parameters for one error-correction block in one symbol version.
 * This includes the number of data codewords, and the number of times a block with these
 * parameters is used consecutively in the QR code version's format.</p>
 */
#[derive(Debug, Clone, Copy)]
pub struct ECB {
    count: u32,
    dataCodewords: u32,
}

impl ECB {
    pub const fn new(count: u32, dataCodewords: u32) -> Self {
        Self {
            count,
            dataCodewords,
        }
    }

    pub const fn getCount(&self) -> u32 {
        self.count
    }

    pub const fn getDataCodewords(&self) -> u32 {
        self.dataCodewords
    }
}
