/**

 * Copyright (c) 2024 Huawei Technologies Co., Ltd.

 *

 * This source code is licensed under the MIT license found in the

 * LICENSE-MIT file in the root directory of this source tree.

 */



import React, { useRef, useState } from 'react';



import { Animated, Pressable, Text, View } from 'react-native';



import { TestCase, TestSuite } from '@rnoh/testerino';

import { Button } from '../components';



export function AnimatedTest() {

  return (

    <TestSuite name="Animated">

      <TestCase itShould="update the button label after a delay">

        <AnimatedEndCallbackTest />

      </TestCase>

      <TestCase itShould="animate width">

        <AnimatedRectangle />

      </TestCase>

      <TestCase itShould="move red square horizontally relatively to the scroll offset">

        <AnimatedScrollViewTestCase />

      </TestCase>

      <TestCase itShould="fade in and out when clicked">

        <FadeInOut />

        <FadeInOut nativeDriver />

      </TestCase>

      <TestCase itShould="rotate grey square after red square with 0.5 second delay">

        <Delay />

      </TestCase>

      <TestCase itShould="rotate red square in a loop">

        <Loop />

      </TestCase>

      <TestCase itShould="rotate both squares in paralell">

        <Parallel />

      </TestCase>

      <TestCase itShould="rotate button on press">

        <AnimatedPressableView />

      </TestCase>

      <TestCase itShould="rotate squares with different stiffness/mass">

        <Spring />

      </TestCase>

      <TestCase itShould="move squares with different initial velocity and deceleration values">

        <Decay />

      </TestCase>

      <TestCase itShould="move square immediately after pressing button">

        <DiffClamp />

      </TestCase>

      <TestCase itShould="move grey square 2x further horizontally than red">

        <Multiply />

      </TestCase>

      <TestCase itShould="move grey twice but half the total distance of red">

        <Modulo />

      </TestCase>

      <TestCase skip itShould="move red square closer">

        <Perspective />

      </TestCase>

      <TestCase itShould="move square both vertically and horizontally">

        <ValueXY />

      </TestCase>

      <TestCase

        skip

        itShould="(broken everywhere) move both squares, with blue square following the red with a spring">

        <TrackingValue />

      </TestCase>

    </TestSuite>

  );

}



function AnimatedRectangle() {

  const animWidth = React.useRef(new Animated.Value(100)).current;



  const animation = React.useMemo(() => {

    const expand = Animated.timing(animWidth, {

      toValue: 300,

      duration: 1000,

      useNativeDriver: false,

    });

    const contract = Animated.timing(animWidth, {

      toValue: 100,

      duration: 1000,

      useNativeDriver: false,

    });

    return Animated.sequence([expand, contract]);

  }, []);



  return (

    <Animated.View

      style={{

        height: 100,

        width: animWidth,

        backgroundColor: 'red',

      }}

      onTouchEnd={() => {

        animation.reset();

        animation.start();

      }}

    />

  );

}



const AnimatedScrollViewTestCase = () => {

  const scrollY = new Animated.Value(0);

  const translation = scrollY.interpolate({

    inputRange: [0, 200],

    outputRange: [0, 200],

    extrapolate: 'clamp',

  });



  return (

    <View

      style={{

        width: '100%',

        height: 100,

        position: 'relative',

        overflow: 'hidden',

      }}>

      <Animated.ScrollView

        style={{ width: '100%', height: '100%' }}

        contentContainerStyle={{ alignItems: 'center', justifyContent: 'center' }}

        scrollEventThrottle={16}

        onScroll={Animated.event(

          [{ nativeEvent: { contentOffset: { y: scrollY } } }],

          {

            useNativeDriver: true,

          },

        )}>

        {new Array(3).fill(0).map((_, idx) => {

          return (

            <View

              key={idx}

              style={{

                width: '100%',

                height: 50,

                backgroundColor: 'gray',

                marginBottom: 50,

              }}

            />

          );

        })}

      </Animated.ScrollView>

      <Animated.View

        style={[

          {

            position: 'absolute',

            bottom: 0,

            transform: [{ translateX: translation }],

            width: 32,

            height: 32,

            backgroundColor: 'red',

          },

        ]}

      />

    </View>

  );

};



function FadeInOut({ nativeDriver = false }) {

  const fadeAnim = React.useRef(new Animated.Value(1)).current;



  const handleFadePress = () => {

    Animated.sequence([

      Animated.timing(fadeAnim, {

        toValue: 0,

        duration: 1000,

        useNativeDriver: nativeDriver,

      }),

      Animated.timing(fadeAnim, {

        toValue: 1,

        duration: 1000,

        useNativeDriver: nativeDriver,

      }),

    ]).start();

  };



  return (

    <Pressable onPress={handleFadePress}>

      <Animated.View

        style={{

          marginTop: 24,

          height: 100,

          width: 100,

          opacity: fadeAnim,

          backgroundColor: 'red',

        }}>

        <Text style={{ width: 100, height: 48 }}>

          Press me to fade{nativeDriver && ' using native driver'}

        </Text>

      </Animated.View>

    </Pressable>

  );

}



