import { Injectable } from '@angular/core';

import { ToastrService } from 'ngx-toastr';
import { Effect, Actions, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { of } from 'rxjs';
import { map, switchMap, withLatestFrom, catchError } from 'rxjs/operators';

import * as criteriaActions from '../../metamodel/action/criteria.action';
import * as searchActions from './search.action';
import * as fromSearch from './search.reducer';
import { SearchService } from './search.service';
import { BetweenCriterion, SearchMeta } from './model';

@Injectable()
export class SearchEffects {
    constructor(
        private actions$: Actions,
        private searchService: SearchService,
        private toastr: ToastrService,
        private store$: Store<{search: fromSearch.State}>
    ) { }

    @Effect()
    loadDatasetAttributeListSuccessAction$ = this.actions$.pipe(
        ofType(criteriaActions.LOAD_DATASET_ATTRIBUTE_LIST_SUCCESS),
        switchMap((action: criteriaActions.LoadDatasetAttributeListSuccessAction) => {
            const outputList = action.payload
                .filter(attribute => attribute.selected && attribute.id_output_family)
                .map(attribute => attribute.id);

            const criteriaList = action.payload
                .filter(attribute => attribute.id_criteria_family && attribute.search_type && attribute.min)
                .map(attribute => {
                    if (attribute.search_type === 'bw') {
                        return new BetweenCriterion(attribute.id, attribute.min.toString(), attribute.max.toString());
                    }
                });

            return [
                new searchActions.UpdateDefaultOutputListAction(outputList),
                new searchActions.UpdateDefaultCriteriaListAction(criteriaList)
            ];
        })
    );

    @Effect()
    updateDefaultOutputListAction$ = this.actions$.pipe(
        ofType(searchActions.UPDATE_DEFAULT_OUTPUT_LIST),
        withLatestFrom(this.store$),
        switchMap(([action, state]) => {
            if (state.search.outputList.length > 0) {
                return of({type: 'No Action'});
            } else {
                const updateDefaultOutputListAction = action as searchActions.UpdateDefaultOutputListAction;
                return of(new searchActions.UpdateOutputListAction(updateDefaultOutputListAction.payload));
            }
        })
    );

    @Effect()
    updateDefaultCriteriaListAction$ = this.actions$.pipe(
        ofType(searchActions.UPDATE_DEFAULT_CRITERIA_LIST),
        withLatestFrom(this.store$),
        switchMap(([action, state]) => {
            if (state.search.criteriaList.length > 0) {
                return of({type: 'No Action'});
            } else {
                const updateDefaultCriteriaListAction = action as searchActions.UpdateDefaultCriteriaListAction;
                return of(new searchActions.UpdateCriteriaListAction(updateDefaultCriteriaListAction.payload));
            }
        })
    );

    @Effect()
    retrieveMetaAction$ = this.actions$.pipe(
        ofType(searchActions.RETRIEVE_META),
        withLatestFrom(this.store$),
        switchMap(([action, state]) => {
            const query = state.search.datasetName + '?a=' + 
            state.search.outputList.join(';') + '&c=' +
            state.search.criteriaList.map(criterion => criterion.getCriterionStr()).join(';');
            return this.searchService.retrieveMeta(query).pipe(
                map((searchMeta: SearchMeta) => new searchActions.RetrieveMetaSuccessAction(searchMeta)),
                catchError(() => of(new searchActions.RetrieveMetaFailedAction()))
            )
        })
    )

    @Effect({dispatch: false})
    retrieveMetaFailedAction$ = this.actions$.pipe(
        ofType(searchActions.RETRIEVE_META_FAILED),
        map(_ => this.toastr.error('Loading Failed!', 'The search meta data loading failed'))
    );

    @Effect()
    retrieveDataAction$ = this.actions$.pipe(
        ofType(searchActions.RETRIEVE_DATA),
        withLatestFrom(this.store$),
        switchMap(([action, state]) => {
            const retrieveDataAction = action as searchActions.RetrieveDataAction;
            const query = state.search.datasetName + '?a=' + 
            state.search.outputList.join(';') + '&c=' +
            state.search.criteriaList.map(criterion => criterion.getCriterionStr()).join(';') +
            '&p=10:' + retrieveDataAction.payload;
            return this.searchService.retrieveData(query).pipe(
                map((searchData: any[]) => new searchActions.RetrieveDataSuccessAction(searchData)),
                catchError(() => of(new searchActions.RetrieveDataFailedAction()))
            )
        })
    )

    @Effect({dispatch: false})
    retrieveDataFailedAction$ = this.actions$.pipe(
        ofType(searchActions.RETRIEVE_DATA_FAILED),
        map(_ => this.toastr.error('Loading Failed!', 'The search data loading failed'))
    );
}