/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you 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 org.jboss.netty.bootstrap;

import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelHandler;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.util.ExternalResourceReleasable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import static org.jboss.netty.channel.Channels.*;

/**
 * A helper class which initializes a {@link Channel}.  This class provides
 * the common data structure for its subclasses which actually initialize
 * {@link Channel}s and their child {@link Channel}s using the common data
 * structure.  Please refer to {@link ClientBootstrap}, {@link ServerBootstrap},
 * and {@link ConnectionlessBootstrap} for client side, server-side, and
 * connectionless (e.g. UDP) channel initialization respectively.
 *
 * @apiviz.uses org.jboss.netty.channel.ChannelFactory
 */
public class Bootstrap implements ExternalResourceReleasable {

    private volatile ChannelFactory factory;
    private volatile ChannelPipeline pipeline = pipeline();
    private volatile ChannelPipelineFactory pipelineFactory = pipelineFactory(pipeline);
    private volatile Map<String, Object> options = new HashMap<String, Object>();

    /**
     * Creates a new instance with no {@link ChannelFactory} set.
     * {@link #setFactory(ChannelFactory)} must be called at once before any
     * I/O operation is requested.
     */
    protected Bootstrap() {
    }

    /**
     * Creates a new instance with the specified initial {@link ChannelFactory}.
     */
    protected Bootstrap(ChannelFactory channelFactory) {
        setFactory(channelFactory);
    }

    /**
     * Returns the {@link ChannelFactory} that will be used to perform an
     * I/O operation.
     *
     * @throws IllegalStateException
     *         if the factory is not set for this bootstrap yet.
     *         The factory can be set in the constructor or
     *         {@link #setFactory(ChannelFactory)}.
     */
    public ChannelFactory getFactory() {
        ChannelFactory factory = this.factory;
        if (factory == null) {
            throw new IllegalStateException(
                    "factory is not set yet.");
        }
        return factory;
    }

    /**
     * Sets the {@link ChannelFactory} that will be used to perform an I/O
     * operation.  This method can be called only once and can't be called at
     * all if the factory was specified in the constructor.
     *
     * @throws IllegalStateException
     *         if the factory is already set
     */
    public void setFactory(ChannelFactory factory) {
        if (factory == null) {
            throw new NullPointerException("factory");
        }
        if (this.factory != null) {
            throw new IllegalStateException(
                    "factory can't change once set.");
        }
        this.factory = factory;
    }

    /**
     * Returns the default {@link ChannelPipeline} which is cloned when a new
     * {@link Channel} is created.  {@link Bootstrap} creates a new pipeline
     * which has the same entries with the returned pipeline for a new
     * {@link Channel}.
     * <p>
     * Please note that this method is a convenience method that works only
     * when <b>1)</b> you create only one channel from this bootstrap (e.g.
     * one-time client-side or connectionless channel) or <b>2)</b> all handlers
     * in the pipeline is stateless.  You have to use
     * {@link #setPipelineFactory(ChannelPipelineFactory)} if <b>1)</b> your
     * pipeline contains a stateful {@link ChannelHandler} and <b>2)</b> one or
     * more channels are going to be created by this bootstrap (e.g. server-side
     * channels).
     *
     * @return the default {@link ChannelPipeline}
     *
     * @throws IllegalStateException
     *         if {@link #setPipelineFactory(ChannelPipelineFactory)} was
     *         called by a user last time.
     */
    public ChannelPipeline getPipeline() {
        ChannelPipeline pipeline = this.pipeline;
        if (pipeline == null) {
            throw new IllegalStateException(
                    "getPipeline() cannot be called " +
                    "if setPipelineFactory() was called.");
        }
        return pipeline;
    }