const Delay = () => {

  const square1Anim = useRef(new Animated.Value(0)).current;

  const square2Anim = useRef(new Animated.Value(0)).current;

  const animation = Animated.sequence([

    Animated.timing(square1Anim, {

      toValue: 1,

      duration: 500,

      useNativeDriver: true,

    }),

    Animated.delay(500),

    Animated.timing(square2Anim, {

      toValue: 1,

      duration: 500,

      useNativeDriver: true,

    }),

  ]);



  const handleAnimation = () => {

    animation.reset();

    animation.start();

  };



  return (

    <AnimatedRotatingSquaresView

      handleAnimation={handleAnimation}

      square1Anim={square1Anim}

      square2Anim={square2Anim}

    />

  );

};

const Loop = () => {

  const squareAnim = useRef(new Animated.Value(0)).current;

  const [isRunning, setIsRunning] = useState(false);

  const animation = Animated.loop(

    Animated.sequence([

      Animated.timing(squareAnim, {

        toValue: 1,

        duration: 1000,

        useNativeDriver: true,

      }),

      Animated.timing(squareAnim, {

        toValue: 0,

        duration: 1000,

        useNativeDriver: true,

      }),

    ]),

  );



  const handleAnimation = () => {

    if (isRunning) {

      animation.stop();

      setIsRunning(false);

    } else {

      animation.reset();

      animation.start();

      setIsRunning(true);

    }

  };



  return (

    <AnimatedRotatingSquaresView

      handleAnimation={handleAnimation}

      square1Anim={squareAnim}

      square2Anim={new Animated.Value(0)}

    />

  );

};



const Parallel = () => {

  const square1Anim = useRef(new Animated.Value(0)).current;

  const square2Anim = useRef(new Animated.Value(0)).current;



  const animation = Animated.parallel([

    Animated.timing(square1Anim, {

      toValue: 1,

      duration: 1000,

      useNativeDriver: true,

    }),

    Animated.timing(square2Anim, {

      toValue: 1,

      duration: 1000,

      useNativeDriver: true,

    }),

  ]);

  const handleAnimation = () => {

    animation.reset();

    animation.start();

  };



  return (

    <AnimatedRotatingSquaresView

      handleAnimation={handleAnimation}

      square1Anim={square1Anim}

      square2Anim={square2Anim}

    />

  );

};



const AnimatedRotatingSquaresView = (props: {

  square1Anim: Animated.Value;

  square2Anim: Animated.Value;

  handleAnimation: () => void;

}) => {

  return (

    <Pressable

      style={{ height: 100, width: '100%' }}

      onPress={props.handleAnimation}>

      <View style={{ flexDirection: 'row' }}>

        <Animated.View

          style={{

            height: 50,

            width: 50,

            margin: 10,

            backgroundColor: 'red',

            transform: [

              {

                rotateZ: props.square1Anim.interpolate({

                  inputRange: [0, 1],

                  outputRange: ['0deg', '360deg'],

                }),

              },

            ],

          }}

        />

        <Animated.View

          style={{

            height: 50,

            width: 50,

            margin: 10,

            backgroundColor: 'grey',

            transform: [

              {

                rotateZ: props.square2Anim.interpolate({

                  inputRange: [0, 1],

                  outputRange: ['0deg', '360deg'],

                }),

              },

            ],

          }}

        />

      </View>

      <Text style={{ height: 20 }}>Press me to start animation</Text>

    </Pressable>

  );

};



const AnimatedPressableView = () => {

  const pressableAnim = useRef(new Animated.Value(0)).current;

  const AnimatedPressable = Animated.createAnimatedComponent(Pressable);

  const animation = Animated.timing(pressableAnim, {

    toValue: 1,

    duration: 500,

    useNativeDriver: true,

  });

  const onPress = () => {

    animation.reset();

    animation.start();

  };

  return (

    <View style={{ height: 80, width: '100%' }}>

      <AnimatedPressable

        style={{

          width: 100,

          margin: 20,

          backgroundColor: 'red',

          transform: [

            {

              rotateZ: pressableAnim.interpolate({

                inputRange: [0, 1],

                outputRange: ['0deg', '360deg'],

              }),

            },

          ],

        }}

        onPress={onPress}>

        <Text style={{ height: 40 }}>Press me to start animation</Text>

      </AnimatedPressable>

    </View>

  );

};



