/*
 * This software is distributed under following license based on modified BSD
 * style license.
 * ----------------------------------------------------------------------
 * 
 * Copyright 2009 The Nimbus2 Project. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE NIMBUS PROJECT ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL THE NIMBUS PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the Nimbus2 Project.
 */
package jp.ossc.nimbus.service.context;

import java.util.Map;
import java.util.HashMap;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectInput;

import jp.ossc.nimbus.beans.dataset.Record;
import jp.ossc.nimbus.beans.dataset.RecordList;
import jp.ossc.nimbus.beans.dataset.RecordSchema;
import jp.ossc.nimbus.beans.dataset.PropertySchemaDefineException;

/**
 * 共有コンテキスト用のレコードリスト。<p>
 * 差分更新をサポートする。<br>
 *
 * @author M.Takata
 */
public class SharedContextRecordList extends RecordList implements SharedContextValueDifferenceSupport{
    
    /**
     * 未定義のレコードリストを生成する。<p>
     */
    public SharedContextRecordList(){
    }
    
    /**
     * 未定義のレコードリストを生成する。<p>
     *
     * @param name レコード名
     */
    public SharedContextRecordList(String name){
        super(name);
    }
    
    /**
     * 空のレコードリストを生成する。<p>
     *
     * @param name レコード名
     * @param schema スキーマ文字列
     * @exception PropertySchemaDefineException プロパティのスキーマ定義に失敗した場合
     */
    public SharedContextRecordList(String name, String schema)
     throws PropertySchemaDefineException{
        super(name, schema);
    }
    
    /**
     * 空のレコードリストを生成する。<p>
     *
     * @param name レコード名
     * @param schema スキーマ
     * @exception PropertySchemaDefineException プロパティのスキーマ定義に失敗した場合
     */
    public SharedContextRecordList(String name, RecordSchema schema)
     throws PropertySchemaDefineException{
        super(name, schema);
    }
    
    @Override
    public Record createRecord(){
        return new SharedContextRecord(recordSchema);
    }
    
    @Override
    public boolean add(Record o){
        if(o != null){
            ((SharedContextRecord)o).setRecordList(this);
            ((SharedContextRecord)o).setIndex(size());
        }
        return super.add(o);
    }
    
    @Override
    public void add(int index, Record element){
        super.add(index, element);
        if(element != null){
            ((SharedContextRecord)element).setRecordList(this);
            ((SharedContextRecord)element).setIndex(index);
        }
        for(int i = index + 1, imax = size(); i < imax; i++){
            SharedContextRecord record = (SharedContextRecord)get(i);
            if(record != null){
                record.setIndex(record.getIndex() + 1);
            }
        }
    }
    
    @Override
    public Record set(int index, Record element){
        Record result = super.set(index, element);
        if(result != null){
            ((SharedContextRecord)result).setRecordList(null);
            ((SharedContextRecord)result).setIndex(-1);
        }
        return result;
    }
    
    @Override
    public boolean remove(Object o){
        boolean result = super.remove(o);
        if(result && o != null){
            ((SharedContextRecord)o).setRecordList(null);
            ((SharedContextRecord)o).setIndex(-1);
        }
        for(int i = 0, imax = size(); i < imax; i++){
            SharedContextRecord record = (SharedContextRecord)get(i);
            if(record != null){
                record.setIndex(i);
            }
        }
        return result;
    }
    
    @Override
    public Record remove(int index){
        Record result = super.remove(index);
        if(result != null){
            ((SharedContextRecord)result).setRecordList(null);
            ((SharedContextRecord)result).setIndex(-1);
        }
        for(int i = index, imax = size(); i < imax; i++){
            SharedContextRecord record = (SharedContextRecord)get(i);
            if(record != null){
                record.setIndex(record.getIndex() - 1);
            }
        }
        return result;
    }
    
    @Override
    public boolean addAll(Collection<? extends Record> c){
        boolean result = super.addAll(c);
        if(result){
            for(int i = 0, imax = size(); i < imax; i++){
                SharedContextRecord record = (SharedContextRecord)get(i);
                if(record != null){
                    record.setRecordList(this);
                    record.setIndex(i);
                }
            }
        }
        return result;
    }
    
    @Override
    public boolean addAll(int index, Collection<? extends Record> c){
        boolean result = super.addAll(index, c);
        if(result){
            for(int i = index, imax = size(); i < imax; i++){
                SharedContextRecord record = (SharedContextRecord)get(i);
                if(record != null){
                    record.setRecordList(this);
                    record.setIndex(i);
                }
            }
        }
        return result;
    }
    
