/**

 * 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, { useEffect, useRef, useState } from 'react';

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

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

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



export function AnimatedValueTest() {

  return (

    <>

      <TestSuite name="Animated.Value">

        <TestCase

          itShould="move square 200px to the right and stop animation on pressing setValue"

          initialState={0}

          arrange={({ setState }) => (

            <SetValueView singular setState={setState} />

          )}

          assert={({ expect, state }) => {

            expect(state).to.be.eq(200);

          }}

        />

        <TestCase itShould="add and remove listeners on click">

          <ListenerView singular={true} />

        </TestCase>

        <TestCase itShould="move square 200px to the right on pressing setOffset">

          <SetOffsetView singular={true} />

        </TestCase>

      </TestSuite>

      <TestSuite name="Animated.ValueXY">

        <TestCase

          itShould="move square 100px to the right and stop animation on pressing setValue"

          initialState={0}

          arrange={({ setState }) => <SetValueView setState={setState} />}

          assert={({ expect, state }) => {

            expect(state).to.be.eq(100);

          }}

        />

        <TestCase itShould="add and remove listeners on click">

          <ListenerView singular={false} />

        </TestCase>

        <TestCase itShould="move square 100px to the right on pressing setOffset">

          <SetOffsetView singular={false} />

        </TestCase>

        <TestCase<Object>

          itShould="get layout of animated value on press"

          initialState={{}}

          arrange={({ setState }) => {

            const animValue = new Animated.ValueXY({ x: 1, y: 1 });

            return (

              <Effect

                onMount={() => {

                  setState(animValue.getLayout());

                }}

              />

            );

          }}

          assert={({ state, expect }) => {

            expect(JSON.stringify(state)).to.be.eq(

              JSON.stringify({ left: 1, top: 1 }),

            );

          }}

        />

        <TestCase itShould="move square to the right after extract offset">

          <ExtractOffsetView />

        </TestCase>

        <TestCase itShould="move square to the left after flatten offset">

          <FlattenOffsetView />

        </TestCase>

      </TestSuite>

    </>

  );

}

const ExtractOffsetView = () => {

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

  const animation = Animated.loop(

    Animated.sequence([

      Animated.timing(value, {

        toValue: 100,

        duration: 1000,

        useNativeDriver: true,

      }),

      Animated.timing(value, {

        toValue: 0,

        duration: 1000,

        useNativeDriver: true,

      }),

    ]),

  );

  return (

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

      <Animated.View

        style={{

          height: 20,

          width: 20,

          margin: 10,

          backgroundColor: 'red',

          transform: [

            {

              translateX: value,

            },

          ],

        }}

      />

      <View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>

        <Button

          label="start"

          onPress={() => {

            animation.reset();

            animation.start();

          }}

        />

        <Button label="stop" onPress={() => animation.stop()} />

        <Button label="extract offset" onPress={() => value.extractOffset()} />

      </View>

    </View>

  );

};

const FlattenOffsetView = () => {

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

  const animation = Animated.loop(

    Animated.sequence([

      Animated.timing(value, {

        toValue: 100,

        duration: 1000,

        useNativeDriver: true,

      }),

      Animated.timing(value, {

        toValue: 0,

        duration: 1000,

        useNativeDriver: true,

      }),

    ]),

  );

  value.setOffset(100);

  return (

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

      <Animated.View

        style={{

          height: 20,

          width: 20,

          margin: 10,

          backgroundColor: 'red',

          transform: [

            {

              translateX: value,

            },

          ],

        }}

      />

      <View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>

        <Button

          label="start"

          onPress={() => {

            animation.reset();

            animation.start();

          }}

        />

        <Button label="stop" onPress={() => animation.stop()} />

        <Button

          label="flatten offset"

          onPress={() => {

            value.flattenOffset();

          }}

        />

      </View>

    </View>

  );

};

const SetOffsetView = (props: { singular: boolean }) => {

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

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

  if (props.singular) {

    return (

      <MovingSquare

        animValue={animValue}

        labels={['set offset']}

        onPresses={[() => animValue.setOffset(200)]}

      />

    );

  } else {

    return (

      <MovingSquareXY

        animValueXY={animValueXY}

        labels={['set offset']}

        onPresses={[() => animValueXY.setOffset({ x: 100, y: 0 })]}

      />

    );

  }

};



const ListenerView = (props: { singular: boolean }) => {

  const [text, setText] = useState('');

  const [listeners, setListeners] = useState<string[]>([]);

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

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

  const listener = () => { };

  const addListener = () => {

    listeners.push(animValue.addListener(listener));

    setListeners(listeners);

    checkListener();

  };

  const removeListener = () => {

    const lastListener = listeners.pop();

    if (lastListener) {

      animValue.removeListener(lastListener);

    }

    checkListener();

  };

  const removeAll = () => {

    animValue.removeAllListeners();

    checkListener();

  };

  const checkListener = () => {

    setText(

      animValue.hasListeners()

        ? `listener: ${listeners}`

        : 'no listener is attached',

    );

  };

  return (

    <>

      <Text>{text}</Text>

      {props.singular ? (

        <MovingSquare

          animValue={animValue}

          labels={['add', 'remove', 'removeAll']}

          onPresses={[addListener, removeListener, removeAll]}

        />

      ) : (

        <MovingSquareXY

          animValueXY={animValueXY}

          labels={['add', 'remove', 'removeAll']}

          onPresses={[addListener, removeListener, removeAll]}

        />

      )}

    </>

  );

};



const SetValueView = (props: {

  singular?: boolean;

  setState: React.Dispatch<React.SetStateAction<number>>;

}) => {

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

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



  useEffect(() => {

    if (props.singular) {

      animValue.addListener(({ value }) => {

        if (value === 200) {

          props.setState(value);

        }

      });

    } else {

      animValueXY.addListener(({ x }) => {

        if (x === 100) {

          props.setState(x);

        }

      });

    }

  }, []);



  if (props.singular) {

    return (

      <MovingSquare

        animValue={animValue}

        labels={['set value']}

        onPresses={[() => animValue.setValue(200)]}

      />

    );

  } else {

    return (

      <MovingSquareXY

        animValueXY={animValueXY}

        labels={['set valueXY']}

        onPresses={[() => animValueXY.setValue({ x: 100, y: 0 })]}

      />

    );

  }

};



const MovingSquare = (props: {

  animValue: Animated.Value;

  labels: string[];

  onPresses: (() => void)[];

}) => {

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

  const animation = Animated.loop(

    Animated.sequence([

      Animated.timing(props.animValue, {

        toValue: 100,

        duration: 500,

        useNativeDriver: true,

      }),

      Animated.timing(props.animValue, {

        toValue: 0,

        duration: 500,

        useNativeDriver: true,

      }),

    ]),

  );

  const animate = () => {

    if (isRunning) {

      animation.stop();

      animation.reset();

      props.animValue.setOffset(0);

      setIsRunning(false);

    } else {

      setIsRunning(true);

      animation.start();

    }

  };

  const buttons = props.labels.map((value, index) => (

    <Button label={value} onPress={props.onPresses[index]} key={index} />

  ));

  return (

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

      <Animated.View

        style={{

          height: 20,

          width: 20,

          margin: 10,

          backgroundColor: 'red',

          transform: [

            {

              translateX: props.animValue,

            },

          ],

        }}

      />

      <View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>

        <Button label="animate" onPress={animate} />

        {buttons}

      </View>

    </View>

  );

};



const MovingSquareXY = (props: {

  animValueXY: Animated.ValueXY;

  labels: string[];

  onPresses: (() => void)[];

}) => {

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

  const animation = Animated.loop(

    Animated.sequence([

      Animated.timing(props.animValueXY, {

        toValue: { x: 25, y: 25 },

        duration: 500,

        useNativeDriver: true,

      }),

      Animated.timing(props.animValueXY, {

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

        duration: 500,

        useNativeDriver: true,

      }),

    ]),

  );

  const animate = () => {

    if (isRunning) {

      animation.stop();

      props.animValueXY.setOffset({ x: 0, y: 0 });

      setIsRunning(false);

    } else {

      setIsRunning(true);

      animation.start();

    }

  };

  const buttons = props.labels.map((value, index) => (

    <Button label={value} onPress={props.onPresses[index]} key={index} />

  ));

  return (

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

      <Animated.View

        style={{

          height: 20,

          width: 20,

          margin: 10,

          backgroundColor: 'red',

          transform: [

            {

              translateX: props.animValueXY.x,

            },

            { translateY: props.animValueXY.y },

          ],

        }}

      />

      <View style={{ flexDirection: 'row', flexWrap: 'wrap', marginTop: 20 }}>

        <Button label="animate" onPress={animate} />

        {buttons}

      </View>

    </View>

  );

};