/*
 * ====================================================================
 * Copyright (c) 2004-2012 TMate Software Ltd.  All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.  The terms
 * are also available at http://svnkit.com/license.html
 * If newer versions of this license are posted there, you may use a
 * newer version instead, at your option.
 * ====================================================================
 */
package org.tmatesoft.svn.core;

import java.io.UnsupportedEncodingException;
import java.io.Serializable;
import java.util.Arrays;

import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;


/**
 * The <b>SVNPropertyValue</b> represents an object wrapper for string and binary version controlled 
 * properties providing a set of specific methods to work with them. 
 *  
 * <p/>
 * Since version 1.2 the <code>SVNKit</code> library supports binary properties as well.
 * 
 * @author  TMate Software Ltd.
 * @version 1.3
 * @since   1.2
 */
public class SVNPropertyValue implements Serializable {

    private static final long serialVersionUID = 4845L;
    
    private String myValue;
    private byte[] myData;

    /**
     * Creates a new property value object from the given byte array.
     * 
     * <p>
     * This method is intended to instantiate binary property values. However if <code>propertyName</code> is of 
     * {@link SVNProperty#isSVNProperty(String) svn domain}, then it attempts 
     * to encode the passed bytes into a <code>String</code> value using the 
     * <span class="javastring">"UTF-8"</span> charset. Finally, the property value object is created via
     * a call to {@link #create(String)}. In this way the text nature of the property is automatically 
     * preserved to avoid binary/text properties mess.  
     * 
     * @param  propertyName  property name
     * @param  data          array containing property bytes
     * @param  offset        offset in <code>data</code> to copy bytes from
     * @param  length        amount of bytes to copy from <code>data</code>
     * @return               new property value object; <span class="javakeyword">null</span> if 
     *                       <code>data</code> is <span class="javakeyword">null</span>  
     * 
     */
    public static SVNPropertyValue create(String propertyName, byte[] data, int offset, int length) {
        if (data == null) {
            return null;
        }
        if (SVNProperty.isSVNProperty(propertyName)) {
            String value;
            try {
                value = new String(data, offset, length, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                value = new String(data, offset, length);
            }
            return new SVNPropertyValue(value);
        }
        return new SVNPropertyValue(data, offset, length);
    }

    /**
     * Creates a new property value object from the given byte array.
     * 
     * <p>
     * This method is equivalent to <code>create(propertyName, data, 0, data.length)</code>.
     *  
     * @param  propertyName  property name
     * @param  data          array containing property bytes
     * @return               new property value object; <span class="javakeyword">null</span> if 
     *                       <code>data</code> is <span class="javakeyword">null</span>  
     * @see                  #create(String, byte[], int, int)
     */
    public static SVNPropertyValue create(String propertyName, byte[] data) {
        if (data == null) {
            return null;
        }
        return create(propertyName, data, 0, data.length);
    }

    /**
     * Creates a new property value object representing a text property value.
     * 
     * <p/>
     * This method is intended to create text property values only.
     * 
     * @param  propertyValue text property value which is stored as is 
     * @return               new property value object; <span class="javakeyword">null</span> if 
     *                       <code>propertyValue</code> is <span class="javakeyword">null</span>  
     */
    public static SVNPropertyValue create(String propertyValue) {
        if (propertyValue == null){
            return null;            
        }
        return new SVNPropertyValue(propertyValue);
    }

    /**
     * Creates a new property value object representing a text property value.
     * 
     * <p/>
     * This method is intended to create text property values only.
     * 
     * @param  propertyValue text property value which is stored as is 
     * @return               new property value object; <span class="javakeyword">null</span> if 
     *                       <code>propertyValue</code> is <span class="javakeyword">null</span>  
     */
    public static SVNPropertyValue create(char[] propertyValue, String encoding) {
        if (propertyValue == null){
            return null;            
        }
        return new SVNPropertyValue(propertyValue, encoding);
    }

    /**
     * Returns <code>byte[]</code> representation of <code>value</code>.
     * 
     * <p/>
     * If <code>value</code> is a {@link SVNPropertyValue#isString() string} property value, then bytes of 
     * the string are encoded using the <span class="javastring">"UTF-8"</span> charset and returned by this 
     * method. If encoding fails, then bytes are encoded using the default platform's charset.
     * 
     * <p/>
     * Otherwise, {@link SVNPropertyValue#getBytes()} is returned.
     * 
     * @param  value  property value object
     * @return        bytes of the property value represented by <code>value</code>; 
     *                <span class="javakeyword">null</span> if <code>value</code> is 
     *                <span class="javakeyword">null</span> 
     */
    public static byte[] getPropertyAsBytes(SVNPropertyValue value) {
        if (value == null) {
            return null;
        }
        if (value.isString()) {
            try {
                return value.getString().getBytes("UTF-8");
            } catch (UnsupportedEncodingException e) {
                return value.getString().getBytes();
            }
        }
        return value.getBytes();
    }

    /**
     * Returns <code>String</code> representation of <code>value</code>.
     * 
     * <p/>
     * If <code>value</code> is a {@link SVNPropertyValue#isBinary() binary} property value, then its bytes are
     * converted to a <code>String</code> encoding them with the <span class="javastring">"UTF-8"</span> charset 
     * and returned back to the caller. If that encoding fails, bytes are encoded with the default platform's 
     * charset.
     * 
     * <p/>
     * Otherwise, {@link SVNPropertyValue#getString()} is returned.
     * 
     * @param  value property value object 
     * @return       string property value; <span class="javakeyword">null</span> if <code>value</code> is
     *               <span class="javakeyword">null</span>
     */
    public static String getPropertyAsString(SVNPropertyValue value) {
        if (value == null) {
            return null;
        }
        if (value.isBinary()) {
            try {
                return new String(value.getBytes(), "UTF-8");
            } catch (UnsupportedEncodingException e) {
                return new String(value.getBytes());
            }
        }
        return value.getString();
    }

    /**
     * Returns <code>String</code> representation of <code>value</code>.
     * 
     * <p/>
     * If <code>value</code> is a {@link SVNPropertyValue#isBinary() binary} property value, then its bytes are
     * converted to a <code>String</code> encoding them with the <span class="javastring">"UTF-8"</span> charset 
     * and returned back to the caller. If that encoding fails, bytes are encoded with the default platform's 
     * charset.
     * 
     * <p/>
     * Otherwise, {@link SVNPropertyValue#getString()} is returned.
     * 
     * @param  value property value object 
     * @return       string property value; <span class="javakeyword">null</span> if <code>value</code> is
     *               <span class="javakeyword">null</span>
     */
    public static char[] getPropertyAsChars(SVNPropertyValue value) {
        if (value == null) {
            return null;
        }
        if (value.isBinary()) {
            return SVNEncodingUtil.getChars(value.getBytes(), "UTF-8");
        }
        return value.getString().toCharArray();
    }

    /**
     * Says whether the property value wrapped by this object is binary or not.
     * @return  <span class="javakeyword">true</span> if binary, otherwise <span class="javakeyword">false</span>
     */
    public boolean isBinary() {
        return myData != null;
    }

    /**
     * Returns property value bytes.
     * 
     * <p/>
     * Note: this will be always <span class="javakeyword">null</span> for <code>String</code> property values. 
     * 
     * @return byte array with property value bytes
     */
    public byte[] getBytes() {
        return myData;
    }

    /**
     * Says whether the property value wrapped by this object is <code>String</code> or not.
     * @return  <span class="javakeyword">true</span> if textual, otherwise <span class="javakeyword">false</span>
     */
    public boolean isString() {
        return myValue != null;
    }

    /**
     * Returns property value string.
     * 
     * <p/>
     * Note: this will be always <span class="javakeyword">null</span> for binary property values. 
     * 
     * @return property value string
     */
    public String getString() {
        return myValue;
    }

    /**
     * Returns a string representation of this object.
     * 
     * <p/>
     * Note: irrelevant for binary properties.
     *  
     * @return string representation of this object 
     */
    public String toString() {
        if (isBinary()) {
            return "property is binary";
        }
        return getString();
    }

    /**
     * Says whether this object and <code>obj</code> are equal or not.
     * 
     * @param  obj  object to compare with 
     * @return      <span class="javakeyword">true</span> in the following cases: 
     *              <ul>
     *              <li/><code>obj</code> is the same as this one (by reference) 
     *              <li/>if <code>obj</code> is an <code>SVNPropertyValue</code> and either has got the same
     *              <code>String</code> value in case this object holds a <code>String</code> value, or 
     *              the same byte array contents if this object represents a binary property value
     *              </ul>         
     */
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }

