/*
 *
 *  *  Copyright 2014 OrientDB LTD (info(at)orientdb.com)
 *  *
 *  *  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.
 *  *
 *  * For more information: http://www.orientdb.com
 *
 */

package com.orientechnologies.orient.core.metadata.sequence;

import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.OMetadataUpdateListener;
import com.orientechnologies.orient.core.exception.OSequenceException;
import com.orientechnologies.orient.core.metadata.schema.OClassImpl;
import com.orientechnologies.orient.core.metadata.sequence.OSequence.SEQUENCE_TYPE;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery;

import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Matan Shukry (matanshukry@gmail.com)
 * @since 3/2/2015
 */
public class OSequenceLibraryImpl {
  private final Map<String, OSequence> sequences = new ConcurrentHashMap<String, OSequence>();

  public void create(ODatabaseDocumentInternal database) {
    init(database);
  }

  public void load(final ODatabaseDocumentInternal db) {
    sequences.clear();

    if (db.getMetadata().getImmutableSchemaSnapshot().existsClass(OSequence.CLASS_NAME)) {
      final List<ODocument> result = db.query(new OSQLSynchQuery<ODocument>("SELECT FROM " + OSequence.CLASS_NAME));
      for (ODocument document : result) {
        document.reload();

        final OSequence sequence = OSequenceHelper.createSequence(document);
        sequences.put(sequence.getName().toUpperCase(Locale.ENGLISH), sequence);
      }
    }
  }

  public void close() {
    sequences.clear();
  }

  public Set<String> getSequenceNames() {
    return sequences.keySet();
  }

  public int getSequenceCount() {
    return sequences.size();
  }

  public OSequence getSequence(final ODatabaseDocumentInternal database, final String iName) {
    final String name = iName.toUpperCase(Locale.ENGLISH);

    OSequence seq = sequences.get(name);
    if (seq == null) {
      load(database);
      seq = sequences.get(name);
    }

    if (seq != null) {
      seq.bindOnLocalThread();
    }

    return seq;
  }

  public OSequence createSequence(final ODatabaseDocumentInternal database, final String iName, final SEQUENCE_TYPE sequenceType,
      final OSequence.CreateParams params) {
    init(database);

    final String key = iName.toUpperCase(Locale.ENGLISH);
    validateSequenceNoExists(key);

    final OSequence sequence = OSequenceHelper.createSequence(sequenceType, params, null).setName(iName);
    sequence.save(database);
    sequences.put(key, sequence);

    return sequence;
  }

  public void dropSequence(final ODatabaseDocumentInternal database, final String iName) {
    final OSequence seq = getSequence(database, iName);

    if (seq != null) {
      database.delete(seq.getDocument().getIdentity());
      sequences.remove(iName.toUpperCase(Locale.ENGLISH));
    }
  }

  public OSequence onSequenceCreated(final ODatabaseDocumentInternal database, final ODocument iDocument) {
    init(database);

    String name = OSequence.getSequenceName(iDocument);
    if (name == null)
      return null;

    name = name.toUpperCase(Locale.ENGLISH);

    final OSequence seq = getSequence(database, name);

    if (seq != null)
      return seq;

    final OSequence sequence = OSequenceHelper.createSequence(iDocument);

    sequences.put(name, sequence);
    onSequenceLibraryUpdate(database);
    return sequence;
  }

  public OSequence onSequenceUpdated(final ODatabaseDocumentInternal database, final ODocument iDocument) {
    String name = OSequence.getSequenceName(iDocument);
    if (name == null)
      return null;

    name = name.toUpperCase(Locale.ENGLISH);

    final OSequence sequence = sequences.get(name);
    if (sequence == null)
      return null;

    sequence.onUpdate(iDocument);
    return sequence;
  }

  public void onSequenceDropped(final ODatabaseDocumentInternal database, final ODocument iDocument) {
    String name = OSequence.getSequenceName(iDocument);
    if (name == null)
      return;

    name = name.toUpperCase(Locale.ENGLISH);

    sequences.remove(name);
    onSequenceLibraryUpdate(database);
  }

  private void init(final ODatabaseDocumentInternal database) {
    if (database.getMetadata().getSchema().existsClass(OSequence.CLASS_NAME)) {
      return;
    }

    final OClassImpl sequenceClass = (OClassImpl) database.getMetadata().getSchema().createClass(OSequence.CLASS_NAME);
    OSequence.initClass(sequenceClass);
  }

  private void validateSequenceNoExists(final String iName) {
    if (sequences.containsKey(iName)) {
      throw new OSequenceException("Sequence '" + iName + "' already exists");
    }
  }

  private void validateSequenceExists(final String iName) {
    if (!sequences.containsKey(iName)) {
      throw new OSequenceException("Sequence '" + iName + "' does not exists");
    }
  }

  private void onSequenceLibraryUpdate(ODatabaseDocumentInternal database) {
    for (OMetadataUpdateListener one : database.getSharedContext().browseListeners()) {
      one.onSequenceLibraryUpdate(this);
    }
  }

}
@@ -1721,24 +1721,37 @@
 %7D%0A%0A  public 
+synchronized 
 void load(fi
@@ -2326,16 +2326,29 @@
  public 
+synchronized 
 Set%3CStri
@@ -2417,16 +2417,29 @@
  public 
+synchronized 
 int getS
@@ -2654,24 +2654,61 @@
   OSequence 
+seq;%0A    synchronized (this) %7B%0A      
 seq = sequen
@@ -2722,24 +2722,26 @@
 (name);%0A    
+  
 if (seq == n
@@ -2753,16 +2753,18 @@
 %7B%0A      
+ 
+ 
 load(dat
@@ -2767,24 +2767,26 @@
 (database);%0A
+  
       seq = 
@@ -2802,24 +2802,32 @@
 .get(name);%0A
+      %7D%0A
     %7D%0A%0A    i
@@ -2904,32 +2904,45 @@
 ;%0A  %7D%0A%0A  public 
+synchronized 
 OSequence create
@@ -3407,24 +3407,37 @@
 %7D%0A%0A  public 
+synchronized 
 void dropSeq

Fix synchronization issues in sequence library