/*
 * Copyright (c) 2012-2020 MIRACL UK Ltd.
 *
 * This file is part of MIRACL Core
 * (see https://github.com/miracl/core).
 *
 * 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.
 */

pub const HASH224: usize = 28;
pub const HASH256: usize = 32;
pub const HASH384: usize = 48;
pub const HASH512: usize = 64;
pub const SHAKE128: usize = 16;
pub const SHAKE256: usize = 32;

const ROUNDS: usize = 24;

const RC: [u64; 24] = [
    0x0000000000000001,
    0x0000000000008082,
    0x800000000000808A,
    0x8000000080008000,
    0x000000000000808B,
    0x0000000080000001,
    0x8000000080008081,
    0x8000000000008009,
    0x000000000000008A,
    0x0000000000000088,
    0x0000000080008009,
    0x000000008000000A,
    0x000000008000808B,
    0x800000000000008B,
    0x8000000000008089,
    0x8000000000008003,
    0x8000000000008002,
    0x8000000000000080,
    0x000000000000800A,
    0x800000008000000A,
    0x8000000080008081,
    0x8000000000008080,
    0x0000000080000001,
    0x8000000080008008,
];

pub struct SHA3 {
    length: usize,
    rate: usize,
    len: usize,
    //s: [[u64; 5]; 5],
    s: [u64;25],
}

impl SHA3 {
    fn rotl(x: u64, n: u64) -> u64 {
        ((x) << n) | ((x) >> (64 - n))
    }

    fn transform(&mut self) {
        for k in 0..ROUNDS {
            let c0=self.s[0]^self.s[5]^self.s[10]^self.s[15]^self.s[20];
            let c1=self.s[1]^self.s[6]^self.s[11]^self.s[16]^self.s[21];
            let c2=self.s[2]^self.s[7]^self.s[12]^self.s[17]^self.s[22];
            let c3=self.s[3]^self.s[8]^self.s[13]^self.s[18]^self.s[23];
            let c4=self.s[4]^self.s[9]^self.s[14]^self.s[19]^self.s[24];

            let d0=c4^SHA3::rotl(c1,1);
            let d1=c0^SHA3::rotl(c2,1);
            let d2=c1^SHA3::rotl(c3,1);
            let d3=c2^SHA3::rotl(c4,1);
            let d4=c3^SHA3::rotl(c0,1);

            let b00 = self.s[0]^d0;
            let b02 = SHA3::rotl(self.s[1]^d1, 1);
            let b04 = SHA3::rotl(self.s[2]^d2, 62);
            let b01 = SHA3::rotl(self.s[3]^d3, 28);
            let b03 = SHA3::rotl(self.s[4]^d4, 27);

            let b13 = SHA3::rotl(self.s[5]^d0, 36);
            let b10 = SHA3::rotl(self.s[6]^d1, 44);
            let b12 = SHA3::rotl(self.s[7]^d2, 6);
            let b14 = SHA3::rotl(self.s[8]^d3, 55);
            let b11 = SHA3::rotl(self.s[9]^d4, 20);

            let b21 = SHA3::rotl(self.s[10]^d0, 3);
            let b23 = SHA3::rotl(self.s[11]^d1, 10);
            let b20 = SHA3::rotl(self.s[12]^d2, 43);
            let b22 = SHA3::rotl(self.s[13]^d3, 25);
            let b24 = SHA3::rotl(self.s[14]^d4, 39);

            let b34 = SHA3::rotl(self.s[15]^d0, 41);
            let b31 = SHA3::rotl(self.s[16]^d1, 45);
            let b33 = SHA3::rotl(self.s[17]^d2, 15);
            let b30 = SHA3::rotl(self.s[18]^d3, 21);
            let b32 = SHA3::rotl(self.s[19]^d4, 8);

            let b42 = SHA3::rotl(self.s[20]^d0, 18);
            let b44 = SHA3::rotl(self.s[21]^d1, 2);
            let b41 = SHA3::rotl(self.s[22]^d2, 61);
            let b43 = SHA3::rotl(self.s[23]^d3, 56);
            let b40 = SHA3::rotl(self.s[24]^d4, 14);   

            self.s[0]=b00^(!b10&b20);
            self.s[1]=b10^(!b20&b30);
            self.s[2]=b20^(!b30&b40);
            self.s[3]=b30^(!b40&b00);
            self.s[4]=b40^(!b00&b10);

            self.s[5]=b01^(!b11&b21);
            self.s[6]=b11^(!b21&b31);
            self.s[7]=b21^(!b31&b41);
            self.s[8]=b31^(!b41&b01);
            self.s[9]=b41^(!b01&b11);

            self.s[10]=b02^(!b12&b22);
            self.s[11]=b12^(!b22&b32);
            self.s[12]=b22^(!b32&b42);
            self.s[13]=b32^(!b42&b02);
            self.s[14]=b42^(!b02&b12);

            self.s[15]=b03^(!b13&b23);
            self.s[16]=b13^(!b23&b33);
            self.s[17]=b23^(!b33&b43);
            self.s[18]=b33^(!b43&b03);
            self.s[19]=b43^(!b03&b13);

            self.s[20]=b04^(!b14&b24);
            self.s[21]=b14^(!b24&b34);
            self.s[22]=b24^(!b34&b44);
            self.s[23]=b34^(!b44&b04);
            self.s[24]=b44^(!b04&b14);

            self.s[0] ^= RC[k];
        }
    }

