1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { Log } from '../utils/Log';
17import { loggerMiddle } from './middlewares/loggerMiddle';
18import type { CameraInitState } from './reducers/CameraInitReducer';
19import { cameraInitReducer } from './reducers/CameraInitReducer';
20import type { ContextState } from './reducers/ContextReducer';
21import { contextReducer } from './reducers/ContextReducer';
22import type { CameraState } from './reducers/CameraReducer';
23import { cameraReducer } from './reducers/CameraReducer';
24import type { PreviewState } from './reducers/PreviewReducer';
25import { previewReducer } from './reducers/PreviewReducer';
26import type { CaptureState } from './reducers/CaptureReducer';
27import { captureReducer } from './reducers/CaptureReducer';
28import type { ModeChangeState } from './reducers/ModeChangeReducer';
29import { modeChangeReducer } from './reducers/ModeChangeReducer';
30import type { ModeState } from './reducers/ModeReducer';
31import { modeReducer } from './reducers/ModeReducer';
32import type { SettingState } from './reducers/SettingReducer';
33import { settingReducer } from './reducers/SettingReducer';
34import type { RecordState } from './reducers/RecordReducer';
35import { recordReducer } from './reducers/RecordReducer';
36import type { ZoomState } from './reducers/ZoomReducer';
37import { zoomReducer } from './reducers/ZoomReducer';
38import type { ActionData } from './actions/Action';
39import { applyMiddleware } from './core';
40import type { Enhancer, Reducer } from './core';
41import { eventBusMiddle } from './middlewares/EventBusMiddle';
42import { combineReducers } from './core';
43
44const TAG = '[store]:';
45const INIT_TAG = 'StoreInit';
46
47export type OhCombinedState = {
48  cameraInitReducer: CameraInitState;
49  contextReducer: ContextState;
50  cameraReducer: CameraState;
51  previewReducer: PreviewState;
52  captureReducer: CaptureState;
53  recordReducer: RecordState;
54  modeChangeReducer: ModeChangeState;
55  modeReducer: ModeState
56  settingReducer: SettingState
57  zoomReducer: ZoomState
58};
59
60function getReducers(): Reducer {
61  return combineReducers([
62    cameraInitReducer,
63    contextReducer,
64    cameraReducer,
65    previewReducer,
66    captureReducer,
67    recordReducer,
68    modeChangeReducer,
69    modeReducer,
70    settingReducer,
71    zoomReducer,
72  ]);
73}
74
75export interface Unsubscribe {
76  destroy(): void
77}
78
79export interface Dispatch<A = ActionData> {
80  <T extends A>(action: T): T;
81}
82
83function getEnhancer(): Enhancer {
84  return applyMiddleware(loggerMiddle, eventBusMiddle);
85}
86
87export function getStore(): Store {
88  Log.info('getStore');
89  return Store.getInstance();
90}
91
92export class Store {
93  private currentReducer: Reducer;
94  private currentState = undefined;
95  private currentListeners: (() => void)[] | null = [];
96  private nextListeners = this.currentListeners;
97  private isDispatching = false;
98  private static mInstance: Store | undefined = undefined;
99
100  public static hasStore(): boolean {
101    return Store.mInstance !== undefined;
102  }
103
104  public static getInstance(): Store {
105    if (!Store.mInstance) {
106      Store.mInstance = new Store(getReducers(), getEnhancer());
107    }
108    return Store.mInstance;
109  }
110
111  public static createStore(reducer: Reducer, enhancer: Enhancer): Store {
112    Store.mInstance = new Store(reducer, enhancer);
113    return Store.mInstance;
114  }
115
116  private constructor(reducer: Reducer, enhancer: Enhancer) {
117    this.currentReducer = reducer;
118    this.dispatch({ type: INIT_TAG, data: null });
119    this.dispatch = enhancer(this.dispatch.bind(this));
120  }
121
122  public getState(): OhCombinedState {
123    if (this.isDispatching) {
124      Log.error('isDispatching get error');
125      return null;
126    }
127    return this.currentState;
128  }
129
130  public dispatch(action: ActionData): ActionData {
131    if (this.isDispatching) {
132      Log.error('isDispatching get error');
133      return null;
134    }
135    try {
136      this.isDispatching = true;
137      this.currentState = this.currentReducer(this.currentState, action);
138    } finally {
139      this.isDispatching = false;
140    }
141    this.currentListeners = this.nextListeners;
142    const listeners = this.nextListeners;
143    for (let i = 0; i < listeners.length; i++) {
144      const listener = listeners[i];
145      listener();
146    }
147    return action;
148  }
149
150  public subscribe(mapToProps: MapStateProp | null, mapToDispatch: MapDispatchProp | null): Unsubscribe {
151    Log.info(`${TAG} getStore subscribe invoke.`);
152    if (mapToDispatch) {
153      mapToDispatch(this.dispatch as Dispatch);
154    }
155    let unsubscribe: () => void = () => {};
156    if (mapToProps) {
157      mapToProps(this.currentState);
158      unsubscribe = this.stateSubscribe(() => mapToProps(this.currentState));
159      return {
160        destroy(): void {
161          unsubscribe();
162        }
163      };
164    }
165    return null;
166  }
167
168  private stateSubscribe(listener: () => void): () => void {
169    if (typeof listener !== 'function') {
170      Log.error('listener is not function');
171      return () => {};
172    }
173    if (this.isDispatching) {
174      Log.error('isDispatching stateSubscribe error');
175      return () => {};
176    }
177    let isSubScribed = true;
178
179    this.ensureCanMutateNextListeners();
180    this.nextListeners.push(listener);
181    return (): void => {
182      if (!isSubScribed) {
183        return;
184      }
185      this.currentListeners = null;
186      if (this.isDispatching) {
187        Log.error('isDispatching unstateSubscribe error');
188        return;
189      }
190      isSubScribed = false;
191      this.ensureCanMutateNextListeners();
192      const index = this.nextListeners.indexOf(listener);
193      this.nextListeners.slice(index, 1);
194      this.currentListeners = null;
195    };
196  }
197
198  private ensureCanMutateNextListeners(): void {
199    if (this.nextListeners === this.currentListeners) {
200      this.nextListeners = this.currentListeners.slice();
201    }
202  }
203}
204
205interface MapStateProp {
206  (state: OhCombinedState): void;
207}
208
209interface MapDispatchProp {
210  (dispatch: Dispatch): void;
211}