use subtle::ConditionallySelectable;

use curve25519_dalek::edwards::EdwardsPoint;

use group::ff::{Field, PrimeField};
use dalek_ff_group::FieldElement;

use monero_io::decompress_point;

use crate::keccak256;

/// Monero's `hash_to_ec` function.
pub fn hash_to_point(bytes: [u8; 32]) -> EdwardsPoint {
  #[allow(non_snake_case)]
  let A = FieldElement::from(486662u64);

  let v = FieldElement::from_square(keccak256(&bytes)).double();
  let w = v + FieldElement::ONE;
  let x = w.square() + (-A.square() * v);

  // This isn't the complete X, yet its initial value
  // We don't calculate the full X, and instead solely calculate Y, letting dalek reconstruct X
  // While inefficient, it solves API boundaries and reduces the amount of work done here
  #[allow(non_snake_case)]
  let X = {
    let u = w;
    let v = x;
    let v3 = v * v * v;
    let uv3 = u * v3;
    let v7 = v3 * v3 * v;
    let uv7 = u * v7;
    uv3 * uv7.pow((-FieldElement::from(5u8)) * FieldElement::from(8u8).invert().unwrap())
  };
  let x = X.square() * x;

  let y = w - x;
  let non_zero_0 = !y.is_zero();
  let y_if_non_zero_0 = w + x;
  let sign = non_zero_0 & (!y_if_non_zero_0.is_zero());

  let mut z = -A;
  z *= FieldElement::conditional_select(&v, &FieldElement::from(1u8), sign);
  #[allow(non_snake_case)]
  let Z = z + w;
  #[allow(non_snake_case)]
  let mut Y = z - w;

  Y *= Z.invert().unwrap();
  let mut bytes = Y.to_repr();
  bytes[31] |= sign.unwrap_u8() << 7;

  decompress_point(bytes).unwrap().mul_by_cofactor()
}
