/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.bookkeeper.server.http;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import lombok.extern.slf4j.Slf4j;
import org.apache.bookkeeper.bookie.Bookie;
import org.apache.bookkeeper.client.BKException;
import org.apache.bookkeeper.client.BookKeeperAdmin;
import org.apache.bookkeeper.conf.ClientConfiguration;
import org.apache.bookkeeper.conf.ServerConfiguration;
import org.apache.bookkeeper.http.HttpServer.ApiType;
import org.apache.bookkeeper.http.HttpServiceProvider;
import org.apache.bookkeeper.http.service.ErrorHttpService;
import org.apache.bookkeeper.http.service.HeartbeatService;
import org.apache.bookkeeper.http.service.HttpEndpointService;
import org.apache.bookkeeper.meta.LedgerManagerFactory;
import org.apache.bookkeeper.proto.BookieServer;
import org.apache.bookkeeper.replication.Auditor;
import org.apache.bookkeeper.replication.AutoRecoveryMain;
import org.apache.bookkeeper.server.http.service.AutoRecoveryStatusService;
import org.apache.bookkeeper.server.http.service.BookieInfoService;
import org.apache.bookkeeper.server.http.service.BookieIsReadyService;
import org.apache.bookkeeper.server.http.service.BookieSanityService;
import org.apache.bookkeeper.server.http.service.BookieStateReadOnlyService;
import org.apache.bookkeeper.server.http.service.BookieStateService;
import org.apache.bookkeeper.server.http.service.ClusterInfoService;
import org.apache.bookkeeper.server.http.service.ConfigurationService;
import org.apache.bookkeeper.server.http.service.DecommissionService;
import org.apache.bookkeeper.server.http.service.DeleteLedgerService;
import org.apache.bookkeeper.server.http.service.ExpandStorageService;
import org.apache.bookkeeper.server.http.service.GCDetailsService;
import org.apache.bookkeeper.server.http.service.GetLastLogMarkService;
import org.apache.bookkeeper.server.http.service.GetLedgerMetaService;
import org.apache.bookkeeper.server.http.service.ListBookieInfoService;
import org.apache.bookkeeper.server.http.service.ListBookiesService;
import org.apache.bookkeeper.server.http.service.ListDiskFilesService;
import org.apache.bookkeeper.server.http.service.ListLedgerService;
import org.apache.bookkeeper.server.http.service.ListUnderReplicatedLedgerService;
import org.apache.bookkeeper.server.http.service.LostBookieRecoveryDelayService;
import org.apache.bookkeeper.server.http.service.MetricsService;
import org.apache.bookkeeper.server.http.service.ReadLedgerEntryService;
import org.apache.bookkeeper.server.http.service.RecoveryBookieService;
import org.apache.bookkeeper.server.http.service.ResumeCompactionService;
import org.apache.bookkeeper.server.http.service.SuspendCompactionService;
import org.apache.bookkeeper.server.http.service.TriggerAuditService;
import org.apache.bookkeeper.server.http.service.TriggerGCService;
import org.apache.bookkeeper.server.http.service.TriggerLocationCompactService;
import org.apache.bookkeeper.server.http.service.WhoIsAuditorService;
import org.apache.bookkeeper.stats.StatsProvider;
import org.apache.zookeeper.KeeperException;

/**
 * Bookkeeper based implementation of HttpServiceProvider,
 * which provide bookkeeper services to handle http requests
 * from different http endpoints.
 */
@Slf4j
public class BKHttpServiceProvider implements HttpServiceProvider {

    private final StatsProvider statsProvider;
    private final BookieServer bookieServer;
    private final AutoRecoveryMain autoRecovery;
    private final LedgerManagerFactory ledgerManagerFactory;
    private final ServerConfiguration serverConf;
    private final BookKeeperAdmin bka;
    private final ExecutorService executor;

    private BKHttpServiceProvider(BookieServer bookieServer,
                                  AutoRecoveryMain autoRecovery,
                                  LedgerManagerFactory ledgerManagerFactory,
                                  ServerConfiguration serverConf,
                                  StatsProvider statsProvider)
        throws IOException, KeeperException, InterruptedException, BKException {
        this.bookieServer = bookieServer;
        this.autoRecovery = autoRecovery;
        this.ledgerManagerFactory = ledgerManagerFactory;
        this.serverConf = serverConf;
        this.statsProvider = statsProvider;
        ClientConfiguration clientConfiguration = new ClientConfiguration(serverConf);
        this.bka = new BookKeeperAdmin(clientConfiguration);

        this.executor = Executors.newSingleThreadExecutor(
          new ThreadFactoryBuilder().setNameFormat("BKHttpServiceThread").setDaemon(true).build());
    }

    @Override
    public void close() throws IOException {
        try {
            executor.shutdown();
            if (bka != null) {
                bka.close();
            }
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
            log.error("Interruption while closing BKHttpServiceProvider", ie);
            throw new IOException("Interruption while closing BKHttpServiceProvider", ie);
        } catch (BKException e) {
            log.error("Error while closing BKHttpServiceProvider", e);
            throw new IOException("Error while closing BKHttpServiceProvider", e);
        }
    }