    @Override
    public boolean removeAll(Collection<?> c){
        boolean result = super.removeAll(c);
        if(result){
            for(int i = 0, imax = size(); i < imax; i++){
                SharedContextRecord record = (SharedContextRecord)get(i);
                if(record != null){
                    record.setIndex(i);
                }
            }
        }
        return result;
    }
    
    @Override
    public boolean retainAll(Collection<?> c){
        boolean result = super.retainAll(c);
        if(result){
            for(int i = 0, imax = size(); i < imax; i++){
                SharedContextRecord record = (SharedContextRecord)get(i);
                if(record != null){
                    record.setIndex(i);
                }
            }
        }
        return result;
    }
    
    /**
     * 指定されたレコードを追加した場合の差分情報を取得する。<p>
     *
     * @param record レコード
     * @param diff 差分
     * @return 差分
     * @exception SharedContextUpdateException 差分情報の取得に失敗した場合
     */
    public SharedContextValueDifference updateAdd(Record record, SharedContextValueDifference diff) throws SharedContextUpdateException{
        if(diff == null){
            diff = new Difference();
        }else if(!(diff instanceof Difference)){
            throw new SharedContextUpdateException("Unsupported type. class=" + diff.getClass().getName());
        }
        ((Difference)diff).add(this, record);
        return diff;
    }
    
    /**
     * 指定されたレコードを、指定したインデックスに挿入した場合の差分情報を取得する。<p>
     *
     * @param index インデックス
     * @param record レコード
     * @param diff 差分
     * @return 差分
     * @exception SharedContextUpdateException 差分情報の取得に失敗した場合
     */
    public SharedContextValueDifference updateAdd(int index, Record record, SharedContextValueDifference diff) throws SharedContextUpdateException{
        if(diff == null){
            diff = new Difference();
        }else if(!(diff instanceof Difference)){
            throw new SharedContextUpdateException("Unsupported type. class=" + diff.getClass().getName());
        }
        ((Difference)diff).add(this, index, record);
        return diff;
    }
    
    /**
     * 指定されたレコードを、指定したインデックスのレコードと差し替えた場合の差分情報を取得する。<p>
     *
     * @param index インデックス
     * @param record レコード
     * @param diff 差分
     * @return 差分
     * @exception SharedContextUpdateException 差分情報の取得に失敗した場合
     */
    public SharedContextValueDifference updateSet(int index, Record record, SharedContextValueDifference diff) throws SharedContextUpdateException{
        if(diff == null){
            diff = new Difference();
        }else if(!(diff instanceof Difference)){
            throw new SharedContextUpdateException("Unsupported type. class=" + diff.getClass().getName());
        }
        ((Difference)diff).set(this, index, record);
        return diff;
    }
    
    /**
     * 指定されたインデックスのレコードを削除した場合の差分情報を取得する。<p>
     *
     * @param index インデックス
     * @param record レコード
     * @param diff 差分
     * @return 差分
     * @exception SharedContextUpdateException 差分情報の取得に失敗した場合
     */
    public SharedContextValueDifference updateRemove(int index, SharedContextValueDifference diff) throws SharedContextUpdateException{
        if(diff == null){
            diff = new Difference();
        }else if(!(diff instanceof Difference)){
            throw new SharedContextUpdateException("Unsupported type. class=" + diff.getClass().getName());
        }
        ((Difference)diff).remove(this, index);
        return diff;
    }
    
    public SharedContextValueDifference updateRemove(Record record, SharedContextValueDifference diff) throws SharedContextUpdateException{
        if(diff == null){
            diff = new Difference();
        }else if(!(diff instanceof Difference)){
            throw new SharedContextUpdateException("Unsupported type. class=" + diff.getClass().getName());
        }
        ((Difference)diff).remove(this, record);
        return diff;
    }
    
    public SharedContextValueDifference updateClear(SharedContextValueDifference diff) throws SharedContextUpdateException{
        if(diff == null){
            diff = new Difference();
        }else if(!(diff instanceof Difference)){
            throw new SharedContextUpdateException("Unsupported type. class=" + diff.getClass().getName());
        }
        ((Difference)diff).clear(this);
        return diff;
    }
    
