/*
 * (c) Copyright 2005, 2006, 2007, 2008, 2009 Hewlett-Packard Development Company, LP
 * [See end of file]
 */

package com.hp.hpl.jena.iri.impl;

import java.io.UnsupportedEncodingException;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import com.hp.hpl.jena.iri.IRI;
import com.hp.hpl.jena.iri.IRIComponents;
import com.hp.hpl.jena.iri.ViolationCodes;

public class IRIFactoryImpl 
  extends AbsIRIFactoryImpl
  implements ViolationCodes, Force, IRIComponents {

public static final int UNKNOWN_SYNTAX = -4;
    //    boolean throwUncheckedExceptions = false;
    /*
    static final long conformanceMasks[] = {
            // RFC3986
              (1l<<ILLEGAL_CHAR) 
              |(1l<<ILLEGAL_PERCENT_ENCODING)
              |(1l<<EMPTY_SCHEME)
              |(1l<<IP_V4_HAS_FOUR_COMPONENTS)
              |(1l<<IP_V4_OCTET_RANGE)
              |(1l<<IP_V6_OR_FUTURE_ADDRESS_SYNTAX)
              |(1l<<IRI_CHAR)
              |(1l<<LTR_CHAR)
              |(1l<<NOT_XML_SCHEMA_WHITESPACE)
              |(1l<<SCHEME_MUST_START_WITH_LETTER)
              |(1l<<UNWISE_CHAR)
              |(1l<<WHITESPACE)
              |(1l<<ARBITRARY_CHAR)
              ,
            // RFC3987
              (1l<<ILLEGAL_CHAR) 
              |(1l<<ILLEGAL_PERCENT_ENCODING)
              |(1l<<EMPTY_SCHEME)
              |(1l<<IP_V4_HAS_FOUR_COMPONENTS)
              |(1l<<IP_V4_OCTET_RANGE)
              |(1l<<IP_V6_OR_FUTURE_ADDRESS_SYNTAX)
              |(1l<<LTR_CHAR)
              |(1l<<NOT_XML_SCHEMA_WHITESPACE)
              |(1l<<SCHEME_MUST_START_WITH_LETTER)
              |(1l<<UNWISE_CHAR)
              |(1l<<WHITESPACE)
              |(1l<<ARBITRARY_CHAR)
              ,
            // RDF
              (1l<<ILLEGAL_CHAR) 
              |(1l<<ILLEGAL_PERCENT_ENCODING)
              |(1l<<EMPTY_SCHEME)
              |(1l<<IP_V4_HAS_FOUR_COMPONENTS)
              |(1l<<IP_V4_OCTET_RANGE)
              |(1l<<IP_V6_OR_FUTURE_ADDRESS_SYNTAX)
              |(1l<<SCHEME_MUST_START_WITH_LETTER)
              |(1l<<RELATIVE_URI)
              ,
            // XLink
              (1l<<ILLEGAL_CHAR) 
              |(1l<<ILLEGAL_PERCENT_ENCODING)
              |(1l<<EMPTY_SCHEME)
              |(1l<<IP_V4_HAS_FOUR_COMPONENTS)
              |(1l<<IP_V4_OCTET_RANGE)
              |(1l<<IP_V6_OR_FUTURE_ADDRESS_SYNTAX)
              |(1l<<SCHEME_MUST_START_WITH_LETTER)
              |(1l<<NON_XML_CHARACTER)
              ,
            // XMLSchema
              (1l<<ILLEGAL_CHAR) 
              |(1l<<ILLEGAL_PERCENT_ENCODING)
              |(1l<<EMPTY_SCHEME)
              |(1l<<IP_V4_HAS_FOUR_COMPONENTS)
              |(1l<<IP_V4_OCTET_RANGE)
              |(1l<<IP_V6_OR_FUTURE_ADDRESS_SYNTAX)
              |(1l<<SCHEME_MUST_START_WITH_LETTER)
              |(1l<<NOT_XML_SCHEMA_WHITESPACE)
              |(1l<<NON_XML_CHARACTER)
              ,
            // IDN
//              (1l<<ACE_PREFIX)
              0
              ,
            // Should
              (1l<<LOWERCASE_PREFERRED) 
              |(1l<<PORT_SHOULD_NOT_BE_EMPTY)
              |(1l<<PORT_SHOULD_NOT_START_IN_ZERO)
    //          |(1l<<SCHEME_NAMES_SHOULD_BE_LOWER_CASE)
              |(1l<<PERCENT_ENCODING_SHOULD_BE_UPPERCASE)
              |(1l<<IPv6ADDRESS_SHOULD_BE_LOWERCASE)
              |(1l<<USE_PUNYCODE_NOT_PERCENTS)
              ,
            // Minting
      /* consider HAS_PASSWORD vs LOWER_CASE_PREFERRED
       * The former should be an error unless switched
       * off (but it can be, unlike a MUST), whereas the 
       * latter should be a warning by default.
       * /
              (1l<<LOWERCASE_PREFERRED) 
              |(1l<<PORT_SHOULD_NOT_BE_EMPTY)
              |(1l<<PORT_SHOULD_NOT_START_IN_ZERO)
    //          |(1l<<SCHEME_NAMES_SHOULD_BE_LOWER_CASE)
              |(1l<<PERCENT_ENCODING_SHOULD_BE_UPPERCASE)
              |(1l<<IPv6ADDRESS_SHOULD_BE_LOWERCASE)
              |(1l<<USE_PUNYCODE_NOT_PERCENTS)
              ,
            // DNS
              (1l<<NOT_DNS_NAME)
              ,
        };
        */
    protected long errors;
    protected long warnings;
    
    protected Set<Specification> specs = new HashSet<Specification>();

    public IRIFactoryImpl() {
    }

    public IRIFactoryImpl(IRIFactoryImpl template) {
        if (backwardCompatibleRelativeRefs.size()==Integer.MAX_VALUE)
    	   backwardCompatibleRelativeRefs = template.backwardCompatibleRelativeRefs;
        else 
        	backwardCompatibleRelativeRefs = new HashSet<String>(backwardCompatibleRelativeRefs);
        encoding = template.encoding;
        errors = template.errors;
        prohibited = template.prohibited;
        required = template.required;
        warnings = template.warnings;
        System.arraycopy(template.asErrors,0,asErrors,0,asErrors.length);
        System.arraycopy(template.asWarnings,0,asWarnings,0,asWarnings.length);
        Iterator<Entry<String, SchemeSpecificPart>> it = template.schemes.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, SchemeSpecificPart> entry = it.next();
            SchemeSpecificPart p = entry.getValue();
            if (p.withScheme()) {
                schemes.put(entry.getKey(),new WithScheme((WithScheme)p));
            } else if (p.port()!=IRI.NO_PORT) {
                schemes.put(entry.getKey(),new NoScheme(p.port()));
            }
        }
    }

    /*
    protected long recsToMask(int recs) {
        long  mask = 0;
        for (int i=0; recs != 0 && i<conformanceMasks.length; i++) {
            if ((recs & (1<<i)) != 0) {
                mask |= conformanceMasks[i];
                recs &= ~(1<<i);
            }
        }
        return mask;
    }
*/
    private final long getMask(boolean includeWarnings) {
        
        return includeWarnings?(errors|warnings):errors;
    }

    @Override
    protected IRIFactoryImpl getFactory() {
        return this;
    }
    
    @Override
    public IRI create(IRI i) {
        if (i instanceof AbsIRIImpl && 
                ((AbsIRIImpl)i).getFactory()==this)
            return i;
        return 
            create(i.toString());
    }

    boolean getSameSchemaRelativeReferences(String scheme) {
        return backwardCompatibleRelativeRefs.contains(scheme.toLowerCase());
    }
    
    private String encoding = "utf-8";
    String getEncoding() {
        return encoding;
    }
    public void setEncoding(String enc) throws UnsupportedEncodingException {
        // check enc is valid encoding.
        "".getBytes(enc);
        encoding = enc;
    }
    
   
    boolean asErrors[] = new boolean[]{
            true,
            true,
            false,
            true,
            true,
            true,
            
    };
    boolean asWarnings[] = new boolean[]{
      false,
      false,
      true,
      false,
      false,
      false
    };
    
    protected void setViolation(int ix, boolean e, boolean w){
        if (e && w)
            throw new IllegalArgumentException("xxxViolation(true,true) is not permitted.");
        initializing();
        asErrors[ix]=e;
        asWarnings[ix]=w;
        
    }
    
    protected boolean getAsWarnings(int ix) {
        return asWarnings[ix];
    }

    protected boolean getAsErrors(int ix) {
        return asErrors[ix];
    }