    /**
     * Sets the default {@link ChannelPipeline} which is cloned when a new
     * {@link Channel} is created.  {@link Bootstrap} creates a new pipeline
     * which has the same entries with the specified pipeline for a new channel.
     * <p>
     * Calling this method also sets the {@code pipelineFactory} property to an
     * internal {@link ChannelPipelineFactory} implementation which returns
     * a shallow copy of the specified pipeline.
     * <p>
     * Please note that this method is a convenience method that works only
     * when <b>1)</b> you create only one channel from this bootstrap (e.g.
     * one-time client-side or connectionless channel) or <b>2)</b> all handlers
     * in the pipeline is stateless.  You have to use
     * {@link #setPipelineFactory(ChannelPipelineFactory)} if <b>1)</b> your
     * pipeline contains a stateful {@link ChannelHandler} and <b>2)</b> one or
     * more channels are going to be created by this bootstrap (e.g. server-side
     * channels).
     */
    public void setPipeline(ChannelPipeline pipeline) {
        if (pipeline == null) {
            throw new NullPointerException("pipeline");
        }
        this.pipeline = pipeline;
        pipelineFactory = pipelineFactory(pipeline);
    }

    /**
     * Dependency injection friendly convenience method for
     * {@link #getPipeline()} which returns the default pipeline of this
     * bootstrap as an ordered map.
     * <p>
     * Please note that this method is a convenience method that works only
     * when <b>1)</b> you create only one channel from this bootstrap (e.g.
     * one-time client-side or connectionless channel) or <b>2)</b> all handlers
     * in the pipeline is stateless.  You have to use
     * {@link #setPipelineFactory(ChannelPipelineFactory)} if <b>1)</b> your
     * pipeline contains a stateful {@link ChannelHandler} and <b>2)</b> one or
     * more channels are going to be created by this bootstrap (e.g. server-side
     * channels).
     *
     * @throws IllegalStateException
     *         if {@link #setPipelineFactory(ChannelPipelineFactory)} was
     *         called by a user last time.
     */
    public Map<String, ChannelHandler> getPipelineAsMap() {
        ChannelPipeline pipeline = this.pipeline;
        if (pipeline == null) {
            throw new IllegalStateException("pipelineFactory in use");
        }
        return pipeline.toMap();
    }

    /**
     * Dependency injection friendly convenience method for
     * {@link #setPipeline(ChannelPipeline)} which sets the default pipeline of
     * this bootstrap from an ordered map.
     * <p>
     * Please note that this method is a convenience method that works only
     * when <b>1)</b> you create only one channel from this bootstrap (e.g.
     * one-time client-side or connectionless channel) or <b>2)</b> all handlers
     * in the pipeline is stateless.  You have to use
     * {@link #setPipelineFactory(ChannelPipelineFactory)} if <b>1)</b> your
     * pipeline contains a stateful {@link ChannelHandler} and <b>2)</b> one or
     * more channels are going to be created by this bootstrap (e.g. server-side
     * channels).
     *
     * @throws IllegalArgumentException
     *         if the specified map is not an ordered map
     */
    public void setPipelineAsMap(Map<String, ChannelHandler> pipelineMap) {
        if (pipelineMap == null) {
            throw new NullPointerException("pipelineMap");
        }

        if (!isOrderedMap(pipelineMap)) {
            throw new IllegalArgumentException(
                    "pipelineMap is not an ordered map. " +
                    "Please use " +
                    LinkedHashMap.class.getName() + '.');
        }

        ChannelPipeline pipeline = pipeline();
        for (Map.Entry<String, ChannelHandler> e: pipelineMap.entrySet()) {
            pipeline.addLast(e.getKey(), e.getValue());
        }

        setPipeline(pipeline);
    }

    /**
     * Returns the {@link ChannelPipelineFactory} which creates a new
     * {@link ChannelPipeline} for each new {@link Channel}.
     *
     * @see #getPipeline()
     */
    public ChannelPipelineFactory getPipelineFactory() {
        return pipelineFactory;
    }

    /**
     * Sets the {@link ChannelPipelineFactory} which creates a new
     * {@link ChannelPipeline} for each new {@link Channel}.  Calling this
     * method invalidates the current {@code pipeline} property of this
     * bootstrap.  Subsequent {@link #getPipeline()} and {@link #getPipelineAsMap()}
     * calls will raise {@link IllegalStateException}.
     *
     * @see #setPipeline(ChannelPipeline)
     * @see #setPipelineAsMap(Map)
     */
    public void setPipelineFactory(ChannelPipelineFactory pipelineFactory) {
        if (pipelineFactory == null) {
            throw new NullPointerException("pipelineFactory");
        }
        pipeline = null;
        this.pipelineFactory = pipelineFactory;
    }

