/*
 * 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.meta;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

/**
 * This class encapsulates ledger layout information that is persistently stored
 * in registration service. It provides parsing and serialization methods of such
 * information.
 */
@Slf4j
@Getter
@EqualsAndHashCode
@ToString
public class LedgerLayout {

    // version of compatibility layout version
    public static final int LAYOUT_MIN_COMPAT_VERSION = 1;
    // version of ledger layout metadata
    public static final int LAYOUT_FORMAT_VERSION = 2;

    private static final String splitter = ":";
    private static final String lSplitter = "\n";

    // ledger manager factory class
    private final String managerFactoryClass;
    // ledger manager version
    private final int managerVersion;

    // layout version of how to store layout information
    private final int layoutFormatVersion;

    /**
     * Ledger Layout Constructor.
     *
     * @param managerFactoryCls
     *          Ledger Manager Factory Class
     * @param managerVersion
     *          Ledger Manager Version
     */
    public LedgerLayout(String managerFactoryCls, int managerVersion) {
        this(managerFactoryCls, managerVersion, LAYOUT_FORMAT_VERSION);
    }

    LedgerLayout(String managerFactoryCls, int managerVersion,
                 int layoutVersion) {
        this.managerFactoryClass = managerFactoryCls;
        this.managerVersion = managerVersion;
        this.layoutFormatVersion = layoutVersion;
    }

    /**
     * Generates a byte array based on the LedgerLayout object.
     *
     * @return byte[]
     */
    public byte[] serialize() throws IOException {
        String s =
          new StringBuilder().append(layoutFormatVersion).append(lSplitter)
              .append(managerFactoryClass).append(splitter).append(managerVersion).toString();

        if (log.isDebugEnabled()) {
            log.debug("Serialized layout info: {}", s);
        }
        return s.getBytes(StandardCharsets.UTF_8);
    }

    /**
     * Parses a given byte array and transforms into a LedgerLayout object.
     *
     * @param bytes
     *          byte array to parse
     * @return LedgerLayout
     * @throws IOException
     *             if the given byte[] cannot be parsed
     */
    public static LedgerLayout parseLayout(byte[] bytes) throws IOException {
        String layout = new String(bytes, StandardCharsets.UTF_8);
        if (log.isDebugEnabled()) {
            log.debug("Parsing Layout: {}", layout);
        }

        String[] lines = layout.split(lSplitter);

        try {
            int layoutFormatVersion = Integer.parseInt(lines[0]);
            if (LAYOUT_FORMAT_VERSION < layoutFormatVersion || LAYOUT_MIN_COMPAT_VERSION > layoutFormatVersion) {
                throw new IOException("Metadata version not compatible. Expected "
                        + LAYOUT_FORMAT_VERSION + ", but got " + layoutFormatVersion);
            }

            if (lines.length < 2) {
                throw new IOException("Ledger manager and its version absent from layout: " + layout);
            }

            String[] parts = lines[1].split(splitter);
            if (parts.length != 2) {
                throw new IOException("Invalid Ledger Manager defined in layout : " + layout);
            }
            // ledger manager factory class
            String managerFactoryCls = parts[0];
            // ledger manager version
            int managerVersion = Integer.parseInt(parts[1]);
            return new LedgerLayout(managerFactoryCls, managerVersion, layoutFormatVersion);
        } catch (NumberFormatException e) {
            throw new IOException("Could not parse layout '" + layout + "'", e);
        }
    }

}