//    boolean isException(int code) {
//        return (errors & (1l<<code))!=0;
//    }

    private boolean initializing = true;
private Set<String> backwardCompatibleRelativeRefs = new HashSet<String>();
    protected void initializing() {
        if (!initializing)
            throw new IllegalStateException("Cannot reinitialize IRIFactory after first use.");
        
    }
    
    @Override
    public IRI create(String s) {
        initializing = false;
        return super.create(s);
    }

    public void setSameSchemeRelativeReferences(String scheme) {
        if (scheme.equals("*"))
          backwardCompatibleRelativeRefs  = new AbstractSet<String>(){

            @Override
            public int size() {
                return Integer.MAX_VALUE;
            }

            @Override
            public Iterator<String> iterator() {
                throw new UnsupportedOperationException();
            }
            @Override
            public boolean add(String o) {
                return false;
            }

            @Override
            public boolean contains(Object o) {
                return true;
            }
        };
        else 
            backwardCompatibleRelativeRefs.add(scheme.toLowerCase());
    }

    protected void useSpec(String name, boolean asErr) {
        initializing();
        Specification spec = Specification.get(name);
        specs.add(spec);
        for (int i=0; i<Force.SIZE; i++) {
            if (asErrors[i] || (asWarnings[i] && asErr)) {
                errors |= spec.getErrors(i);
            }
            if (asWarnings[i] ) {
                warnings |= spec.getErrors(i);
            }
        }
        prohibited |= spec.getProhibited();
        required |= spec.getRequired();
        warnings &= ~errors;
        
    }

    public SchemeSpecificPart getScheme(String scheme, Parser parser) {
        
        scheme = scheme.toLowerCase();
        SchemeSpecificPart p = schemes.get(scheme);
        if (p!=null) {
            p.usedBy(parser);
            return p;
        }
        int dash = scheme.indexOf('-');
        if (dash != -1) {
            if (scheme.startsWith("x-")) {
                p = noScheme();
            } else {
            	if (nonIETFScheme==null)
            		nonIETFScheme = new NoScheme() {
            	    @Override void usedBy(Parser pp) {
                      pp.recordError(SCHEME,UNREGISTERED_NONIETF_SCHEME_TREE);
                  }
                };
                p = nonIETFScheme;
            }
        } else if (Specification.schemes.containsKey(scheme)) {
            SchemeSpecification spec = (SchemeSpecification)Specification.schemes.get(scheme);
            p = new NoScheme(spec.port);
        } else{
        	if (unregisteredScheme==null){
        		unregisteredScheme = new NoScheme() {
        		    @Override void usedBy(Parser pp) {
        	            pp.recordError(SCHEME,UNREGISTERED_IANA_SCHEME);
        	        }
        	      };
        	}
            p= unregisteredScheme;
        }
        p.usedBy(parser);
//        System.err.println("Scheme: "+scheme);
        if (schemes.size() < 1000)
          schemes.put(scheme,p);
        return p;
    }
    
    private NoScheme unregisteredScheme=null;
    private NoScheme nonIETFScheme=null;

    public SchemeSpecificPart noScheme() {
        return noScheme;
    }
    private class WithScheme extends SchemeSpecificPart
    {
        long zerrors;
        long zwarnings;
        int zrequired;
        int zprohibited;
        boolean inited = false;
        final SchemeSpecification scheme;
        private WithScheme(WithScheme ws) {
            zerrors = ws.zerrors;
            zwarnings = ws.zwarnings;
            zprohibited = ws.zprohibited;
            zrequired = ws.zrequired;
            scheme = ws.scheme;
        }
        private WithScheme(SchemeSpecification spec, boolean asErr){
            scheme = spec;
            for (int i=0; i<Force.SIZE; i++) {
                if (asErrors[i] || (asWarnings[i] && asErr)) {
                    zerrors |= spec.getErrors(i);
                }
                if (asWarnings[i] ) {
                    zwarnings |= spec.getErrors(i);
                }
            }
            
        }
        @Override void usedBy(Parser parser) {
            if (!inited) {
                inited = true;
                zerrors |= errors;
                zwarnings |= warnings;
                zwarnings &= ~zerrors;
                zrequired = scheme.getRequired() | required;
                zprohibited = scheme.getProhibited() | prohibited;
            }
            
        }
        @Override
        public long getMask(boolean includeWarnings) {
            return includeWarnings?(zerrors|zwarnings):zerrors;
        }
        @Override
        public int getRequired() {
            return zrequired;
        }
        @Override
        public int getProhibited() {
            return zprohibited;
        }
        @Override
        public void analyse(Parser parser, int range) {
            scheme.analyse(parser,range);
        }
        @Override
        public int port() {
            return scheme.port;
        }
        @Override
        public boolean withScheme() {
            return true;
        }
        
    }
    private class NoScheme extends SchemeSpecificPart {
        
        NoScheme() {
            this(-1);
        }
        final private int port;
        NoScheme(int i) {
            port = i;
        }
        @Override
        public long getMask(boolean includeWarnings) {
            return IRIFactoryImpl.this.getMask(includeWarnings);
        }
        @Override
        public int getRequired() {
            return IRIFactoryImpl.this.getRequired();
        }
        @Override
        public int getProhibited() {
            return IRIFactoryImpl.this.getProhibited();
        }
        @Override
        void usedBy(Parser parser) { /* nothing */ }
        @Override
        public void analyse(Parser parser, int range) {/* nothing */ }
        @Override
        public int port() {
            return port;
        }
        @Override
        public boolean withScheme() {
            return false;
        }
        
    }
    
    final private NoScheme noScheme = new NoScheme();
    private int required = 0;
    private int prohibited = 0;

    public int getRequired() {
        return required;
    }

    public int getProhibited() {
        return prohibited ;
    }

    final private Map<String, SchemeSpecificPart> schemes = new HashMap<String, SchemeSpecificPart>();
    
    public void useSchemeSpecificRules(String scheme, boolean asErr)
    {
        if (scheme.equals("*"))
        {
            Iterator<String> it = Specification.schemes.keySet().iterator() ;
            while (it.hasNext())
            {
                scheme = it.next() ;
                if (!schemes.containsKey(scheme)) useSchemeSpecificRules(scheme, asErr) ;
            }
            return ;
        }
        scheme = scheme.toLowerCase() ;
        SchemeSpecification spec = (SchemeSpecification) Specification.schemes.get(scheme) ;
        if (spec == null)
        {
            schemes.put(scheme, noScheme) ;
        } else
        {
            schemes.put(scheme, new WithScheme(spec, asErr)) ;
        }

    }
}


/*
 *  (c) Copyright 2005, 2006, 2007, 2008, 2009 Hewlett-Packard Development Company, LP
 *  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.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */
 