    /**
     * Returns the options which configures a new {@link Channel} and its
     * child {@link Channel}s.  The names of the child {@link Channel} options
     * are prepended with {@code "child."} (e.g. {@code "child.keepAlive"}).
     */
    public Map<String, Object> getOptions() {
        return new TreeMap<String, Object>(options);
    }

    /**
     * Sets the options which configures a new {@link Channel} and its child
     * {@link Channel}s.  To set the options of a child {@link Channel}, prepend
     * {@code "child."} to the option name (e.g. {@code "child.keepAlive"}).
     */
    public void setOptions(Map<String, Object> options) {
        if (options == null) {
            throw new NullPointerException("options");
        }
        this.options = new HashMap<String, Object>(options);
    }

    /**
     * Returns the value of the option with the specified key.  To retrieve
     * the option value of a child {@link Channel}, prepend {@code "child."}
     * to the option name (e.g. {@code "child.keepAlive"}).
     *
     * @param key  the option name
     *
     * @return the option value if the option is found.
     *         {@code null} otherwise.
     */
    public Object getOption(String key) {
        if (key == null) {
            throw new NullPointerException("key");
        }
        return options.get(key);
    }

    /**
     * Sets an option with the specified key and value.  If there's already
     * an option with the same key, it is replaced with the new value.  If the
     * specified value is {@code null}, an existing option with the specified
     * key is removed.  To set the option value of a child {@link Channel},
     * prepend {@code "child."} to the option name (e.g. {@code "child.keepAlive"}).
     *
     * @param key    the option name
     * @param value  the option value
     */
    public void setOption(String key, Object value) {
        if (key == null) {
            throw new NullPointerException("key");
        }
        if (value == null) {
            options.remove(key);
        } else {
            options.put(key, value);
        }
    }

    /**
     * This method simply delegates the call to
     * {@link ChannelFactory#releaseExternalResources()}.
     */
    public void releaseExternalResources() {
        ChannelFactory factory = this.factory;
        if (factory != null) {
            factory.releaseExternalResources();
        }
    }