const Spring = () => {

  const square1Anim = useRef(new Animated.Value(0)).current;

  const square2Anim = useRef(new Animated.Value(0)).current;



  const animation = Animated.parallel([

    Animated.spring(square1Anim, {

      toValue: 1,

      stiffness: 1,

      useNativeDriver: true,

    }),

    Animated.spring(square2Anim, {

      toValue: 1,

      mass: 20,

      useNativeDriver: true,

    }),

  ]);

  const handleAnimation = () => {

    animation.reset();

    animation.start();

  };



  return (

    <Pressable style={{ height: 120, width: '100%' }} onPress={handleAnimation}>

      <Animated.View

        style={{

          height: 50,

          width: 50,

          backgroundColor: 'red',

          transform: [

            {

              rotateZ: square1Anim.interpolate({

                inputRange: [0, 1],

                outputRange: ['0deg', '360deg'],

              }),

            },

          ],

        }}

      />

      <Animated.View

        style={{

          height: 50,

          width: 50,

          backgroundColor: 'grey',

          transform: [

            {

              rotateZ: square2Anim.interpolate({

                inputRange: [0, 1],

                outputRange: ['0deg', '360deg'],

              }),

            },

          ],

        }}

      />

      <Text style={{ height: 20 }}>Press me to start animation</Text>

    </Pressable>

  );

};



const Decay = () => {

  const square1Anim = useRef(new Animated.Value(0)).current;

  const square2Anim = useRef(new Animated.Value(0)).current;



  const animation = Animated.parallel([

    Animated.decay(square1Anim, {

      velocity: 0.5,

      useNativeDriver: true,

    }),

    Animated.decay(square2Anim, {

      velocity: 0.25,

      // a deceleration value empirically determined to be _nice_

      deceleration: 0.99875,

      useNativeDriver: true,

    }),

  ]);

  const handleAnimation = () => {

    animation.reset();

    animation.start();

  };



  return (

    <Pressable style={{ height: 120, width: '100%' }} onPress={handleAnimation}>

      <Animated.View

        style={{

          height: 50,

          width: 50,

          backgroundColor: 'red',

          transform: [

            {

              translateX: square1Anim,

            },

          ],

        }}

      />

      <Animated.View

        style={{

          height: 50,

          width: 50,

          backgroundColor: 'grey',

          transform: [

            {

              translateX: square2Anim,

            },

          ],

        }}

      />

      <Text style={{ height: 20 }}>Press me to start animation</Text>

    </Pressable>

  );

};



const Multiply = () => {

  const square1Anim = useRef(new Animated.Value(0)).current;

  const multValue = new Animated.Value(2);

  const squareMultAnim = Animated.multiply(square1Anim, multValue);



  const animation = Animated.timing(square1Anim, {

    toValue: 100,

    duration: 1000,

    useNativeDriver: true,

  });

  const handleAnimation = () => {

    animation.reset();

    animation.start();

  };



  return (

    <Pressable style={{ height: 120, width: '100%' }} onPress={handleAnimation}>

      <Animated.View

        style={{

          height: 50,

          width: 50,

          backgroundColor: 'red',

          transform: [

            {

              translateX: square1Anim,

            },

          ],

        }}

      />

      <Animated.View

        style={{

          height: 50,

          width: 50,

          backgroundColor: 'grey',

          transform: [

            {

              translateX: squareMultAnim,

            },

          ],

        }}

      />

      <Text style={{ height: 20 }}>Press me to start animation</Text>

    </Pressable>

  );

};



const DiffClamp = () => {

  const square1Anim = useRef(new Animated.Value(0)).current;

  const clamped = Animated.diffClamp(square1Anim, 0, 50);

  const [direction, setDirection] = useState('left');



  const animation = React.useRef<Animated.CompositeAnimation>();



  const handleAnimation = () => {

    animation.current?.stop();

    animation.current = Animated.timing(square1Anim, {

      toValue: direction === 'left' ? 400 : 0,

      duration: 4000,

      useNativeDriver: true,

    });

    setDirection(direction === 'left' ? 'right' : 'left');

    animation.current.start();

  };



  return (

    <Pressable style={{ width: '100%' }} onPress={handleAnimation}>

      <Animated.View

        style={{

          height: 50,

          width: 50,

          backgroundColor: 'red',

          transform: [

            {

              translateX: clamped,

            },

          ],

        }}

      />

      <Text style={{ height: 20 }}>Press me to start animation</Text>

    </Pressable>

  );

};



