import { expect } from 'chai';
import { animate, createTimer, createTimeline, engine } from '../../dist/modules/index.js';
import { render } from '../../dist/modules/core/render.js';
// import { animate } from '../visual/assets/anime.4.0.0.beta-29.js';

const totalInstances = 10000;
const targets = [];

for (let i = 0; i < totalInstances; i++) {
  targets.push({ prop: Math.random() });
}

suite('Node tests', () => {

  test('Barebone test to see if an animation runs', resolve => {
    const obj = {test: 1}
    const startTime = Date.now();
    animate(obj, {
      test: 2,
      duration: 1000,
      onComplete: () => {
        const endTime = Date.now() - startTime;
        expect(endTime).to.be.closeTo(1000, 15);
        console.log('endTime:', endTime);
        expect(obj.test).to.equal(2);
        resolve();
      }
    });
  });

  function getOptimizationStatus(fn) {
    // @ts-ignore
    const status = %GetOptimizationStatus(fn);
    // console.log(status, status.toString(2).padStart(12, '0'));
    if (status & (1 << 0)) console.log(fn, ' is function');
    if (status & (1 << 1)) console.log(fn, ' is never optimized');
    if (status & (1 << 2)) console.log(fn, ' is always optimized');
    if (status & (1 << 3)) console.log(fn, ' is maybe deoptimized');
    if (status & (1 << 4)) console.log(fn, ' is optimized');
    if (status & (1 << 5)) console.log(fn, ' is optimized by TurboFan');
    if (status & (1 << 6)) console.log(fn, ' is interpreted');
    if (status & (1 << 7)) console.log(fn, ' is marked for optimization');
    if (status & (1 << 8)) console.log(fn, ' is marked for concurrent optimization');
    if (status & (1 << 9)) console.log(fn, ' is optimizing concurrently');
    if (status & (1 << 10)) console.log(fn, ' is executing');
    if (status & (1 << 11)) console.log(fn, ' topmost frame is turbo fanned');
    console.log(fn, ' optimization status is ', status);
    return status;
  }

  test('test if the animate() function can be optimized', () => {
    for (let i = 0; i < totalInstances; i++) {
      animate(targets[i], {
        prop: Math.random(),
        delay: Math.random() * 1.5,
        duration: Math.random() * 2,
        loopDelay: Math.random() * 3
      })
    }

    const animation = animate(targets[0], {
      prop: Math.random(),
      duration: Math.random() * 2,
      ease: 'linear',
    });

    const tween = animation._head;

    const animateIsOptimized = getOptimizationStatus(animate);
    // @ts-ignore
    const animationHasFastProperties = %HasFastProperties(animation);
    // @ts-ignore
    const tweenHasFastProperties = %HasFastProperties(tween);

    if (animationHasFastProperties) console.log('animation has fast properties');
    if (tweenHasFastProperties) console.log('tween has fast properties');

    expect(animateIsOptimized).to.equal(49);
    expect(animationHasFastProperties).to.equal(true);
    expect(tweenHasFastProperties).to.equal(true);
  });

  test('test if the createTimeline() function can be optimized', () => {

    for (let i = 0; i < totalInstances; i++) {
      createTimeline({
        defaults: {
          delay: Math.random() * 1.5,
          duration: Math.random() * 2,
          loopDelay: Math.random() * 3
        },
      })
      .add(targets[i], {
        prop: Math.random(),
        delay: Math.random() * 1.5,
        duration: Math.random() * 2,
        loopDelay: Math.random() * 3
      })
      .add(targets[i], {
        prop: Math.random(),
        delay: Math.random() * 1.5,
        duration: Math.random() * 2,
        loopDelay: Math.random() * 3
      })
    }

    const tl1 = createTimeline({
      defaults: {
        delay: Math.random() * 1.5,
        duration: Math.random() * 2,
        loopDelay: Math.random() * 3
      },
    })
    .add(targets[0], {
      prop: Math.random(),
      delay: Math.random() * 1.5,
      duration: Math.random() * 2,
      loopDelay: Math.random() * 3
    })
    .add(targets[1], {
      prop: Math.random(),
      delay: Math.random() * 1.5,
      duration: Math.random() * 2,
      loopDelay: Math.random() * 3
    })

    expect(getOptimizationStatus(createTimeline)).to.equal(49);

    const tl2 = createTimeline({
      defaults: {
        duration: Math.random() * 10,
        autoplay: false
      }
    });

    for (let i = 0; i < 3500; i++) {
      tl2.add(targets[i], {
        prop: Math.random()
      });
    }

    tl2.restart();
    tl2.play();
    tl2.pause();

    expect(getOptimizationStatus(createTimeline)).to.equal(49);

    const tlHasFastProperties = %HasFastProperties(tl2);

    if (tlHasFastProperties) console.log('timeline has fast properties');

    expect(tlHasFastProperties).to.equal(true);
  });

  test('test if the createTimer() function can be optimized', () => {
    for (let i = 0; i < totalInstances * 2; i++) {
      createTimer({
        duration: Math.random() * 2
      })
    }

    const timer = createTimer({
      duration: Math.random() * 2,
    });

    const animateIsOptimized = getOptimizationStatus(createTimer);
    const animationHasFastProperties = %HasFastProperties(timer);

    if (animationHasFastProperties) console.log('timer has fast properties');

    expect(animateIsOptimized).to.equal(49);
    expect(animationHasFastProperties).to.equal(true);
  });

  const generateInstances = () => {
    let anim, tl, timer;
    for (let i = 0; i < totalInstances; i++) {
      anim = animate(targets[i], {
        prop: Math.random(),
        delay: Math.random() * 1.5,
        duration: Math.random() * 2,
        loopDelay: Math.random() * 3
      });
      tl = createTimeline()
      .add(targets[i], {
        prop: Math.random(),
        delay: Math.random() * 1.5,
        duration: Math.random() * 2,
        loopDelay: Math.random() * 3
      })
      .add(targets[i], {
        prop: Math.random(),
        delay: Math.random() * 1.5,
        duration: Math.random() * 2,
        loopDelay: Math.random() * 3,
      })
      timer = createTimer({
        delay: Math.random() * 1.5,
        duration: Math.random() * 2,
        loopDelay: Math.random() * 3
      });
    }
    return { anim, tl, timer }
  }

  test('test if the render() function can be optimized', resolve => {

    // First test a normal run

    for (let i = 0; i < totalInstances; i++) {
      animate(targets[i], {
        prop: Math.random(),
        delay: Math.random() * 1.5,
        duration: Math.random() * 100,
        loopDelay: Math.random() * 3
      })
    }

    setTimeout(() => {
      expect(getOptimizationStatus(render)).to.equal(49);

      // Then mix animation types an manually render them

      const animations = [];

      // animations.push(globalClock._additiveAnimation);

      for (let i = 0; i < totalInstances / 3; i++) {
        animations.push(animate(targets[i], {prop: Math.random(), duration: Math.random() * 100, autoplay: false}));
        animations.push(createTimer({duration: Math.random() * 100, autoplay: false}));
      }

      // animations.push(globalClock._additiveAnimation);

      const start = Date.now();

      for (let i = 0; i < animations.length; i++) {
        const time = Date.now() - start;
        const tickMode = Math.random() < .5 ? 0 : -1;
        render(animations[i], time, 0, 0, tickMode);
      }

      setTimeout(() => {
        expect(getOptimizationStatus(render)).to.equal(49);
        resolve();
      }, 200)

    }, 200)

  });

  test('test if the engine.update() function can be optimized', resolve => {

    // First test a normal run

    for (let i = 0; i < totalInstances; i++) {
      animate(targets[i], {
        prop: Math.random(),
        delay: Math.random() * 1.5,
        duration: Math.random() * 100,
        loopDelay: Math.random() * 3,
        frameRate: Math.random() * 10,
      });
      createTimer({
        duration: Math.random() * 100,
        frameRate: Math.random() * 10,
      });
    }

    setTimeout(() => {
      expect(getOptimizationStatus(engine.update)).to.equal(49);
      resolve();
    }, 200)
  });

  test('test if engine has fast properties', () => {
    const { anim, tl, timer } = generateInstances();
    expect(%HasFastProperties(engine)).to.equal(true);
  });

  test('test if engine\'s defaults has fast properties', () => {
    const { anim, tl, timer } = generateInstances();
    expect(%HasFastProperties(engine.defaults)).to.equal(true);
  });

  test('test if timer has fast properties', () => {
    const { anim, tl, timer } = generateInstances();
    expect(%HasFastProperties(timer)).to.equal(true);
  });

  test('test if animation has fast properties', () => {
    const { anim, tl, timer } = generateInstances();
    expect(%HasFastProperties(anim)).to.equal(true);
  });

  test('test if tween has fast properties', () => {
    const { anim, tl, timer } = generateInstances();
    const tween = anim._head;
    expect(%HasFastProperties(tween)).to.equal(true);
  });

  test('test if tl has fast properties', () => {
    const { anim, tl, timer } = generateInstances();
    expect(%HasFastProperties(tl)).to.equal(true);
  });

});