    @Override
    public void update(SharedContextValueDifference diff) throws SharedContextUpdateException{
        if(!(diff instanceof Difference)){
            throw new SharedContextUpdateException("Unsupported type. class=" + diff.getClass().getName());
        }
        ((Difference)diff).updateRecordList(this);
    }
    
    public static class Difference implements SharedContextValueDifference, Externalizable{
        private Map<Integer,SharedContextRecord.Difference> recordDiffMap;
        private List<Transaction> transactionList;
        
        public void add(SharedContextRecordList list, Record record) throws SharedContextUpdateException{
            if(transactionList == null){
                transactionList = new ArrayList<Transaction>();
            }
            transactionList.add(new AddTransaction(record));
        }
        
        public void add(SharedContextRecordList list, int index, Record record) throws SharedContextUpdateException{
            if(transactionList == null){
                transactionList = new ArrayList<Transaction>();
            }
            if(index < 0 || index > list.size() - 1){
                throw new SharedContextUpdateException("Illegal index. index=" + index + ", size=" + list.size());
            }
            transactionList.add(new AddTransaction(index, record));
        }
        
        public void set(SharedContextRecordList list, int index, Record record) throws SharedContextUpdateException{
            if(transactionList == null){
                transactionList = new ArrayList<Transaction>();
            }
            if(index < 0 || index > list.size() - 1){
                throw new SharedContextUpdateException("Illegal index. index=" + index + ", size=" + list.size());
            }
            transactionList.add(new SetTransaction(index, record));
        }
        
        public void remove(SharedContextRecordList list, Object val) throws SharedContextUpdateException{
            if(transactionList == null){
                transactionList = new ArrayList<Transaction>();
            }
            int index = -1;
            if(val != null){
                SharedContextRecord record = (SharedContextRecord)val;
                index = record.getIndex();
            }
            if(index == -1){
                index = list.indexOf(val);
            }
            if(index != -1){
                transactionList.add(new RemoveTransaction(index));
            }
        }
        
        public void remove(SharedContextRecordList list, int index) throws SharedContextUpdateException{
            if(transactionList == null){
                transactionList = new ArrayList<Transaction>();
            }
            if(index < 0 || index > list.size() - 1){
                throw new SharedContextUpdateException("Illegal index. index=" + index + ", size=" + list.size());
            }
            transactionList.add(new RemoveTransaction(index));
        }
        
        public void clear(SharedContextRecordList list) throws SharedContextUpdateException{
            if(transactionList == null){
                transactionList = new ArrayList<Transaction>();
            }
            transactionList.add(new ClearTransaction());
        }
        
        public void addAll(SharedContextRecordList list, Collection<? extends Record> c) throws SharedContextUpdateException{
            if(transactionList == null){
                transactionList = new ArrayList<Transaction>();
            }
            transactionList.add(new AddAllTransaction(c));
        }
        
        public void addAll(SharedContextRecordList list, int index, Collection<? extends Record> c) throws SharedContextUpdateException{
            if(transactionList == null){
                transactionList = new ArrayList<Transaction>();
            }
            if(index < 0 || index > list.size() - 1){
                throw new SharedContextUpdateException("Illegal index. index=" + index + ", size=" + list.size());
            }
            transactionList.add(new AddAllTransaction(index, c));
        }
        
        public void removeAll(SharedContextRecordList list, Collection<?> c) throws SharedContextUpdateException{
            if(transactionList == null){
                transactionList = new ArrayList<Transaction>();
            }
            transactionList.add(new RemoveAllTransaction(c));
        }
        
        public void retainAll(SharedContextRecordList list, Collection<?> c) throws SharedContextUpdateException{
            if(transactionList == null){
                transactionList = new ArrayList<Transaction>();
            }
            transactionList.add(new RetainAllTransaction(c));
        }
        
        public void updateRecord(SharedContextRecordList list, int index, SharedContextRecord.Difference diff) throws SharedContextUpdateException{
            if(transactionList == null){
                transactionList = new ArrayList<Transaction>();
            }
            if(index < 0 || index > list.size() - 1){
                throw new SharedContextUpdateException("Illegal index. index=" + index + ", size=" + list.size());
            }
            if(recordDiffMap == null){
                recordDiffMap = new HashMap<Integer,SharedContextRecord.Difference>();
            }
            Integer key = new Integer(index);
            if(!recordDiffMap.containsKey(key)){
                recordDiffMap.put(key, diff);
                transactionList.add(new UpdateTransaction(index, diff));
            }
        }
        