const ValueXY = () => {

  const square1Anim = useRef(new Animated.ValueXY({ x: 50, y: 50 })).current;



  const animation = Animated.sequence([

    Animated.timing(square1Anim, {

      toValue: { x: 0, y: 0 },

      duration: 1000,

      useNativeDriver: true,

    }),

    Animated.timing(square1Anim, {

      toValue: { x: 0, y: 50 },

      duration: 1000,

      useNativeDriver: true,

    }),

    Animated.timing(square1Anim, {

      toValue: { x: 50, y: 0 },

      duration: 1000,

      useNativeDriver: true,

    }),

    Animated.timing(square1Anim, {

      toValue: { x: 50, y: 50 },

      duration: 1000,

      useNativeDriver: true,

    }),

  ]);

  const handleAnimation = () => {

    animation.reset();

    animation.start();

  };



  return (

    <Pressable style={{ height: 120, width: '100%' }} onPress={handleAnimation}>

      <View style={{ height: 100 }}>

        <Animated.View

          style={{

            height: 40,

            width: 40,

            backgroundColor: 'red',

            transform: [

              {

                translateX: square1Anim.x,

              },

              {

                translateY: square1Anim.y,

              },

            ],

          }}

        />

      </View>

      <Text style={{ height: 20 }}>Press me to start animation</Text>

    </Pressable>

  );

};



const AnimatedEndCallbackTest = () => {

  const [count, setCount] = React.useState(0);

  const increase = () => {

    Animated.timing(new Animated.Value(20), {

      toValue: 0,

      useNativeDriver: true,

      duration: 1000,

    }).start(() => {

      setCount(c => c + 1);

    });

  };



  return <Button onPress={increase} label={JSON.stringify(count)} />;

};



const TrackingValue = () => {

  const square1Anim = useRef(new Animated.Value(50)).current;

  const square2Anim = useRef(new Animated.Value(50)).current;



  const animation = Animated.sequence([

    Animated.timing(square1Anim, {

      toValue: 0,

      duration: 1000,

      useNativeDriver: true,

    }),

    Animated.timing(square1Anim, {

      toValue: 50,

      duration: 1000,

      useNativeDriver: true,

    }),

    Animated.timing(square1Anim, {

      toValue: 0,

      duration: 1000,

      useNativeDriver: true,

    }),

    Animated.timing(square1Anim, {

      toValue: 50,

      duration: 1000,

      useNativeDriver: true,

    }),

  ]);



  const handleAnimation = () => {

    animation.reset();

    animation.start();



    const tracking = Animated.spring(square2Anim, {

      toValue: square1Anim,

      useNativeDriver: true,

      mass: 10,

    });

    tracking.start();

  };



  return (

    <Pressable style={{ height: 120, width: '100%' }} onPress={handleAnimation}>

      <View style={{ height: 100 }}>

        <Animated.View

          style={{

            height: 40,

            width: 40,

            backgroundColor: 'red',

            transform: [{ translateX: square1Anim }],

          }}

        />

        <Animated.View

          style={{

            height: 40,

            width: 40,

            backgroundColor: 'blue',

            transform: [{ translateX: square2Anim }],

          }}

        />

      </View>

      <Text style={{ height: 20 }}>Press me to start animation</Text>

    </Pressable>

  );

};



const Modulo = () => {

  const square1Anim = useRef(new Animated.Value(0)).current;

  const squareModuloAnim = Animated.modulo(square1Anim, 100);



  const animation = Animated.timing(square1Anim, {

    toValue: 199,

    duration: 1000,

    useNativeDriver: true,

  });

  const handleAnimation = () => {

    animation.reset();

    animation.start();

  };



  return (

    <Pressable style={{ height: 120, width: '100%' }} onPress={handleAnimation}>

      <Animated.View

        style={{

          height: 50,

          width: 50,

          backgroundColor: 'red',

          transform: [

            {

              translateX: square1Anim,

            },

          ],

        }}

      />

      <Animated.View

        style={{

          height: 50,

          width: 50,

          backgroundColor: 'grey',

          transform: [

            {

              translateX: squareModuloAnim,

            },

          ],

        }}

      />

      <Text style={{ height: 20 }}>Press me to start animation</Text>

    </Pressable>

  );

};



const Perspective = () => {

  const square1Anim = useRef(new Animated.Value(500)).current;



  const animation = Animated.timing(square1Anim, {

    toValue: 50,

    duration: 1000,

    useNativeDriver: true,

  });

  const handleAnimation = () => {

    animation.reset();

    animation.start();

  };



  return (

    <Pressable style={{ height: 120, width: '100%' }} onPress={handleAnimation}>

      <View style={{ height: 100, justifyContent: 'center' }}>

        <Animated.View

          style={{

            height: 50,

            width: 50,

            backgroundColor: 'red',

            transform: [

              {

                rotateY: '60deg',

              },

              {

                perspective: square1Anim,

              },

            ],

          }}

        />

      </View>

      <Text style={{ height: 20 }}>Press me to start animation</Text>

    </Pressable>

  );

};