// File: CloneFactory.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CloneFactory {
  function createClone(address target) internal returns (address result) {
    bytes20 targetBytes = bytes20(target);
    assembly {
      let clone := mload(0x40)
      mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
      mstore(add(clone, 0x14), targetBytes)
      mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
      result := create(0, clone, 0x37)
    }
  }

  function isClone(address target, address query) internal view returns (bool result) {
    bytes20 targetBytes = bytes20(target);
    assembly {
      let clone := mload(0x40)
      mstore(clone, 0x363d3d373d3d3d363d7300000000000000000000000000000000000000000000)
      mstore(add(clone, 0xa), targetBytes)
      mstore(add(clone, 0x1e), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)

      let other := add(clone, 0x40)
      extcodecopy(query, other, 0, 0x2d)
      result := and(
        eq(mload(clone), mload(other)),
        eq(mload(add(clone, 0xd)), mload(add(other, 0xd)))
      )
    }
  }
}

// File: FrameFactory.sol



//SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

import "./CloneFactory.sol";

interface IFrameDataStore {
    function saveData(string memory _key, uint128 _pageNumber, bytes memory _b) external;
    function lock() external;
}

interface IFrameDataStoreFactory {
    function createFrameDataStore(string memory _name, string memory _version) external returns (address);
}

interface IFrame {
    struct Asset {
        string wrapperKey;
        string key;
        address wrapperStore;
        address store;
    }

    function init(
        Asset[] calldata _deps,
        address _sourceStore,
        address _pageWrapStore,
        uint256[4][] calldata _renderMap
    ) external;

    function setName(string calldata _name) external;
}

contract FrameFactory is CloneFactory {
    address public libraryAddress;

    event FrameCreated(address newAddress);

    constructor() {}

    function setLibraryAddress(address _libraryAddress) public  {
        require(libraryAddress == address(0), "FrameFactory: Library already set");
        libraryAddress = _libraryAddress;
    }

    function createFrameWithSource(
        IFrameDataStoreFactory _frameDataStoreFactory,
        IFrame.Asset[] memory _deps,
        bytes[] calldata _newData,
        address _pageWrapStore,
        uint256[4][] calldata _renderMap,
        string calldata _name
    ) public returns (address)  {
        // Create new frame contract and new datastore contract
        address clone = createClone(libraryAddress);
        address sourceStore = _frameDataStoreFactory.createFrameDataStore(_name, "1.0.0");

        // Saves data and sets internalStore to an dep with an empty store address
        uint256 newAssetAddedCursor = 0;
        for (uint256 dx = 0; dx < _deps.length; dx++) {
          if (_deps[dx].store == address(0)) {
            // Save data to new store
            IFrameDataStore(sourceStore).saveData(_deps[dx].key, 0, _newData[newAssetAddedCursor]);
            newAssetAddedCursor++;

            // Replace item
            _deps[dx].store = sourceStore;
          }
        }

        // Init frame
        IFrame(clone).init(
            _deps,
            sourceStore,
            _pageWrapStore,
            _renderMap
        );

        // Set frame name
        IFrame(clone).setName(_name);
        
        // Lock newly created data store
        IFrameDataStore(sourceStore).lock();

        emit FrameCreated(clone);
        return clone;
    }
}