        protected SharedContextRecord.Difference getRecordDifference(int index){
            return recordDiffMap == null ? null : (SharedContextRecord.Difference)recordDiffMap.get(new Integer(index));
        }
        
        public void updateRecordList(SharedContextRecordList list) throws SharedContextUpdateException{
            if(transactionList != null && transactionList.size() != 0){
                for(int i = 0, imax = transactionList.size(); i < imax; i++){
                    transactionList.get(i).execute(list);
                }
            }
        }
        
        @Override
        public boolean isUpdate(){
            return transactionList != null && transactionList.size() != 0;
        }
        
        public List<Transaction> getTransactionList(){
            return transactionList;
        }
        
        @Override
        public void writeExternal(ObjectOutput out) throws IOException{
            if(transactionList == null || transactionList.size() == 0){
                out.writeInt(0);
            }else{
                out.writeInt(transactionList.size());
                for(int i = 0, imax = transactionList.size(); i < imax; i++){
                    Transaction tran = (Transaction)transactionList.get(i);
                    out.write(tran.getType());
                    tran.writeExternal(out);
                }
            }
        }
        
        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
            int size = in.readInt();
            if(size > 0){
                transactionList = new ArrayList<Transaction>(size);
                for(int i = 0; i < size; i++){
                    final int type = in.read();
                    Transaction tran = null;
                    switch(type){
                    case Transaction.ADD:
                        tran = new AddTransaction();
                        break;
                    case Transaction.SET:
                        tran = new SetTransaction();
                        break;
                    case Transaction.REMOVE:
                        tran = new RemoveTransaction();
                        break;
                    case Transaction.CLEAR:
                        tran = new ClearTransaction();
                        break;
                    case Transaction.ADDALL:
                        tran = new AddAllTransaction();
                        break;
                    case Transaction.REMOVEALL:
                        tran = new RemoveAllTransaction();
                        break;
                    case Transaction.RETAINALL:
                        tran = new RetainAllTransaction();
                        break;
                    case Transaction.UPDATE:
                        tran = new UpdateTransaction();
                        break;
                    default:
                        throw new IOException("Unknown transaction. type=" + type);
                    }
                    tran.readExternal(in);
                    transactionList.add(tran);
                }
            }
        }
        
        public static interface Transaction extends Externalizable{
            
            public static final byte ADD       = 1;
            public static final byte SET       = 2;
            public static final byte REMOVE    = 3;
            public static final byte CLEAR     = 4;
            public static final byte ADDALL    = 5;
            public static final byte REMOVEALL = 6;
            public static final byte RETAINALL = 7;
            public static final byte UPDATE    = 8;
            
            public byte getType();
            
            public void execute(SharedContextRecordList list) throws SharedContextUpdateException;
        }
        
        public static class AddTransaction implements Transaction{
            private int index = -1;
            private Record record;
            public AddTransaction(){
            }
            public AddTransaction(Record record){
                this.record = record;
            }
            public AddTransaction(int index, Record record){
                this.index = index;
                this.record = record;
            }
            @Override
            public byte getType(){
                return ADD;
            }
            @Override
            public void execute(SharedContextRecordList list) throws SharedContextUpdateException{
                if(index == -1){
                    list.add(record);
                }else{
                    list.add(index, record);
                }
            }
            
            public int getIndex(){
                return index;
            }
            
            public Record getRecord(){
                return record;
            }
            