    /* Initialise Hash function */
    pub fn init(&mut self, olen: usize) {
        /* initialise */
        for i in 0..25 {
            self.s[i] = 0;
        }
        self.length = 0;
        self.len = olen;
        self.rate = 200 - 2 * olen;
    }

    pub fn new(olen: usize) -> SHA3 {
        let mut nh = SHA3 {
            length: 0,
            rate: 0,
            len: 0,
            s: [0; 25],
        };
        nh.init(olen);
        nh
    }

    pub fn new_copy(hh: &SHA3) -> SHA3 {
        let mut nh = SHA3 {
            length: 0,
            rate: 0,
            len: 0,
            s: [0; 25],
        };
        nh.length=hh.length;
        nh.len=hh.len;
        nh.rate=hh.rate;
        for i in 0..25 {
            nh.s[i] = hh.s[i];
        }
        nh
    }

    /* process a single byte */
    pub fn process(&mut self, byt: u8) {
        /* process the next message byte */
        let cnt = self.length as usize;
        let b = cnt % 8;
        let ind = cnt / 8;
        self.s[ind] ^= (byt as u64) << (8 * b);
        self.length += 1;
        if self.length == self.rate {
            self.length=0;
            self.transform();
        }
    }

    /* process an array of bytes */

    pub fn process_array(&mut self, b: &[u8]) {
        for i in 0..b.len() {
            self.process(b[i])
        }
    }

    /* process a 32-bit integer */
    pub fn process_num(&mut self, n: i32) {
        self.process(((n >> 24) & 0xff) as u8);
        self.process(((n >> 16) & 0xff) as u8);
        self.process(((n >> 8) & 0xff) as u8);
        self.process((n & 0xff) as u8);
    }

    pub fn squeeze(&mut self, buff: &mut [u8], olen: usize) {
        let mut m = 0;
        let nb=olen/self.rate;

        for _ in 0..nb {
            for i in 0..self.rate/8 {
                let mut el=self.s[i];
                for _ in 0..8 {
                    buff[m]=(el & 0xff) as u8;
                    m += 1;
                    el >>= 8;
                }
            }
            self.transform();
        }

        let mut i=0;
        while m<olen {
            let mut el=self.s[i]; i += 1;
            for _ in 0..8 {
                buff[m]=(el & 0xff) as u8;
                m += 1;
                if m >= olen {
                    break;
                }
                el >>= 8;
            }
        }

/*
        loop {
            for i in 0..25 {
                let mut el = self.s[i];
                for _ in 0..8 {
                    buff[m] = (el & 0xff) as u8;
                    m += 1;
                    if m >= olen || (m % self.rate) == 0 {
                        done = true;
                        break;
                    }
                    el >>= 8;
                }
                if done {
                    break;
                }
            }
            if m >= olen {
                break;
            }
            done = false;
            self.transform();
        } */
    }

    /* Generate 32-byte Hash */
    pub fn hash(&mut self, digest: &mut [u8]) {
        /* pad message and finish - supply digest */
        let q = self.rate - self.length;
        if q == 1 {
            self.process(0x86);
        } else {
            self.process(0x06);
            while self.length != self.rate - 1 {
                self.process(0x00)
            }
            self.process(0x80);
        }
        let hlen = self.len as usize;
        self.squeeze(digest, hlen);
    }

    pub fn continuing_hash(&mut self, digest: &mut [u8]) {
        let mut sh=SHA3::new_copy(self);    
        sh.hash(digest)
    }

    pub fn shake(&mut self, digest: &mut [u8], olen: usize) {
        let q = self.rate - self.length;
        if q == 1 {
            self.process(0x9f);
        } else {
            self.process(0x1f);
            while self.length != self.rate - 1 {
                self.process(0x00)
            }
            self.process(0x80);
        }
        self.squeeze(digest, olen);
    }

    pub fn continuing_shake(&mut self, digest: &mut [u8], olen: usize) {
        let mut sh=SHA3::new_copy(self); 
        sh.shake(digest,olen);
    }
}

//916f6061fe879741ca6469b43971dfdb28b1a32dc36cb3254e812be27aad1d18
//afebb2ef542e6579c50cad06d2e578f9f8dd6881d7dc824d26360feebf18a4fa73e3261122948efcfd492e74e82e2189ed0fb440d187f382270cb455f21dd185
//98be04516c04cc73593fef3ed0352ea9f6443942d6950e29a372a681c3deaf4535423709b02843948684e029010badcc0acd8303fc85fdad3eabf4f78cae165635f57afd28810fc2
/*
fn main() {
    let s = String::from("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
    let mut digest: [u8;100]=[0;100];
    let test = s.into_bytes();

    let mut sh=SHA3::new(HASH256);
    for i in 0..test.len(){
        sh.process(test[i]);
    }
    sh.hash(&mut digest);
    for i in 0..32 {print!("{:02x}",digest[i])}
    println!("");

    sh=SHA3::new(HASH512);
    for i in 0..test.len(){
        sh.process(test[i]);
    }
    sh.hash(&mut digest);
    for i in 0..64 {print!("{:02x}",digest[i])}
    println!("");

    sh=SHA3::new(SHAKE256);
    for i in 0..test.len(){
        sh.process(test[i]);
    }
    sh.shake(&mut digest,72);
    for i in 0..72 {print!("{:02x}",digest[i])}
    println!("");

} */