    private ServerConfiguration getServerConf() {
        return serverConf;
    }

    private Auditor getAuditor() {
        return autoRecovery == null ? null : autoRecovery.getAuditor();
    }

    private Bookie getBookie() {
        return bookieServer == null ? null : bookieServer.getBookie();
    }

    /**
     * Builder for HttpServiceProvider.
     */
    public static class Builder {

        BookieServer bookieServer = null;
        AutoRecoveryMain autoRecovery = null;
        LedgerManagerFactory ledgerManagerFactory = null;
        ServerConfiguration serverConf = null;
        StatsProvider statsProvider = null;

        public Builder setBookieServer(BookieServer bookieServer) {
            this.bookieServer = bookieServer;
            return this;
        }

        public Builder setAutoRecovery(AutoRecoveryMain autoRecovery) {
            this.autoRecovery = autoRecovery;
            return this;
        }

        public Builder setServerConfiguration(ServerConfiguration conf) {
            this.serverConf = conf;
            return this;
        }

        public Builder setStatsProvider(StatsProvider statsProvider) {
            this.statsProvider = statsProvider;
            return this;
        }

        public Builder setLedgerManagerFactory(LedgerManagerFactory ledgerManagerFactory) {
            this.ledgerManagerFactory = ledgerManagerFactory;
            return this;
        }

        public BKHttpServiceProvider build()
            throws IOException, KeeperException, InterruptedException, BKException {
            return new BKHttpServiceProvider(
                bookieServer,
                autoRecovery,
                ledgerManagerFactory,
                serverConf,
                statsProvider
            );
        }
    }

    @Override
    public HttpEndpointService provideHttpEndpointService(ApiType type) {
        ServerConfiguration configuration = getServerConf();
        if (configuration == null) {
            return new ErrorHttpService();
        }

        switch (type) {
            case HEARTBEAT:
                return new HeartbeatService();
            case SERVER_CONFIG:
                return new ConfigurationService(configuration);
            case METRICS:
                return new MetricsService(configuration, statsProvider);

            // ledger
            case DELETE_LEDGER:
                return new DeleteLedgerService(configuration);
            case LIST_LEDGER:
                return new ListLedgerService(configuration, ledgerManagerFactory);
            case GET_LEDGER_META:
                return new GetLedgerMetaService(configuration, ledgerManagerFactory);
            case READ_LEDGER_ENTRY:
                return new ReadLedgerEntryService(configuration, bka);

            // bookie
            case LIST_BOOKIES:
                return new ListBookiesService(configuration, bka);
            case LIST_BOOKIE_INFO:
                return new ListBookieInfoService(configuration);
            case LAST_LOG_MARK:
                return new GetLastLogMarkService(configuration);
            case LIST_DISK_FILE:
                return new ListDiskFilesService(configuration);
            case EXPAND_STORAGE:
                return new ExpandStorageService(configuration);
            case GC:
                return new TriggerGCService(configuration, bookieServer);
            case GC_DETAILS:
                return new GCDetailsService(configuration, bookieServer);
            case BOOKIE_STATE:
                return new BookieStateService(bookieServer.getBookie());
            case BOOKIE_SANITY:
                return new BookieSanityService(configuration);
            case BOOKIE_STATE_READONLY:
                return new BookieStateReadOnlyService(bookieServer.getBookie());
            case BOOKIE_IS_READY:
                return new BookieIsReadyService(bookieServer.getBookie());
            case BOOKIE_INFO:
                return new BookieInfoService(bookieServer.getBookie());
            case CLUSTER_INFO:
                return new ClusterInfoService(bka, ledgerManagerFactory);
            case SUSPEND_GC_COMPACTION:
                return new SuspendCompactionService(bookieServer);
            case RESUME_GC_COMPACTION:
                return new ResumeCompactionService(bookieServer);
            case TRIGGER_ENTRY_LOCATION_COMPACT:
                return new TriggerLocationCompactService(bookieServer);

            // autorecovery
            case AUTORECOVERY_STATUS:
                return new AutoRecoveryStatusService(configuration);
            case RECOVERY_BOOKIE:
                return new RecoveryBookieService(configuration, bka, executor);
            case LIST_UNDER_REPLICATED_LEDGER:
                return new ListUnderReplicatedLedgerService(configuration, ledgerManagerFactory);
            case WHO_IS_AUDITOR:
                return new WhoIsAuditorService(configuration, bka);
            case TRIGGER_AUDIT:
                return new TriggerAuditService(configuration, bka);
            case LOST_BOOKIE_RECOVERY_DELAY:
                return new LostBookieRecoveryDelayService(configuration, bka);
            case DECOMMISSION:
                return new DecommissionService(configuration, bka, executor);

            default:
                return new ConfigurationService(configuration);
        }
    }

}