            @Override
            public void writeExternal(ObjectOutput out) throws IOException{
                out.writeInt(index);
                out.writeObject(record);
            }
            @Override
            public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
                index = in.readInt();
                record = (Record)in.readObject();
            }
        }
        
        public static class SetTransaction implements Transaction{
            private int index;
            private Record record;
            public SetTransaction(){
            }
            public SetTransaction(int index, Record record){
                this.index = index;
                this.record = record;
            }
            @Override
            public byte getType(){
                return SET;
            }
            @Override
            public void execute(SharedContextRecordList list) throws SharedContextUpdateException{
                list.set(index, record);
            }
            
            public int getIndex(){
                return index;
            }
            
            public Record getRecord(){
                return record;
            }
            
            @Override
            public void writeExternal(ObjectOutput out) throws IOException{
                out.writeInt(index);
                out.writeObject(record);
            }
            @Override
            public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
                index = in.readInt();
                record = (Record)in.readObject();
            }
        }
        
        public static class RemoveTransaction implements Transaction{
            private int index;
            public RemoveTransaction(){
            }
            public RemoveTransaction(int index){
                this.index = index;
            }
            @Override
            public byte getType(){
                return REMOVE;
            }
            @Override
            public void execute(SharedContextRecordList list) throws SharedContextUpdateException{
                list.remove(index);
            }
            
            public int getIndex(){
                return index;
            }
            
            @Override
            public void writeExternal(ObjectOutput out) throws IOException{
                out.writeInt(index);
            }
            @Override
            public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
                index = in.readInt();
            }
        }
        
        private static class ClearTransaction implements Transaction{
            public ClearTransaction(){
            }
            @Override
            public byte getType(){
                return CLEAR;
            }
            @Override
            public void execute(SharedContextRecordList list) throws SharedContextUpdateException{
                list.clear();
            }
            @Override
            public void writeExternal(ObjectOutput out) throws IOException{
            }
            @Override
            public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
            }
        }
        
        public static class AddAllTransaction implements Transaction{
            private int index = -1;
            private Collection<? extends Record> c;
            public AddAllTransaction(){
            }
            public AddAllTransaction(Collection<? extends Record> c){
                this.c = new ArrayList<Record>(c);
            }
            public AddAllTransaction(int index, Collection<? extends Record> c){
                this.index = index;
                this.c = new ArrayList<Record>(c);
            }
            @Override
            public byte getType(){
                return ADDALL;
            }
            @Override
            public void execute(SharedContextRecordList list) throws SharedContextUpdateException{
                if(index == -1){
                    list.addAll(c);
                }else{
                    list.addAll(index, c);
                }
            }
            
            public int getIndex(){
                return index;
            }
            
            public Collection<? extends Record> getRecords(){
                return c;
            }
            
            @Override
            public void writeExternal(ObjectOutput out) throws IOException{
                out.writeInt(index);
                out.writeObject(c);
            }
            @SuppressWarnings("unchecked")
            @Override
            public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
                index = in.readInt();
                c = (Collection<? extends Record>)in.readObject();
            }
        }
        
        public static class RemoveAllTransaction implements Transaction{
            private Collection<?> c;
            public RemoveAllTransaction(){
            }
            public RemoveAllTransaction(Collection<?> c){
                this.c = new ArrayList<Object>(c);
            }
            @Override
            public byte getType(){
                return REMOVEALL;
            }
            @Override
            public void execute(SharedContextRecordList list) throws SharedContextUpdateException{
                list.removeAll(c);
            }
            
            public Collection<?> getRecords(){
                return c;
            }
            
            @Override
            public void writeExternal(ObjectOutput out) throws IOException{
                out.writeObject(c);
            }
            @Override
            public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
                c = (Collection<?>)in.readObject();
            }
        }
        
        public static class RetainAllTransaction implements Transaction{
            private Collection<?> c;
            public RetainAllTransaction(){
            }
            public RetainAllTransaction(Collection<?> c){
                this.c = new ArrayList<Object>(c);
            }
            @Override
            public byte getType(){
                return RETAINALL;
            }
            @Override
            public void execute(SharedContextRecordList list) throws SharedContextUpdateException{
                list.retainAll(c);
            }
            
            public Collection<?> getRecords(){
                return c;
            }
            
            @Override
            public void writeExternal(ObjectOutput out) throws IOException{
                out.writeObject(c);
            }
            @Override
            public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
                c = (Collection<?>)in.readObject();
            }
        }
        
        public static class UpdateTransaction implements Transaction{
            private int index;
            private SharedContextRecord.Difference diff;
            public UpdateTransaction(){
            }
            public UpdateTransaction(int index, SharedContextRecord.Difference diff){
                this.index = index;
                this.diff = diff;
            }
            @Override
            public byte getType(){
                return UPDATE;
            }
            public boolean isUpdate(){
                return diff.isUpdate();
            }
            
            @Override
            public void execute(SharedContextRecordList list) throws SharedContextUpdateException{
                SharedContextRecord record = (SharedContextRecord)list.getRecord(index);
                record.update(diff);
            }
            
            public int getIndex(){
                return index;
            }
            
            public SharedContextRecord.Difference getRecordDifference(){
                return diff;
            }
            
            @Override
            public void writeExternal(ObjectOutput out) throws IOException{
                out.writeInt(index);
                out.writeObject(diff);
            }
            @Override
            public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
                index = in.readInt();
                diff = (SharedContextRecord.Difference)in.readObject();
            }
        }
    }
}