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

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import org.apache.ignite.IgniteFileSystem;
import org.apache.ignite.internal.IgniteEx;
import org.apache.ignite.internal.IgniteKernal;
import org.apache.ignite.internal.processors.cache.GridCacheAdapter;
import org.apache.ignite.internal.processors.igfs.IgfsImpl;
import org.apache.ignite.internal.util.typedef.CA;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.testframework.GridTestUtils;

/**
 * Tests fragmentizer work.
 */
public class IgfsFragmentizerSelfTest extends IgfsFragmentizerAbstractSelfTest {
    /**
     * @throws Exception If failed.
     */
    public void testReadFragmentizing() throws Exception {
        IgniteFileSystem igfs = grid(0).fileSystem("igfs");

        IgfsPath path = new IgfsPath("/someFile");

        try (IgfsOutputStream out = igfs.create(path, true)) {
            // Write 10 groups.
            for (int i = 0; i < 10 * IGFS_GROUP_SIZE; i++) {
                byte[] data = new byte[IGFS_BLOCK_SIZE];

                Arrays.fill(data, (byte)i);

                out.write(data);
            }
        }

        long start = System.currentTimeMillis();

        do {
            try (IgfsInputStream in = igfs.open(path)) {
                for (int i = 0; i < 10 * IGFS_GROUP_SIZE; i++) {
                    for (int j = 0; j < IGFS_BLOCK_SIZE; j++)
                        assertEquals(i & 0xFF, in.read());
                }

                assertEquals(-1, in.read());
            }
        }
        while (System.currentTimeMillis() - start < 7000);
    }

    /**
     * @throws Exception If failed.
     */
    public void testAppendFragmentizing() throws Exception {
        checkAppendFragmentizing(IGFS_BLOCK_SIZE / 4, false);
    }

    /**
     * @throws Exception If failed.
     */
    public void testAppendFragmentizingAligned() throws Exception {
        checkAppendFragmentizing(IGFS_BLOCK_SIZE, false);
    }

    /**
     * @throws Exception If failed.
     */
    public void testAppendFragmentizingDifferentNodes() throws Exception {
        checkAppendFragmentizing(IGFS_BLOCK_SIZE / 4, true);
    }

    /**
     * @throws Exception If failed.
     */
    public void testAppendFragmentizingAlignedDifferentNodes() throws Exception {
        checkAppendFragmentizing(IGFS_BLOCK_SIZE, true);
    }

    /**
    * @throws Exception If failed.
    */
    private void checkAppendFragmentizing(int chunkSize, boolean rotate) throws Exception {
        IgfsPath path = new IgfsPath("/someFile");

        long written = 0;

        int i = 0;
        int igfsIdx = 0;

        int fileSize = 30 * IGFS_GROUP_SIZE * IGFS_BLOCK_SIZE;

        while (written < fileSize) {
            IgniteFileSystem igfs = grid(igfsIdx).fileSystem("igfs");

            try (IgfsOutputStream out = igfs.append(path, true)) {
                byte[] data = new byte[chunkSize];

                Arrays.fill(data, (byte)i);

                out.write(data);
            }

            System.out.println("Written [start=" + written + ", filler=" + i + ']');

            written += chunkSize;
            i++;

            if (rotate && i % 5 == 0) {
                igfsIdx++;

                if (igfsIdx >= NODE_CNT)
                    igfsIdx = 0;
            }
        }

        IgniteFileSystem igfs = grid(0).fileSystem("igfs");

        try (IgfsInputStream in = igfs.open(path)) {
            i = 0;

            int read = 0;

            byte[] chunk = new byte[chunkSize];

            while (read < fileSize) {
                readFully(in, chunk);

                for (byte b : chunk)
                    assertEquals("For read offset [start=" + read + ", filler=" + (i & 0xFF) + ']',
                        i & 0xFF, b & 0xFF);

                i++;

                read += chunkSize;
            }
        }
    }

    /**
     * @throws Exception If failed.
     */
    public void testFlushFragmentizing() throws Exception {
        checkFlushFragmentizing(IGFS_BLOCK_SIZE / 4);
    }

    /**
     * @throws Exception If failed.
     */
    public void testFlushFragmentizingAligned() throws Exception {
        checkFlushFragmentizing(IGFS_BLOCK_SIZE);
    }

    /**
     * @param chunkSize Chunk size to test.
     * @throws Exception If failed.
     */
    private void checkFlushFragmentizing(int chunkSize) throws Exception {
        IgfsPath path = new IgfsPath("/someFile");

        long written = 0;
        int cnt = 0;

        int fileSize = 50 * IGFS_GROUP_SIZE * IGFS_BLOCK_SIZE;

        IgniteFileSystem igfs = grid(0).fileSystem("igfs");

        byte[] chunk = new byte[chunkSize];

        while (written < fileSize) {
            try (IgfsOutputStream out = igfs.append(path, true)) {
                for (int i = 0; i < 8; i++) {
                    Arrays.fill(chunk, (byte)cnt);

                    out.write(chunk);

                    out.flush();

                    written += chunkSize;

                    cnt++;
                }
            }
        }

        try (IgfsInputStream in = igfs.open(path)) {
            cnt = 0;

            int read = 0;

            while (read < fileSize) {
                readFully(in, chunk);

                for (byte b : chunk)
                    assertEquals("For read offset [start=" + read + ", filler=" + (cnt & 0xFF) + ']',
                        cnt & 0xFF, b & 0xFF);

                cnt++;

                read += chunkSize;
            }
        }
    }

    /**
     * @throws Exception If failed.
     */
    public void testDeleteFragmentizing() throws Exception {
        IgfsImpl igfs = (IgfsImpl)grid(0).fileSystem("igfs");

        for (int i = 0; i < 30; i++) {
            IgfsPath path = new IgfsPath("/someFile" + i);

            try (IgfsOutputStream out = igfs.create(path, true)) {
                for (int j = 0; j < 5 * IGFS_GROUP_SIZE; j++)
                    out.write(new byte[IGFS_BLOCK_SIZE]);
            }

            U.sleep(200);
        }

        igfs.clear();

        GridTestUtils.retryAssert(log, 50, 100, new CA() {
            @Override public void apply() {
                for (int i = 0; i < NODE_CNT; i++) {
                    IgniteEx g = grid(i);

                    GridCacheAdapter<Object, Object> cache = ((IgniteKernal)g).internalCache(
                        g.igfsx("igfs").configuration().getDataCacheConfiguration().getName());

                    assertTrue("Data cache is not empty [keys=" + cache.keySet() +
                        ", node=" + g.localNode().id() + ']', cache.isEmpty());
                }
            }
        });
    }

    /**
     * @param in Input stream to read from.
     * @param data Byte array to read to.
     * @throws IOException If read failed.
     */
    private static void readFully(InputStream in, byte[] data) throws IOException {
        int read = 0;

        while(read < data.length)
            read += in.read(data, read, data.length - read);
    }
}