        if (obj instanceof SVNPropertyValue) {
            SVNPropertyValue value = (SVNPropertyValue) obj;
            if (isString()) {
                return myValue.equals(getPropertyAsString(value));
            } else if (isBinary()) {
                return Arrays.equals(myData, getPropertyAsBytes(value));
            }
        }
        return false;
    }

    /**
     * Returns the hash code for this object. If this object represents a {@link #isString() string} property,
     * then returns the hash code of the <code>String</code> object. Otherwise, this object represents 
     * a binary property and the hash code of the <code>byte[]</code> array is returned.
     * 
     * @return hash code
     */
    public int hashCode() {
        if (myValue != null) {
            return myValue.hashCode();
        }
        if (myData != null) {
            return myData.hashCode();
        }
        return super.hashCode();
    }

    private SVNPropertyValue(byte[] data, int offset, int length) {
        myData = new byte[length];
        System.arraycopy(data, offset, myData, 0, length);
    }

    private SVNPropertyValue(String propertyValue) {
        myValue = propertyValue;
    }

    private SVNPropertyValue(char[] propertyValue, String encoding) {
        myData = SVNEncodingUtil.getBytes(propertyValue, encoding == null ? "UTF-8" : encoding);
    }

    public static boolean areEqual(SVNPropertyValue propertyValue1, SVNPropertyValue propertyValue2) {
        if (propertyValue1 == null) {
            return propertyValue2 == null;
        }
        if (propertyValue2 == null) {
            return false;
        }
        byte[] propertyValueBytes1 = SVNPropertyValue.getPropertyAsBytes(propertyValue1);
        byte[] propertyValueBytes2 = SVNPropertyValue.getPropertyAsBytes(propertyValue2);

        return Arrays.equals(propertyValueBytes1, propertyValueBytes2);
    }

    public void clear() {
        SVNEncodingUtil.clearArray(myData);
        myValue = null;
    }
}