/*
 * Copyright 2010 Srikanth Reddy Lingala
 *
 * 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.
 */

package net.lingala.zip4j.crypto;

import net.lingala.zip4j.crypto.engine.ZipCryptoEngine;
import net.lingala.zip4j.exception.ZipException;

import java.security.SecureRandom;

import static net.lingala.zip4j.util.InternalZipConstants.STD_DEC_HDR_SIZE;

public class StandardEncrypter implements Encrypter {

  private final ZipCryptoEngine zipCryptoEngine = new ZipCryptoEngine();
  private byte[] headerBytes;

  public StandardEncrypter(char[] password, long key, boolean useUtf8ForPassword) throws ZipException {
    init(password, key, useUtf8ForPassword);
  }

  private void init(char[] password, long key, boolean useUtf8ForPassword) throws ZipException {
    if (password == null || password.length <= 0) {
      throw new ZipException("input password is null or empty, cannot initialize standard encrypter");
    }
    zipCryptoEngine.initKeys(password, useUtf8ForPassword);
    headerBytes = generateRandomBytes();
    // Initialize again since the generated bytes were encrypted.
    zipCryptoEngine.initKeys(password, useUtf8ForPassword);

    headerBytes[STD_DEC_HDR_SIZE - 1] = (byte) ((key >>> 24));
    headerBytes[STD_DEC_HDR_SIZE - 2] = (byte) ((key >>> 16));

    encryptData(headerBytes);
  }

  public int encryptData(byte[] buff) throws ZipException {
    if (buff == null) {
      throw new NullPointerException();
    }
    return encryptData(buff, 0, buff.length);
  }

  public int encryptData(byte[] buff, int start, int len) throws ZipException {
    if (len < 0) {
      throw new ZipException("invalid length specified to decrpyt data");
    }

    for (int i = start; i < start + len; i++) {
      buff[i] = encryptByte(buff[i]);
    }
    return len;
  }

  protected byte encryptByte(byte val) {
    byte temp_val = (byte) (val ^ zipCryptoEngine.decryptByte() & 0xff);
    zipCryptoEngine.updateKeys(val);
    return temp_val;
  }

  protected byte[] generateRandomBytes() {
    byte[] buff = new byte[STD_DEC_HDR_SIZE];
    SecureRandom random = new SecureRandom();
    for (int i = 0; i < buff.length; i++) {
      buff[i] = encryptByte((byte) random.nextInt(256));
    }
    return buff;
  }

  public byte[] getHeaderBytes() {
    return headerBytes;
  }

}
