/*
 * Copyright 2003-2014 JetBrains s.r.o.
 *
 * Licensed 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 jetbrains.mps.generator.impl.cache;

import jetbrains.mps.generator.GenerationCacheContainer.ModelCacheContainer;
import jetbrains.mps.util.FileUtil;
import jetbrains.mps.util.io.ModelInputStream;
import jetbrains.mps.util.io.ModelOutputStream;
import org.jetbrains.mps.openapi.model.SModelReference;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * Evgeny Gryaznov, Sep 21, 2010
 */
public class IntermediateModelsCache {

  private static final String SIGNATURE = "signature";
  private static final String STEPS = "steps";

  private final ModelCacheContainer myCacheContainer;
  private final String mySignature;
  private boolean isOk = true;

  private List<Integer> mySteps;

  public IntermediateModelsCache(ModelCacheContainer cacheContainer, String signature) {
    myCacheContainer = cacheContainer;
    mySignature = signature;
    mySteps = new ArrayList<Integer>();
  }

  public String getSignature() {
    return mySignature;
  }

  private String getStorageName(int major, int minor) {
    return "step" + major + "_" + minor;
  }

  public static IntermediateModelsCache load(ModelCacheContainer cacheContainer) {
    try {
      ModelInputStream is = new ModelInputStream(cacheContainer.openStream(STEPS));
      List<Integer> steps;
      try {
        int count = is.readInt();
        if (count > 10000) {
          throw new IOException("illegal data");
        }
        steps = new ArrayList<Integer>(count);
        for (int i = 0; i < count; i++) {
          steps.add(is.readInt());
        }
      } finally {
        is.close();
      }

      InputStreamReader reader = new InputStreamReader(cacheContainer.openStream(SIGNATURE), FileUtil.DEFAULT_CHARSET);
      try {
        StringBuilder signature = new StringBuilder();
        char[] buff = new char[512];
        int size;
        while ((size = reader.read(buff)) > 0) {
          signature.append(buff, 0, size);
        }
        IntermediateModelsCache result = new IntermediateModelsCache(cacheContainer, signature.toString());
        result.setSteps(steps);
        return result;
      } finally {
        reader.close();
      }
    } catch (IOException e) {
      /* cannot load */
    }
    return null;
  }

  private void setSteps(List<Integer> steps) {
    mySteps = steps;
  }

  public int getMinorCount(int majorStep) {
    return mySteps.get(majorStep);
  }

  public void store(int majorStep, int minor, TransientModelWithMetainfo model) {
    try {
      while (majorStep >= mySteps.size()) {
        mySteps.add(0);
      }
      int minorStep = mySteps.get(majorStep);
      mySteps.set(majorStep, minorStep + 1);
      assert minor == minorStep;

      ModelOutputStream os = new ModelOutputStream(myCacheContainer.createStream(getStorageName(majorStep, minorStep)));
      try {
        model.save(os);
      } finally {
        os.close();
      }
    } catch (IOException e) {
      isOk = false;
    }
  }

  public TransientModelWithMetainfo load(int majorStep, int minorStep, SModelReference modelReference) {
    try {
      InputStream stream = myCacheContainer.openStream(getStorageName(majorStep, minorStep));
      ModelInputStream is = new ModelInputStream(stream);
      try {
        return TransientModelWithMetainfo.load(is, modelReference);
      } finally {
        is.close();
      }
    } catch (IOException e) {
      isOk = false;
    }
    return null;
  }

  public void store() {
    try {
      ModelOutputStream os = new ModelOutputStream(myCacheContainer.createStream(STEPS));
      try {
        os.writeInt(mySteps.size());
        for (Integer i : mySteps) {
          os.writeInt(i);
        }
      } finally {
        os.close();
      }

      OutputStreamWriter writer = new OutputStreamWriter(myCacheContainer.createStream(SIGNATURE));
      try {
        writer.write(mySignature);
      } finally {
        writer.close();
      }

      if (isOk) {
        myCacheContainer.commit();
      } else {
        myCacheContainer.revert();
      }
    } catch (IOException e) {
      myCacheContainer.revert();
    }
  }

  public void remove() {
    myCacheContainer.revert();
  }
}
