;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC

(ns common-tests.logic.comp-creation-test
  (:require
   [app.common.data :as d]
   [app.common.files.changes-builder :as pcb]
   [app.common.files.shapes-helpers :as cfsh]
   [app.common.geom.point :as gpt]
   [app.common.logic.libraries :as cll]
   [app.common.logic.shapes :as cls]
   [app.common.test-helpers.components :as thc]
   [app.common.test-helpers.compositions :as tho]
   [app.common.test-helpers.files :as thf]
   [app.common.test-helpers.ids-map :as thi]
   [app.common.test-helpers.shapes :as ths]
   [app.common.types.component :as ctk]
   [app.common.types.components-list :as ctkl]
   [app.common.types.shape-tree :as ctst]
   [clojure.test :as t]))

(t/use-fixtures :each thi/test-fixture)

(t/deftest test-add-component-from-single-frame
  (let [;; ==== Setup
        file   (-> (thf/sample-file :file1)
                   (ths/add-sample-shape :frame1 :type :frame))

        page   (thf/current-page file)
        frame1 (ths/get-shape file :frame1)

        ;; ==== Action
        [_ component-id changes]
        (cll/generate-add-component (pcb/empty-changes)
                                    [frame1]
                                    (:objects page)
                                    (:id page)
                                    (:id file)
                                    true
                                    nil
                                    nil)

        file' (thf/apply-changes file changes)

        ;; ==== Get
        component (thc/get-component-by-id file' component-id)
        root      (ths/get-shape-by-id file' (:main-instance-id component))
        frame1'   (ths/get-shape file' :frame1)]

    ;; ==== Check
    (t/is (some? component))
    (t/is (some? root))
    (t/is (some? frame1'))
    (t/is (= (:id root) (:id frame1')))
    (t/is (ctk/main-instance? root))
    (t/is (ctk/main-instance-of? (:id root) (:id page) component))))

(t/deftest test-add-component-from-single-shape
  (let [;; ==== Setup
        file   (-> (thf/sample-file :file1)
                   (ths/add-sample-shape :shape1 :type :rect))

        page   (thf/current-page file)
        shape1 (ths/get-shape file :shape1)

        ;; ==== Action
        [_ component-id changes]
        (cll/generate-add-component (pcb/empty-changes)
                                    [shape1]
                                    (:objects page)
                                    (:id page)
                                    (:id file)
                                    true
                                    nil
                                    cfsh/prepare-create-artboard-from-selection)

        file' (thf/apply-changes file changes)

        ;; ==== Get
        component (thc/get-component-by-id file' component-id)
        root      (ths/get-shape-by-id file' (:main-instance-id component))
        shape1'   (ths/get-shape file' :shape1)]

    ;; ==== Check
    (t/is (some? component))
    (t/is (some? root))
    (t/is (some? shape1'))
    (t/is (ctst/parent-of? root shape1'))
    (t/is (= (:type root) :frame))
    (t/is (ctk/main-instance? root))
    (t/is (ctk/main-instance-of? (:id root) (:id page) component))))

(t/deftest test-add-component-from-several-shapes
  (let [;; ==== Setup
        file   (-> (thf/sample-file :file1)
                   (ths/add-sample-shape :shape1 :type :rect)
                   (ths/add-sample-shape :shape2 :type :rect))

        page   (thf/current-page file)
        shape1 (ths/get-shape file :shape1)
        shape2 (ths/get-shape file :shape2)

        ;; ==== Action
        [_ component-id changes]
        (cll/generate-add-component (pcb/empty-changes)
                                    [shape1 shape2]
                                    (:objects page)
                                    (:id page)
                                    (:id file)
                                    true
                                    nil
                                    cfsh/prepare-create-artboard-from-selection)

        file' (thf/apply-changes file changes)

        ;; ==== Get
        component (thc/get-component-by-id file' component-id)
        root      (ths/get-shape-by-id file' (:main-instance-id component))
        shape1'   (ths/get-shape file' :shape1)
        shape2'   (ths/get-shape file' :shape2)]

    ;; ==== Check
    (t/is (some? component))
    (t/is (some? root))
    (t/is (some? shape1'))
    (t/is (some? shape2'))
    (t/is (ctst/parent-of? root shape1'))
    (t/is (ctst/parent-of? root shape2'))
    (t/is (= (:type root) :frame))
    (t/is (ctk/main-instance? root))
    (t/is (ctk/main-instance-of? (:id root) (:id page) component))))

(t/deftest test-add-component-from-several-frames
  (let [;; ==== Setup
        file   (-> (thf/sample-file :file1)
                   (ths/add-sample-shape :frame1 :type :frame)
                   (ths/add-sample-shape :frame2 :type :frame))

        page   (thf/current-page file)
        frame1 (ths/get-shape file :frame1)
        frame2 (ths/get-shape file :frame2)

        ;; ==== Action
        [_ component-id changes]
        (cll/generate-add-component (pcb/empty-changes)
                                    [frame1 frame2]
                                    (:objects page)
                                    (:id page)
                                    (:id file)
                                    true
                                    nil
                                    cfsh/prepare-create-artboard-from-selection)

        file' (thf/apply-changes file changes)

        ;; ==== Get
        component (thc/get-component-by-id file' component-id)
        root      (ths/get-shape-by-id file' (:main-instance-id component))
        frame1'   (ths/get-shape file' :frame1)
        frame2'   (ths/get-shape file' :frame2)]

    ;; ==== Check
    (t/is (some? component))
    (t/is (some? root))
    (t/is (some? frame1'))
    (t/is (some? frame2'))
    (t/is (ctst/parent-of? root frame1'))
    (t/is (ctst/parent-of? root frame2'))
    (t/is (= (:type root) :frame))
    (t/is (ctk/main-instance? root))
    (t/is (ctk/main-instance-of? (:id root) (:id page) component))))

(t/deftest test-add-component-from-frame-with-children
  (let [;; ==== Setup
        file   (-> (thf/sample-file :file1)
                   (ths/add-sample-shape :frame1 :type :frame)
                   (ths/add-sample-shape :shape1 :type :rect :parent-label :frame1)
                   (ths/add-sample-shape :shape2 :type :rect :parent-label :frame1))

        page   (thf/current-page file)
        frame1 (ths/get-shape file :frame1)

        ;; ==== Action
        [_ component-id changes]
        (cll/generate-add-component (pcb/empty-changes)
                                    [frame1]
                                    (:objects page)
                                    (:id page)
                                    (:id file)
                                    true
                                    nil
                                    nil)

        file' (thf/apply-changes file changes)

        ;; ==== Get
        component (thc/get-component-by-id file' component-id)
        root      (ths/get-shape-by-id file' (:main-instance-id component))
        frame1'   (ths/get-shape file' :frame1)
        shape1'   (ths/get-shape file' :shape1)
        shape2'   (ths/get-shape file' :shape2)]

    ;; ==== Check
    (t/is (some? component))
    (t/is (some? root))
    (t/is (some? frame1'))
    (t/is (= (:id root) (:id frame1')))
    (t/is (ctst/parent-of? frame1' shape1'))
    (t/is (ctst/parent-of? frame1' shape2'))
    (t/is (ctk/main-instance? root))
    (t/is (ctk/main-instance-of? (:id root) (:id page) component))))

(t/deftest test-add-component-from-copy
  (let [;; ==== Setup
        file   (-> (thf/sample-file :file1)
                   (tho/add-simple-component-with-copy :component1
                                                       :main1-root
                                                       :main1-child
                                                       :copy1-root))

        page       (thf/current-page file)
        copy1-root (ths/get-shape file :copy1-root)

        ;; ==== Action
        [_ component2-id changes]
        (cll/generate-add-component (pcb/empty-changes)
                                    [copy1-root]
                                    (:objects page)
                                    (:id page)
                                    (:id file)
                                    true
                                    nil
                                    cfsh/prepare-create-artboard-from-selection)

        file' (thf/apply-changes file changes)

        ;; ==== Get
        component2' (thc/get-component-by-id file' component2-id)
        root2'      (ths/get-shape-by-id file' (:main-instance-id component2'))
        copy1-root' (ths/get-shape file' :copy1-root)]

    ;; ==== Check
    (t/is (some? component2'))
    (t/is (some? root2'))
    (t/is (some? copy1-root'))
    (t/is (ctst/parent-of? root2' copy1-root'))
    (t/is (ctk/main-instance? root2'))
    (t/is (ctk/main-instance-of? (:id root2') (:id page) component2'))))

(t/deftest test-rename-component
  (let [;; ==== Setup
        file  (-> (thf/sample-file :file1)
                  (tho/add-simple-component :component1
                                            :main1-root
                                            :main1-child
                                            :name "Test component before"))

        component (thc/get-component file :component1)

        ;; ==== Action
        changes   (cll/generate-rename-component (pcb/empty-changes)
                                                 (:id component)
                                                 "Test component after"
                                                 (:data file)
                                                 true)

        file' (thf/apply-changes file changes)

        ;; ==== Get
        component' (thc/get-component file' :component1)]

    ;; ==== Check
    (t/is (= (:name component') "Test component after"))))

(t/deftest test-duplicate-component
  (let [;; ==== Setup
        file  (-> (thf/sample-file :file1)
                  (tho/add-simple-component :component1
                                            :main1-root
                                            :main1-child))

        component (thc/get-component file :component1)

        ;; ==== Action
        changes (cll/generate-duplicate-component (pcb/empty-changes)
                                                  file
                                                  (:id component)
                                                  true)

        file'   (thf/apply-changes file changes)

        ;; ==== Get
        components' (ctkl/components-seq (:data file'))
        component1' (d/seek #(= (:id %) (thi/id :component1)) components')
        component2' (d/seek #(not= (:id %) (thi/id :component1)) components')
        root1'      (ths/get-shape-by-id file' (:main-instance-id component1'))
        root2'      (ths/get-shape-by-id file' (:main-instance-id component2'))
        child1'     (ths/get-shape-by-id file' (first (:shapes root1')))
        child2'     (ths/get-shape-by-id file' (first (:shapes root2')))]

    ;; ==== Check
    (t/is (= 2 (count components')))
    (t/is (some? component1'))
    (t/is (some? component2'))
    (t/is (some? root1'))
    (t/is (some? root2'))
    (t/is (= (thi/id :main1-root) (:id root1')))
    (t/is (not= (thi/id :main1-root) (:id root2')))
    (t/is (some? child1'))
    (t/is (some? child2'))
    (t/is (= (thi/id :main1-child) (:id child1')))
    (t/is (not= (thi/id :main1-child) (:id child2')))))

(t/deftest test-delete-component
  (let [;; ==== Setup
        file (-> (thf/sample-file :file1)
                 (tho/add-simple-component-with-copy :component1
                                                     :main1-root
                                                     :main1-child
                                                     :copy1-root))

        page (thf/current-page file)
        root (ths/get-shape file :main1-root)

        ;; ==== Action
        [_ changes]
        (cls/generate-delete-shapes (pcb/empty-changes)
                                    file
                                    page
                                    (:objects page)
                                    #{(:id root)}
                                    {:components-v2 true})

        file' (thf/apply-changes file changes)

        ;; ==== Get
        component1'        (thc/get-component file' :component1 :include-deleted? true)
        copy1-root'        (ths/get-shape file' :copy1-root)

        main1-root'        (ths/get-shape file' :main1-root)
        main1-child'       (ths/get-shape file' :main1-child)

        saved-objects      (:objects component1')
        saved-main1-root'  (get saved-objects (thi/id :main1-root))
        saved-main1-child' (get saved-objects (thi/id :main1-child))]

    ;; ==== Check
    (t/is (true? (:deleted component1')))
    (t/is (some? copy1-root'))
    (t/is (nil? main1-root'))
    (t/is (nil? main1-child'))
    (t/is (some? saved-main1-root'))
    (t/is (some? saved-main1-child'))))

(t/deftest test-restore-component
  (let [;; ==== Setup
        file (-> (thf/sample-file :file1)
                 (tho/add-simple-component-with-copy :component1
                                                     :main1-root
                                                     :main1-child
                                                     :copy1-root))

        page (thf/current-page file)
        root (ths/get-shape file :main1-root)

        ;; ==== Action
        [_ changes]
        (cls/generate-delete-shapes (pcb/empty-changes)
                                    file
                                    page
                                    (:objects page)
                                    #{(:id root)}
                                    {:components-v2 true})

        file-deleted (thf/apply-changes file changes)
        page-deleted (thf/current-page file-deleted)

        changes (cll/generate-restore-component (pcb/empty-changes)
                                                (:data file-deleted)
                                                (thi/id :component1)
                                                (:id file-deleted)
                                                page-deleted
                                                (:objects page-deleted))

        file' (thf/apply-changes file changes)

        ;; ==== Get
        component1'        (thc/get-component file' :component1 :include-deleted? false)
        copy1-root'        (ths/get-shape file' :copy1-root)

        main1-root'        (ths/get-shape file' :main1-root)
        main1-child'       (ths/get-shape file' :main1-child)

        saved-objects'     (:objects component1')]

    ;; ==== Check
    (t/is (nil? (:deleted component1')))
    (t/is (some? copy1-root'))
    (t/is (some? main1-root'))
    (t/is (some? main1-child'))
    (t/is (ctk/main-instance? main1-root'))
    (t/is (ctk/main-instance-of? (:id main1-root') (:id page) component1'))
    (t/is (nil? saved-objects'))))

(t/deftest test-instantiate-component
  (let [;; ==== Setup
        file  (-> (thf/sample-file :file1)
                  (tho/add-simple-component :component1
                                            :main1-root
                                            :main1-child))

        page      (thf/current-page file)
        component (thc/get-component file :component1)

        ;; ==== Action
        [new-shape changes]
        (cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page))  ;; This may not be moved to generate
                                                (pcb/with-objects (:objects page))) ;; because in some cases the objects
                                            (:objects page)                         ;; not the same as those on the page
                                            (:id file)
                                            (:id component)
                                            (gpt/point 1000 1000)
                                            page
                                            {(:id file) file})

        file' (thf/apply-changes file changes)

        ;; ==== Get
        component'   (thc/get-component file' :component1)
        main1-root'  (ths/get-shape file' :main1-root)
        main1-child' (ths/get-shape file' :main1-child)
        copy1-root'  (ths/get-shape-by-id file' (:id new-shape))
        copy1-child' (ths/get-shape-by-id file' (first (:shapes copy1-root')))]

    ;; ==== Check
    (t/is (some? main1-root'))
    (t/is (some? main1-child'))
    (t/is (some? copy1-root'))
    (t/is (some? copy1-child'))
    (t/is (ctk/instance-root? copy1-root'))
    (t/is (ctk/instance-of? copy1-root' (:id file') (:id component')))
    (t/is (ctk/is-main-of? main1-root' copy1-root' true))
    (t/is (ctk/is-main-of? main1-child' copy1-child' true))
    (t/is (ctst/parent-of? copy1-root' copy1-child'))))

(t/deftest test-instantiate-component-from-lib
  (let [;; ==== Setup
        library   (-> (thf/sample-file :library1)
                      (tho/add-simple-component :component1
                                                :main1-root
                                                :main1-child))

        file      (thf/sample-file :file1)

        page      (thf/current-page file)
        component (thc/get-component library :component1)

        ;; ==== Action
        [new-shape changes]
        (cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page))
                                                (pcb/with-objects (:objects page)))
                                            (:objects page)
                                            (:id library)
                                            (:id component)
                                            (gpt/point 1000 1000)
                                            page
                                            {(:id file) file
                                             (:id library) library})

        file' (thf/apply-changes file changes)

        ;; ==== Get
        component'   (thc/get-component library :component1)
        main1-root'  (ths/get-shape library :main1-root)
        main1-child' (ths/get-shape library :main1-child)
        copy1-root'  (ths/get-shape-by-id file' (:id new-shape))
        copy1-child' (ths/get-shape-by-id file' (first (:shapes copy1-root')))]

    ;; ==== Check
    (t/is (some? main1-root'))
    (t/is (some? main1-child'))
    (t/is (some? copy1-root'))
    (t/is (some? copy1-child'))
    (t/is (ctk/instance-root? copy1-root'))
    (t/is (ctk/instance-of? copy1-root' (:id library) (:id component')))
    (t/is (ctk/is-main-of? main1-root' copy1-root' true))
    (t/is (ctk/is-main-of? main1-child' copy1-child' true))
    (t/is (ctst/parent-of? copy1-root' copy1-child'))))

(t/deftest test-instantiate-nested-component
  (let [;; ==== Setup
        file  (-> (thf/sample-file :file1)
                  (tho/add-nested-component :component1
                                            :main1-root
                                            :main1-child
                                            :component2
                                            :main2-root
                                            :main2-nested-head))

        page      (thf/current-page file)
        component (thc/get-component file :component1)

        ;; ==== Action
        [new-shape changes]
        (cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page))
                                                (pcb/with-objects (:objects page)))
                                            (:objects page)
                                            (:id file)
                                            (:id component)
                                            (gpt/point 1000 1000)
                                            page
                                            {(:id file) file})

        file' (thf/apply-changes file changes)

        ;; ==== Get
        component'   (thc/get-component file' :component1)
        main1-root'  (ths/get-shape file' :main1-root)
        main1-child' (ths/get-shape file' :main1-child)
        copy1-root'  (ths/get-shape-by-id file' (:id new-shape))
        copy1-child' (ths/get-shape-by-id file' (first (:shapes copy1-root')))]

    ;; ==== Check
    (t/is (some? main1-root'))
    (t/is (some? main1-child'))
    (t/is (some? copy1-root'))
    (t/is (some? copy1-child'))
    (t/is (ctk/instance-root? copy1-root'))
    (t/is (ctk/instance-of? copy1-root' (:id file') (:id component')))
    (t/is (ctk/is-main-of? main1-root' copy1-root' true))
    (t/is (ctk/is-main-of? main1-child' copy1-child' true))
    (t/is (ctst/parent-of? copy1-root' copy1-child'))))

(t/deftest test-instantiate-nested-component-from-lib
  (let [;; ==== Setup
        library   (-> (thf/sample-file :file1)
                      (tho/add-nested-component :component1
                                                :main1-root
                                                :main1-child
                                                :component2
                                                :main2-root
                                                :main2-nested-head))

        file      (thf/sample-file :file1)

        page      (thf/current-page file)
        component (thc/get-component library :component1)

        ;; ==== Action
        [new-shape changes]
        (cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page))
                                                (pcb/with-objects (:objects page)))
                                            (:objects page)
                                            (:id library)
                                            (:id component)
                                            (gpt/point 1000 1000)
                                            page
                                            {(:id file) file
                                             (:id library) library})

        file' (thf/apply-changes file changes)

        ;; ==== Get
        component'   (thc/get-component library :component1)
        main1-root'  (ths/get-shape library :main1-root)
        main1-child' (ths/get-shape library :main1-child)
        copy1-root'  (ths/get-shape-by-id file' (:id new-shape))
        copy1-child' (ths/get-shape-by-id file' (first (:shapes copy1-root')))]

    ;; ==== Check
    (t/is (some? main1-root'))
    (t/is (some? main1-child'))
    (t/is (some? copy1-root'))
    (t/is (some? copy1-child'))
    (t/is (ctk/instance-root? copy1-root'))
    (t/is (ctk/instance-of? copy1-root' (:id library) (:id component')))
    (t/is (ctk/is-main-of? main1-root' copy1-root' true))
    (t/is (ctk/is-main-of? main1-child' copy1-child' true))
    (t/is (ctst/parent-of? copy1-root' copy1-child'))))

(t/deftest test-detach-copy
  (let [;; ==== Setup
        file   (-> (thf/sample-file :file1)
                   (tho/add-simple-component-with-copy :component1
                                                       :main1-root
                                                       :main1-child
                                                       :copy1-root))

        page       (thf/current-page file)
        copy1-root (ths/get-shape file :copy1-root)

        ;; ==== Action
        changes (cll/generate-detach-component (pcb/empty-changes)
                                               (:id copy1-root)
                                               (:data file)
                                               (:id page)
                                               {(:id file) file})

        file' (thf/apply-changes file changes)

        ;; ==== Get
        copy1-root' (ths/get-shape file' :copy1-root)]

    ;; ==== Check
    (t/is (some? copy1-root'))
    (t/is (not (ctk/instance-head? copy1-root')))
    (t/is (not (ctk/in-component-copy? copy1-root')))))
