/**
 * 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>
  );
};