    /**
     * This method simply delegates the call to
     * {@link ChannelFactory#shutdown()}.
     */
    public void shutdown() {
        ChannelFactory factory = this.factory;
        if (factory != null) {
            factory.shutdown();
        }
    }
    /**
     * Returns {@code true} if and only if the specified {@code map} is an
     * ordered map, like {@link LinkedHashMap} is.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    static boolean isOrderedMap(Map<?, ?> map) {
        Class<?> mapType = map.getClass();
        if (LinkedHashMap.class.isAssignableFrom(mapType)) {
            // LinkedHashMap is an ordered map.
            return true;
        }

        // Not a LinkedHashMap - start autodetection.

        // Detect Apache Commons Collections OrderedMap implementations.
        Class<?> type = mapType;
        while (type != null) {
            for (Class<?> i: type.getInterfaces()) {
                if (i.getName().endsWith("OrderedMap")) {
                    // Seems like it's an ordered map - guessed from that
                    // it implements OrderedMap interface.
                    return true;
                }
            }
            type = type.getSuperclass();
        }

        // Does not implement OrderedMap interface.  As a last resort, try to
        // create a new instance and test if the insertion order is maintained.
        Map<Object, Object> newMap;
        try {
            newMap = (Map<Object, Object>) mapType.newInstance();
        } catch (Exception e) {
            // No default constructor - cannot proceed anymore.
            return false;
        }

        // Run some tests.
        List<String> expectedKeys = new ArrayList<String>();
        String dummyValue = "dummyValue";
        for (short element: ORDER_TEST_SAMPLES) {
            String key = String.valueOf(element);
            newMap.put(key, dummyValue);
            expectedKeys.add(key);

            Iterator<String> it = expectedKeys.iterator();
            for (Object actualKey: newMap.keySet()) {
                if (!it.next().equals(actualKey)) {
                    // Did not pass the test.
                    return false;
                }
            }
        }

        // The specified map passed the insertion order test.
        return true;
    }

    private static final short[] ORDER_TEST_SAMPLES = {
        682, 807, 637, 358, 570, 828, 407, 319,
        105,  41, 563, 544, 518, 298, 418,  50,
        156, 769, 984, 503, 191, 578, 309, 710,
        327, 720, 591, 939, 374, 707,  43, 463,
        227, 174,  30, 531, 135, 930, 190, 823,
        925, 835, 328, 239, 415, 500, 144, 460,
         83, 774, 921,   4,  95, 468, 687, 493,
        991, 436, 245, 742, 149, 821, 142, 782,
        297, 918, 917, 424, 978, 992,  79, 906,
        535, 515, 850,  80, 125, 378, 307, 883,
        836, 160,  27, 630, 668, 226, 560, 698,
        467, 829, 476, 163, 977, 367, 325, 184,
        204, 312, 486,  53, 179, 592, 252, 750,
        893, 517, 937, 124, 148, 719, 973, 566,
        405, 449, 452, 777, 349, 761, 167, 783,
        220, 802, 117, 604, 216, 363, 120, 621,
        219, 182, 817, 244, 438, 465, 934, 888,
        628, 209, 631,  17, 870, 679, 826, 945,
        680, 848, 974, 573, 626, 865, 109, 317,
         91, 494, 965, 473, 725, 388, 302, 936,
        660, 150, 122, 949, 295, 392,  63, 634,
        772, 143, 990, 895, 538,  59, 541,  32,
        669, 321, 811, 756,  82, 955, 953, 636,
        390, 162, 688, 444,  70, 590, 183, 745,
        543, 666, 951, 642, 747, 765,  98, 469,
        884, 929, 178, 721, 994, 840, 353, 726,
        940, 759, 624, 919, 667, 629, 272, 979,
        326, 608, 453,  11, 322, 347, 647, 354,
        381, 746, 472, 890, 249, 536, 733, 404,
        170, 959,  34, 899, 195, 651, 140, 856,
        201, 237,  51, 933, 268, 849, 294, 115,
        157,  14, 854, 373, 186, 872,  71, 523,
        931, 952, 655, 561, 607, 862, 554, 661,
        313, 909, 511, 752, 986, 311, 287, 775,
        505, 878, 422, 103, 299, 119, 107, 344,
        487, 776, 445, 218, 549, 697, 454,   6,
        462, 455,  52, 481, 594, 126, 112,  66,
        877, 172, 153, 912, 834, 741, 610, 915,
        964, 831, 575, 714, 250, 461, 814, 913,
        369, 542, 882, 851, 427, 838, 867, 507,
        434, 569,  20, 950, 792, 605, 798, 962,
        923, 258, 972, 762, 809, 843, 674, 448,
        280, 495, 285, 822, 283, 147, 451, 993,
        794, 982, 748, 189, 274,  96,  73, 810,
        401, 261, 277, 346, 527, 645, 601, 868,
        248, 879, 371, 428, 559, 278, 265,  62,
        225, 853, 483, 771,   9,   8, 339, 653,
        263,  28, 477, 995, 208, 880, 292, 480,
        516, 457, 286, 897,  21, 852, 971, 658,
        623, 528, 316, 471, 860, 306, 638, 711,
        875, 671, 108, 158, 646,  24, 257, 724,
        193, 341, 902, 599, 565, 334, 506, 684,
        960, 780, 429, 801, 910, 308, 383, 901,
        489,  81, 512, 164, 755, 514, 723, 141,
        296, 958, 686,  15, 799, 579, 598, 558,
        414,  64, 420, 730, 256, 131,  45, 129,
        259, 338, 999, 175, 740, 790, 324, 985,
        896, 482, 841, 606, 377, 111, 372, 699,
        988, 233, 243, 203, 781, 969, 903, 662,
        632, 301,  44, 981,  36, 412, 946, 816,
        284, 447, 214, 672, 758, 954, 804,   2,
        928, 886, 421, 596, 574,  16, 892,  68,
        546, 522, 490, 873, 656, 696, 864, 130,
         40, 393, 926, 394, 932, 876, 664, 293,
        154, 916,  55, 196, 842, 498, 177, 948,
        540, 127, 271, 113, 844, 576, 132, 943,
         12, 123, 291,  31, 212, 529, 547, 171,
        582, 609, 793, 830, 221, 440, 568, 118,
        406, 194, 827, 360, 622, 389, 800, 571,
        213, 262, 403, 408, 881, 289, 635, 967,
        432, 376, 649, 832, 857, 717, 145, 510,
        159, 980, 683, 580, 484, 379, 246,  88,
        567, 320, 643,   7, 924, 397,  10, 787,
        845, 779, 670, 716,  19, 600, 382,   0,
        210, 665, 228,  97, 266,  90, 304, 456,
        180, 152, 425, 310, 768, 223, 702, 997,
        577, 663, 290, 537, 416, 426, 914, 691,
         23, 281, 497, 508,  48, 681, 581, 728,
         99, 795, 530, 871, 957, 889, 206, 813,
        839, 709, 805, 253, 151, 613,  65, 654,
         93, 639, 784, 891, 352,  67, 430, 754,
         76, 187, 443, 676, 362, 961, 874, 330,
        331, 384,  85, 217, 855, 818, 738, 361,
        314,   3, 615, 520, 355, 920, 689,  22,
        188,  49, 904, 935, 136, 475, 693, 749,
        519, 812, 100, 207, 963, 364, 464, 572,
        731, 230, 833, 385, 499, 545, 273, 232,
        398, 478, 975, 564, 399, 504,  35, 562,
        938, 211,  26, 337,  54, 614, 586, 433,
        450, 763, 238, 305, 941, 370, 885, 837,
        234, 110, 137, 395, 368, 695, 342, 907,
        396, 474, 176, 737, 796, 446,  37, 894,
        727, 648, 431,   1, 366, 525, 553, 704,
        329, 627, 479,  33, 492, 260, 241,  86,
        185, 491, 966, 247,  13, 587, 602, 409,
        335, 650, 235, 611, 470, 442, 597, 254,
        343, 539, 146, 585, 593, 641, 770,  94,
        976, 705, 181, 255, 315, 718, 526, 987,
        692, 983, 595, 898, 282, 133, 439, 633,
        534, 861, 269, 619, 677, 502, 375, 224,
        806, 869, 417, 584, 612, 803,  58,  84,
        788, 797,  38, 700, 751, 603, 652,  57,
        240, 947, 350, 270, 333, 116, 736,  69,
         74, 104, 767, 318, 735, 859, 357, 555,
        411, 267, 712, 675, 532, 825, 496, 927,
        942, 102,  46, 192, 114, 744, 138, 998,
         72, 617, 134, 846, 166,  77, 900,   5,
        303, 387, 400,  47, 729, 922, 222, 197,
        351, 509, 524, 165, 485, 300, 944, 380,
        625, 778, 685,  29, 589, 766, 161, 391,
        423,  42, 734, 552, 215, 824, 908, 229,
         89, 251, 199, 616,  78, 644, 242, 722,
         25, 437, 732, 956, 275, 200, 970, 753,
        791, 336, 556, 847, 703, 236, 715,  75,
        863, 713, 785, 911, 786, 620, 551, 413,
         39, 739, 820, 808, 764, 701, 819, 173,
        989, 345, 690, 459,  60, 106, 887, 996,
        365, 673, 968, 513,  18, 419, 550, 588,
        435, 264, 789, 340, 659, 466, 356, 288,
         56, 708, 557, 488, 760, 332, 402, 168,
        202, 521, 757, 205, 706, 441, 773, 231,
        583, 386, 678, 618, 815, 279,  87, 533,
         61, 548,  92, 169, 694, 905, 198, 121,
        410, 139, 657, 640, 743, 128, 458, 866,
        501, 348, 155, 276, 101, 858, 323, 359,
    };
}
